赞
踩
滑动窗口算法 通常用于解决字符串和数组相关的问题(如找子串、子数组)。
该算法的基本思想是维护一个固定大小的窗口,通过该窗口在字符串或数组上滑动,从而寻找符合条件的子串或子数组。在每次窗口滑动时,我们只需要对窗口内的元素进行简单的更新,以便快速得到新的结果。
解法思路
for (int i = 0; i < n; i++)
int sum = 0;
for (int j = i; j < n; j++)
sum += nums[j];
if (sum >= target)
// ...
根据上图所示,而单调性+同向双指针的方法,我们称之为滑动窗口。
而滑动窗口算法解题一般分为以下步骤:
代码
int minSubArrayLen(int target, vector<int>& nums) { // 滑动窗口 int n = nums.size(); int left = 0, right = 0; int sum = 0, len = INT_MAX; while(right < n) { sum += nums[right]; // 从前向后遍历数组 计算sum while(sum >= target) // 计算结果,更新sum { len = min(len, right - left + 1); sum -= nums[left++]; } ++right; } // 如果没有满足条件的len,则返回0 return len == INT_MAX ? 0 : len; }
解法思路
解法一:暴力枚举+哈希表
解法二:找规律,使用滑动窗口+哈希表
代码
int lengthOfLongestSubstring(string s) { int hash[128] = {0}; // 用数组模拟哈希表 int ret = 0; for(int left = 0, right = 0; right < s.size(); ++right) { hash[s[right]]++; // 记录当前字符并++right while(hash[s[right]] > 1) // 遇到重复数,Left到重复数(前面的)的后一位 { hash[s[left++]]--; } ret = max(ret, right - left + 1); } return ret; }
解法思路
根据题目,我们可以将k个0翻转成1,由此我们可以将题目要求转化为:
解法一:暴力枚举+zero计数器
O(n^2)
解法二:滑动窗口+zero计数器
代码
int longestOnes(vector<int>& nums, int k) { int left = 0, right = 0; int zero = 0, ret = 0; // 计数器,统计0的个数 while(right < nums.size()) { // 判断 if(nums[right] == 0) zero++; // 此时已经达到了最大能翻转的0的个数 while(zero > k){ if(nums[left] == 0) zero--; left++; } // 更新结果 ret = max(ret, right - left + 1); right++; // 移动窗口 } return ret; }
解法思路
代码
int minOperations(vector<int>& nums, int x) { // 思路:正难则反(将该题反过来思考) // 求和为target(sum-x)的最长子数组 int sum = 0, n = nums.size(); for(int num : nums) sum += num; // 计算数组总和 int target = sum - x, ret = -1; // x可能大于sum,此时return -1 if(target < 0) return -1; // 解法:滑动窗口 for(int left = 0, right = 0, tmp = 0; right < n; ++right) { tmp += nums[right]; // 进窗口 while(tmp > target) // 判断 tmp -= nums[left++]; // 出窗口 if(tmp == target) ret = max(ret, right - left + 1); // 更新结果 } // 由于我们是求的值为sum-x的最长子数组,按照题目要求返回n-ret return ret == -1 ? ret : n - ret; }
解法思路
题意分析:即找到最长子数组,子数组要求只有两种元素
解法一:暴力枚举+哈希表
解法二:滑动窗口+哈希表
代码
int totalFruit(vector<int>& fruits) { // 该题实际上可以理解为:求最长子数组,要求子数组只有两种类型的水果 int left = 0, right = 0, ret = 0, n = fruits.size(); // unordered_map<int, int> hash; // 哈希表统计某种类水果出现次数 int hash[100001] = {0}; // 小优化,由于水果种类<=10^5,使用数组作为哈希表 int kinds = 0; // 分析题目,解法为滑动窗口 while(right < n) { if(hash[fruits[right]] == 0) kinds++; // 使用变量判断水果种类 hash[fruits[right]]++; // 进窗口 while(kinds > 2) // 判断 { hash[fruits[left]]--; // 出窗口 if(hash[fruits[left]] == 0) kinds--; ++left; } ret = max(ret, right - left + 1); // 更新结果 ++right; } return ret; }
解法思路
代码
vector<int> findAnagrams(string s, string p) { int hash1[26] = {0}; // hash1 存储p中各个元素的出现次数 for(char ch : p) hash1[ch - 'a']++; int count = 0, pLen = p.size(); // count 记录有效字符的个数 int hash2[26] = {0}; // 记录每次滑动窗口元素出现次数 vector<int> ret; for(int left = 0, right = 0; right < s.size(); ++right) { char in = s[right]; hash2[in - 'a']++; // 进窗口 if(hash2[in - 'a'] <= hash1[in - 'a']) count++; // 维护count,满足有效字符条件则++ if(right - left + 1 > pLen) // 滑动窗口过大 { char out = s[left++]; if(hash2[out - 'a'] <= hash1[out - 'a']) count--; hash2[out - 'a']--; } if(count == pLen) ret.push_back(left); // 更新结果 } return ret; }
解法思路
代码
关于代码注释中提到的小优化:
// Similar to LCR 015. 找到字符串中所有字母异位词 vector<int> findSubstring(string s, vector<string>& words) { int len = words[0].size(), wSize = words.size(); vector<int> ret; // 结果数组 if(len > s.size()) return ret; unordered_map<string, int> hash2; // 统计 words 字符串次数 for(string str : words) hash2[str]++; // 统计次数 for(int i = 0; i < len; ++i) // 执行len次 { unordered_map<string, int> hash1; // 存储 s 中各字符串的次数 for(int left = i, right = i, count = 0; right + len <= s.size(); right += len) // right每次跳过一个words中字符串大小 // count 计算有效字符串的个数 { string in = s.substr(right, len); // right位置字符串 if(hash2.count(in) && ++hash1[in] <= hash2[in]) count++; // 进窗口 (hash2.count(in),小优化如果hash2中没有in,就不执行,方括号会创建变量) // 判断 if(right - left + 1 > len * wSize) // 出窗口 + 维护count { string out = s.substr(left, len); if(hash2.count(out) && hash1[out] <= hash2[out]) count--; hash1[out]--; left += len; } if(count == wSize) ret.push_back(left); } } return ret; }
解法思路
代码
string minWindow(string s, string t) { int hash1[128] = {0}; // 统计t的字符 int hash2[128] = {0}; // 统计字符串 int kinds = 0; // 统计t中 字符的种类 for(char ch : t) if(hash1[ch]++ == 0) kinds++; int minLen = INT_MAX, begin = -1; // 最后要用的结果,最小子串的长度和起始位置 for(int left = 0, right = 0, count = 0; right < s.size(); ++right) { char in = s[right];// 进窗口 if(++hash2[in] == hash1[in]) count++; // 当t中的字符次数与当前in的次数一致时,更新count // 判断 while(count == kinds) { if(right - left + 1 < minLen) { minLen = right - left + 1; // 更新结果 begin = left; // 记录待返回字符串的起始索引 } char out = s[left++]; // 出窗口 if(hash2[out]-- == hash1[out]) count--; } } return begin == -1 ? "" : s.substr(begin, minLen); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。