双指针在滑动窗口中的收缩博弈:无重复字符最长子串的指针进退法则
发布时间:2026/9/4 23:23:39 锦皓数字建站

双指针在滑动窗口中的收缩博弈无重复字符最长子串的指针进退法则在双指针算法体系中“滑动窗口Sliding Window”是解决连续子串、连续子数组极值问题最锋利的武器。经典题目如 LeetCode 3无重复字符的最长子串、LeetCode 76最小覆盖子串和 LeetCode 209长度最小的子数组。很多同学在写滑动窗口时最容易写成“嵌套循环 乱回退指针”导致原本 $O(N)$ 的线性算法直接退化为 $O(N^2)$甚至在left和right指针的交错位置上产生数组越界。今天我们通过状态机模型与进退博弈法则把无重复字符最长子串的滑动窗口模板彻底讲透。滑动窗口的核心本质单调指针与双向收敛滑动窗口之所以能将 $O(N^2)$ 的枚举降维到 $O(N)$关键在于两个指针left和right都单调向右移动绝不走回头路。整个算法的运行过程就像一个可伸缩的贪吃蛇主动扩张阶段right向右前进不断将右边界元素纳入窗口扩大窗口内的有效信息量被动收缩阶段left向右追赶当窗口内的状态违反了题目约束例如出现了重复字符、或者窗口和超过了限制右指针暂停移动左指针向右移动逐个剔除左侧元素直到窗口重新恢复合法状态极值更新阶段在每次合法状态下动态计算并更新全局最值如maxLen Math.max(maxLen, right - left 1)。graph LR A[right 持续右移吸纳字符] -- B{窗口内是否存在重复字符?} B --|是 违反约束| C[left 持续右移移出字符直到无重复] B --|否 恢复合法| D[更新 maxLen 并继续右移 right] C -- D标准代码实现基于哈希表与数组映射的高效滑动在处理 ASCII 字符集包括字母、数字、符号时直接使用固定大小为 128 或 256 的int[]数组作为哈希计数器性能比java.util.HashMap快 5 倍以上因为完全消除了对象装箱拆箱与哈希冲突链表遍历开销。public class LongestSubstringWithoutRepeating { public int lengthOfLongestSubstring(String s) { if (s null || s.isEmpty()) { return 0; } // 使用 128 大小的整型数组记录字符最后一次出现的下标位置 int[] lastIndex new int[128]; Arrays.fill(lastIndex, -1); // 初始化为 -1 表示未出现 int maxLen 0; int left 0; // 窗口左边界 for (int right 0; right s.length(); right) { char c s.charAt(right); // 如果当前字符在窗口内出现过直接将 left 指针跳跃到重复字符的下一个位置 if (lastIndex[c] left) { left lastIndex[c] 1; } // 更新字符最新出现的下标 lastIndex[c] right; // 维护全局最大长度 maxLen Math.max(maxLen, right - left 1); } return maxLen; } }极客进阶为什么lastIndex[c] left是绝对不可省略的判断很多初学者直接写left lastIndex[c] 1结果在字符串abba用例上直接挂掉。让我们单步推演abbaright 0,c a,lastIndex[a] 0,left 0, 窗口a,maxLen 1right 1,c b,lastIndex[b] 1,left 0, 窗口ab,maxLen 2right 2,c b, 发现b出现过left跳跃到lastIndex[b] 1 2窗口变为b,maxLen 2right 3,c a:此时哈希表记录的lastIndex[a] 0如果不加lastIndex[c] left判断left会被错误地赋值为0 1 1这会导致原本已经在窗口外部的a导致left指针发生回退从 2 倒退回 1使得窗口包含了ba从而产生错误判断lastIndex[c] left这一道防线确保了左指针只能单向向右跳跃绝对不会被历史已出窗的过期字符拉回。复杂度分析与总结时间复杂度每个字符最多被right访问一次被left跳跃排除一次严格为 $O(N)$空间复杂度由于 ASCII 字符集常数大小为 128空间复杂度严格为 $O(1)$。掌握了“进窗、破界、出窗、判界”四部曲滑动窗口问题便能做到心中有图、落笔生花。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。