多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

滑动窗口算法:高效解决子区间问题的双指针技巧

滑动窗口算法:高效解决子区间问题的双指针技巧 1. 滑动窗口算法核心思想解析滑动窗口Sliding Window是解决数组/字符串子区间问题的高效算法范式。它的核心在于维护一个动态变化的窗口通过调整窗口边界来寻找满足条件的解避免了暴力枚举带来的高时间复杂度。1.1 算法适用场景特征适合滑动窗口解决的问题通常具备以下三个特征数据结构为线性序列数组、字符串、链表等求解目标与子区间相关如最长/最短满足条件的子串窗口内元素满足某种单调性如元素和随窗口增大而单调递增典型应用场景包括无重复字符的最长子串LeetCode 3最小覆盖子串LeetCode 76字符串排列LeetCode 567最大连续1的个数LeetCode 4871.2 双指针实现原理滑动窗口通常用双指针实现分为固定窗口和可变窗口两种模式// 可变窗口模板 int left 0, right 0; while (right s.size()) { // 扩展右边界 window.add(s[right]); right; // 满足条件时收缩左边界 while (valid(window)) { // 更新结果 res update(res, window); window.remove(s[left]); left; } }固定窗口则是维护一个长度不变的窗口典型如求长度为k的子数组最大和// 固定窗口模板 int sum 0, maxSum INT_MIN; for (int i 0; i nums.size(); i) { sum nums[i]; if (i k - 1) { maxSum max(maxSum, sum); sum - nums[i - (k - 1)]; } }2. LeetCode Hot 100滑动窗口经典题解2.1 无重复字符的最长子串LeetCode 3问题描述给定字符串s找出不含有重复字符的最长子串长度。int lengthOfLongestSubstring(string s) { unordered_mapchar, int window; int left 0, right 0; int res 0; while (right s.size()) { char c s[right]; right; window[c]; while (window[c] 1) { char d s[left]; left; window[d]--; } res max(res, right - left); } return res; }关键点使用哈希表记录窗口内字符出现次数当某个字符计数1时收缩左边界每次窗口合法时更新最大长度时间复杂度O(n)空间复杂度O(字符集大小)2.2 最小覆盖子串LeetCode 76问题描述给定字符串s和t在s中找出包含t所有字符的最短子串。string minWindow(string s, string t) { unordered_mapchar, int need, window; for (char c : t) need[c]; int left 0, right 0; int valid 0; int start 0, len INT_MAX; while (right s.size()) { char c s[right]; right; if (need.count(c)) { window[c]; if (window[c] need[c]) valid; } while (valid need.size()) { if (right - left len) { start left; len right - left; } char d s[left]; left; if (need.count(d)) { if (window[d] need[d]) valid--; window[d]--; } } } return len INT_MAX ? : s.substr(start, len); }关键点使用两个哈希表分别记录需要的字符和当前窗口的字符valid变量统计满足条件的字符个数当valid等于need大小时尝试收缩窗口时间复杂度O(n)空间复杂度O(字符集大小)3. 滑动窗口的优化技巧3.1 哈希表替代方案对于字符集有限的问题如仅小写字母可以用数组代替哈希表提升效率int window[26] {0}; // 小写字母频率统计3.2 边界条件处理常见边界陷阱空字符串输入目标子串不存在的情况所有字符都相同的情况3.3 复杂度的数学证明滑动窗口的O(n)时间复杂度可以通过摊还分析证明每个元素最多被右指针访问一次每个元素最多被左指针访问一次总操作次数为2n因此是线性复杂度4. 滑动窗口与其他算法的比较4.1 与暴力法的对比暴力解法通常需要O(n^2)时间复杂度// 暴力解法示例 for (int i 0; i n; i) for (int j i; j n; j) if (isValid(i, j)) updateResult();滑动窗口通过消除重复计算将复杂度降至O(n)4.2 与动态规划的关系某些滑动窗口问题可以用DP解决但空间复杂度更高滑动窗口O(1)或O(m)额外空间DP解法通常需要O(n)空间4.3 与双指针的区别广义上滑动窗口属于双指针技术但更强调明确的窗口概念窗口内元素的统计信息维护特定的收缩/扩展条件5. 实战问题分类解析5.1 计数类问题特征需要统计窗口内某类元素的数量例题最大连续1的个数IIILeetCode 1004int longestOnes(vectorint nums, int k) { int left 0, right 0; int zeros 0; int res 0; while (right nums.size()) { if (nums[right] 0) zeros; right; while (zeros k) { if (nums[left] 0) zeros--; left; } res max(res, right - left); } return res; }5.2 子串排列问题特征判断某排列是否存在于字符串中例题字符串的排列LeetCode 567bool checkInclusion(string s1, string s2) { unordered_mapchar, int need, window; for (char c : s1) need[c]; int left 0, right 0; int valid 0; while (right s2.size()) { char c s2[right]; right; if (need.count(c)) { window[c]; if (window[c] need[c]) valid; } while (right - left s1.size()) { if (valid need.size()) return true; char d s2[left]; left; if (need.count(d)) { if (window[d] need[d]) valid--; window[d]--; } } } return false; }5.3 最优解问题特征寻找满足条件的最优最大/最小子区间例题乘积小于K的子数组LeetCode 713int numSubarrayProductLessThanK(vectorint nums, int k) { if (k 1) return 0; int left 0, right 0; int product 1; int res 0; while (right nums.size()) { product * nums[right]; right; while (product k) { product / nums[left]; left; } res right - left; } return res; }6. 滑动窗口的常见陷阱与调试技巧6.1 窗口收缩条件错误典型错误收缩过早导致错过最优解收缩不足导致结果不准确调试方法打印窗口左右边界和关键变量用小规模测试用例手动模拟6.2 哈希表更新时机不当常见错误先更新结果再收缩窗口哈希表更新与指针移动不同步正确做法// 正确的更新顺序 window.add(s[right]); right; // 检查条件 while (invalid(window)) { window.remove(s[left]); left; } // 更新结果 update(res);6.3 特殊输入处理需要特别注意空输入所有元素相同极值情况如k0或kn7. 滑动窗口在竞赛中的应用7.1 多指针扩展某些问题需要维护多个窗口或使用多指针三指针解决某些特殊子序列问题并行滑动多个窗口处理复杂条件7.2 与单调结构结合滑动窗口常与单调队列/栈结合解决更复杂问题滑动窗口最大值LeetCode 239满足条件的子数组个数LeetCode 7957.3 非典型滑动窗口一些变种问题需要灵活应用窗口思想窗口大小不固定但受其他条件约束窗口性质需要复杂数据结构维护8. 性能优化进阶技巧8.1 位运算优化对于特定问题可以用位掩码代替哈希表int window 0; // 用位表示字符出现情况 window | (1 (c - a)); // 设置位8.2 预处理加速提前计算前缀和等辅助数组vectorint prefix(n 1, 0); for (int i 0; i n; i) prefix[i1] prefix[i] nums[i];8.3 并行计算对于超大数组可以考虑分段处理多线程滑动不同区段合并部分结果9. 滑动窗口的数学本质从数学角度看滑动窗口技术实际上是在解空间中的一种剪枝策略利用单调性减少不必要的计算对暴力解法的优化重构其正确性依赖于问题的两个性质窗口的可行性窗口收缩时可以确保不遗漏解解的最优子结构局部最优能导向全局最优10. 扩展学习建议10.1 推荐练习题单按难度排序的滑动窗口练习题长度最小的子数组LeetCode 209替换后的最长重复字符LeetCode 424最多包含两个不同字符的最长子串LeetCode 159滑动窗口最大值LeetCode 239最小窗口子序列LeetCode 72710.2 相关算法领域滑动窗口与以下算法密切相关双指针技术尺取法竞赛常用贪心算法字符串匹配算法如KMP10.3 实际工程应用滑动窗口在工程中的典型应用网络流量控制TCP滑动窗口实时数据处理如计算移动平均日志分析检测异常模式股票分析计算技术指标
返回列表