[刷题总结] 双指针 滑动窗口
🌻个人主页:路飞雪吖~-CSDN博客
目录
一、双指针
1.题目:283. 移动零 - 力扣(LeetCode)
2.题目:1089. 复写零 - 力扣(LeetCode)
3.题目:202. 快乐数 - 力扣(LeetCode)
4.题目:11. 盛最多水的容器 - 力扣(LeetCode)
5.题目:611. 有效三角形的个数 - 力扣(LeetCode)
6.题目:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
7.题目:15. 三数之和 - 力扣(LeetCode)
8.题目:18. 四数之和 - 力扣(LeetCode)
二、滑动窗口
1.题目:209. 长度最小的子数组 - 力扣(LeetCode)
2.题目:3. 无重复字符的最长子串 - 力扣(LeetCode)
3.题目:1004. 最大连续1的个数 III - 力扣(LeetCode)
4.题目:1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
5.题目:904. 水果成篮 - 力扣(LeetCode)
6.题目:438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
7.题目:30. 串联所有单词的子串 - 力扣(LeetCode)
8.题目:76. 最小覆盖子串 - 力扣(LeetCode)
一、双指针
常见两种形式:
<1> 对撞指针:
• 从两端向中间移动
• 一般用于顺序结构中
• 终止条件:两个指针相遇或错开
<2> 快慢指针
• 使用两个移动速度不同的指针在数组或链表等序列结构上移动
• 常用:慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢
注意:将数组的内容分成左右两部 分。这种类型的题,⼀般就是使⽤「双指针」来解决。
1.题目:283. 移动零 - 力扣(LeetCode)
思路: <双指针>
• 定义两个指针
• right != 0,进行交换 swap( left, right); 使得[0 , left]的这个区间全部都是非零的区间;
• left++;
class Solution {
public:void moveZeroes(vector<int>& nums) {for(int left = 0, right = 0; right < nums.size(); right++){if(nums[right] != 0){swap(nums[left], nums[right]);left++;}}}
};
2.题目:1089. 复写零 - 力扣(LeetCode)
正难则反,从前往后覆盖不行,就从后往前。
(1)异地求解(新开数组)
class Solution {
public:void duplicateZeros(vector<int>& arr) {// 异地操作 新开数组int n = arr.size();vector<int> newArr; // 初始化空数组for (int num : arr) {if (newArr.size() >= n) break; // 长度足够时提前终止if (num == 0) {newArr.push_back(0); // 添加第一个0if (newArr.size() < n) // 检查是否还能添加第二个0newArr.push_back(0);} else newArr.push_back(num); // 非零元素直接添加}arr = newArr; // 将结果复制回原数组}
};
(2)双指针(原地)
先根据“异地”操作,然后优化成双指针下的“就地”操作
<1> 先找到最后一个“复写”的数(模拟一遍往后)
• 先判断 cur 位置的值
• 决定 dest 向后移动一步或者两步
• 判断一下 dest 是否已经到结束为止
• cur++
• (处理边界情况)
<2> "从后往前"完成复写操作
class Solution {
public:void duplicateZeros(vector<int>& arr) {// 1、先找到最后一个“复写”的数int dest = -1, cur = 0, n = arr.size();while(cur < n){if(arr[cur]) dest++;else dest += 2;if(dest >= n - 1) break;cur++;}// 处理边界情况if(dest == n){arr[n - 1] = 0;cur--;dest -= 2;}// 从后往前复写while(cur >= 0){if(arr[cur]) arr[dest--] = arr[cur--];else {arr[dest--] = 0;arr[dest--] = 0;cur--;}}}
};
3.题目:202. 快乐数 - 力扣(LeetCode)
(类似链表成环问题)快慢指针
class Solution {
public:int bitSum(int n){int sum = 0;while(n){int t =n % 10;// 取最后一个数sum += t * t;// 把数据存入n = n / 10;// 去掉最后一个数}return sum;}bool isHappy(int n) {// 快慢指针int slow = n, fast = bitSum(n);// fast在第二个位置while(slow != fast){slow = bitSum(slow);// slow走一步fast = bitSum(bitSum(fast));// fast走两步}return slow == 1;// 判断相遇位置是否等于1}
};
4.题目:11. 盛最多水的容器 - 力扣(LeetCode)
class Solution {
public:int maxArea(vector<int>& height) {int left = 0, right = height.size() - 1, ret = 0;while(left < right){int V = min(height[left], height[right]) * (right - left);// 求出体积ret = max(ret, V);// 每次取较大的体积数// 移动指针if(height[left] <= height[right]) left++; else right--;}return ret;;}
};
5.题目:611. 有效三角形的个数 - 力扣(LeetCode)
class Solution {
public:int triangleNumber(vector<int>& nums) {// 优化sort(nums.begin(), nums.end());// 双指针int n = nums.size();int ret = 0;// 记录有效个数for(int i = n - 1; i >= 2; i--)// 固定最大的数{int left = 0, right = i - 1;// left固定的第一个数,right固定的第二个数while(left < right){if(nums[left] + nums[right] > nums[i])// a + b > c 符合条件{ret += right - left;// 在符合条件的区间right--;// 统计完符合的区间后,减小区间,接着下一轮统计}elseleft++;// 不符合条件,++到下一个数寻找}}return ret;}
};
6.题目:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
class Solution {
public:vector<int> twoSum(vector<int>& price, int target) {int n = price.size();int left = 0, right = n - 1;while(left < right){int sum = price[left] + price[right];if(sum < target) left++;if(sum > target) right--;if(sum == target)return {price[left], price[right]};}return {};}
};
7.题目:15. 三数之和 - 力扣(LeetCode)
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> ret;// 排序sort(nums.begin(), nums.end());// 双指针int n = nums.size();for(int i = 0; i < n;)// 固定数 a{int left = i + 1, right = n - 1, target = -nums[i];while(left < right){int sum = nums[left] + nums[right];if(sum > target) right--;else if(sum < target) left++;else // sum == target{ret.push_back({nums[i], nums[left], nums[right]});// 把符合条件的数据存入left++, right--; // 不漏// 去重 left rightwhile(left < right && nums[left] == nums[left - 1]) left++;while(left < right && nums[right] == nums[right + 1]) right--;}}// 去重 ii++;while(i < n && nums[i] == nums[i - 1]) i++; }return ret;}
};
8.题目:18. 四数之和 - 力扣(LeetCode)
与"三数之和"类似,四数之和固定两个数。
class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> ret;// 排序sort(nums.begin(), nums.end());// 双指针int n = nums.size();for(int i = 0; i < n;)// 固定一个数a{for(int j = i + 1; j < n;)// 固定一个数b{int left = j + 1, right = n - 1;while(left < right){long long dest = (long long)target - nums[i] - nums[j];long long sum = nums[left] + nums[right];if(sum > dest) right--;else if(sum < dest) left++;else // sum == target{// 保存数据ret.push_back({nums[i], nums[j], nums[left], nums[right]});left++, right--;// 不漏//去重 left rightwhile(left < right && nums[left] == nums[left - 1]) left++;while(left < right && nums[right] == nums[right + 1]) right--;}}// 去重 jj++;while(j < n && nums[j] == nums[j - 1]) j++;}// 去重 ii++;while(i < n && nums[i] == nums[i - 1]) i++;}return ret;}
};
二、滑动窗口
滑动窗口,即一个区间左右滑动。
• left = 0, right = 0;
• 进窗口
• 判断
• 出窗口
• 更新结果
1.题目:209. 长度最小的子数组 - 力扣(LeetCode)
利用单调性(规避了很多没有必要的枚举行为),使用“同向指针”来进行优化。
• left = 0, right = 0;
• 进窗口 (sum += nums[right];)
• 判断 (sum >= target;)
• 出窗口 (left++;)
• 更新结果 ( min(len, right - left + 1);)
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int n = nums.size(), sum = 0, len = INT_MAX;for(int left = 0, right = 0; right < n; right++){sum += nums[right];// 进窗口while(sum >= target)// 判断{len = min(len, right - left + 1);// 更新结果sum -= nums[left];left++;// 出窗口}}return len == INT_MAX ? 0 : len;}
};
2.题目:3. 无重复字符的最长子串 - 力扣(LeetCode)
hash + 滑动窗口:
• left = 0, right = 0;
• 进窗口 (让字符进入hash表 hash[s[right]]++;)
• 判断 (窗口内是否出现重复字符 hash[s[right]] > 1;)
出窗口 (从hash表中删除该字符 hash[s[left++]]--;)
• 更新结果 (len = max(len, right - left + 1);)
class Solution {
public:int lengthOfLongestSubstring(string s) {int n = s.size(), len = 0;int hash[128] = { 0 };for(int left = 0, right = 0; right < n; right++){hash[s[right]]++;// 进窗口while(hash[s[right]] > 1)// 判断hash[s[left++]]--;// 出窗口len = max(len, right - left + 1);// 更新结果}return len;}
};
3.题目:1004. 最大连续1的个数 III - 力扣(LeetCode)
滑动窗口:
• left = 0, right = 0;
• 进窗口
如果为1,无视
如果为0,计数器+1
• 判断 (zero > k)
出窗口
• 更新结果
class Solution {
public:int longestOnes(vector<int>& nums, int k) {int n = nums.size(), len = 0, zero = 0;for(int left = 0, right = 0; right < n; right++){if(nums[right] == 0) zero++;// 进窗口while(zero > k)// 判断if(nums[left++] == 0) zero--;// 出窗口len = max(len, right -left + 1);// 更新结果}return len;}
};
4.题目:1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
🌟正难则反!!! ----转化为---> 求最长子串
• target = sum[nums] - x; // target 为最长子串数的和
• len 为最长子数的个数
• left = 0, right = 0;
• 进窗口 sumLR += nums[right]
• 判断 sumLR > target (不符合,要出窗口)
• 出窗口 sumLR -= nums[left++];
• 更新结果 sum == target
class Solution {
public:int minOperations(vector<int>& nums, int x) {int n = nums.size(), sum = 0;for(auto e : nums) sum += e;int target = sum - x;int len = -1;if(target < 0) return -1;for(int left = 0, right = 0, sumLR = 0; right < n; right++){sumLR += nums[right];//进窗口while(sumLR > target)// 判断sumLR-= nums[left++];// 出窗口if(sumLR == target)// 更新结果len = max(len, right - left + 1);}if(len == -1) return -1;else return n - len;}
};
5.题目:904. 水果成篮 - 力扣(LeetCode)
✨ hash + 滑动窗口
• left = 0, right = 0;
• 进窗口 hash[f[right]]++;
• 判断 hash.size() > 2
• 出窗口 hash[f[left]]--; (要判断该位置是否为0)
• 更新结果
class Solution {
public:int totalFruit(vector<int>& fruits) {unordered_map<int, int> hash;int n = fruits.size(), len =0;for(int left = 0, right = 0; right < n; right++){hash[fruits[right]]++;// 进窗口while(hash.size() > 2)// 判断{hash[fruits[left]]--;if(hash[fruits[left]] == 0)// 树上无水果,删除hash.erase(fruits[left]);left++;}len = max(len , right - left + 1);// 更新结果}return len;}
};
6.题目:438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
• left = 0, right = 0, count = 0; // count 记录进入hash2的字符数
• 进窗口 hash2[ in ]++;// 判断count是否需要++
• 判断 right - left + 1 > p.size(); // >出窗口
• 出窗口 hash2[ out ]--;
• 更新结果 check( count == p.size());// 直接使用 check(hash2, hash1) --> 大量数据时,使得时间复杂度高
利用变量count来统计窗口中“有效字符”的个数;
进窗口后: 判断 hash2[in] <= hash1[in] --> count++;
出窗口前: 判断 hash2[out] <= hash1[out] --> count--;
更新结果: count == p.size();
class Solution {
public:vector<int> findAnagrams(string s, string p) {vector<int> ret;int hash1[26] = { 0 };for(auto ch : p) hash1[ch - 'a']++;// 记录p中字符出现的个数int hash2[26] = { 0 };// 记录sint n = p.size();for(int left = 0, right = 0, count = 0; right < s.size(); right++){char in = s[right];hash2[in - 'a']++;// 进窗口if(hash2[in -'a'] <= hash1[in - 'a']) count++;// 维护countif(right - left + 1 > n)// 判断{char out = s[left++];// 出窗口if(hash2[out - 'a'] <= hash1[out - 'a']) count--;hash2[out - 'a']--;// 把表中的数据去掉}// 更新结果if(count == n) ret.push_back(left);}return ret;}
};
7.题目:30. 串联所有单词的子串 - 力扣(LeetCode)
class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> ret;unordered_map<string, int> hash1;for(auto& string : words) hash1[string]++;int len = words[0].size(), m = words.size();for(int i = 0; i < len; i++)// 执行 len 次{unordered_map<string, int> hash2;for(int left = i, right = i, count = 0; right + len < s.size(); right += len)// 每次移动 len 个长度{string in = s.substr(right, len);// 截取 一个单词的长度hash2[in]++;// 进窗口if(hash2[in] <= hash1[in]) count++;// 维护 countif(right - left + 1 > m * len)// 判断{string out = s.substr(left, len);// 出窗口if(hash2[out] <= hash1[out]) count--;// 维护counthash2[out]--;left += len;// 每次移动 len 个长度}if(count == m) ret.push_back(left);}}return ret;}
};
8.题目:76. 最小覆盖子串 - 力扣(LeetCode)
• left = 0, right = 0, count = 0;
• 进窗口 hash[in]++;
• 判断 count == kinds
进窗口之后: 当 hash2[in] == hash1[in] --> count++;
出窗口前:当 hash2[out] == hash1[out] --> count--;
判断条件 count == kinds
• 更新结果 --> 起始位置,最短长度
• 出窗口 hash2[out]--;
class Solution {
public:string minWindow(string s, string t) {int hash1[128] = { 0 };// 统计 t 中每一个字符出现的频次int kinds = 0;// 统计有效字符有多少种for(auto ch : t)if(hash1[ch]++ == 0) kinds++;int hash2[128] = { 0 };// 统计窗口内每个字符的频次int minlen = INT_MAX, begin = -1;for(int left = 0, right = 0, count = 0; right < s.size(); right++){char in = s[right];hash2[in]++;// 进窗口if(hash2[in] == hash1[in]) count++;// 维护countwhile(count == kinds)// 判断{if(right - left + 1 < minlen)//更新结果 {minlen = right - left + 1;begin = left;}char out = s[left++];// 出窗口if(hash2[out] == hash1[out]) count--;hash2[out]--;}}if(begin == -1) return "";else return s.substr(begin, minlen);}
};