从两端对撞到窗口收缩,系统打通双指针与滑动窗口全族技巧,让子串/子数组类面试题再无死角
双指针和滑动窗口看似各自独立,其实共享同一根:两个指针在线性结构上协同移动。这条路径分三阶段递进:先用对撞双指针建立「两端夹逼」的直觉,再转向同向快慢指针引出窗口雏形,最后系统拆解定长与变长滑动窗口两种模板。走完之后,遇到子串/子数组最值类题,能立刻判断用哪套模板,而不是靠感觉硬猜。
适合:想掌握定长/变长窗口与双指针的人
对撞双指针的统一模式:左右两端各放一个指针,每轮根据当前两端的关系决定移动哪一侧,直到两指针相遇。关键洞察是「移动较小/较弱一侧不会错过最优解」,这个贪心单调性是正确性的根基。三数之和在此基础上加一层枚举,把 O(n³) 降到 O(n²)。
开篇建立对撞双指针:left 从头、right 从尾,跳过非字母数字后比较字符,不等即否。这是最纯粹的两端同时收缩模板,后续所有对撞题的原型。
上一题对撞只做相等判断,这题用对撞逼近目标和:两端之和偏大收右端,偏小收左端,有序性保证每次移动方向正确,即「单调性夹逼」标准范式。
上一题移动哪侧由「和与目标大小」决定,这题由「哪边更矮」决定:矮边限制面积,移动高边只会更小,故每轮必须移动矮边。对撞贪心换了形态。
固定最左数 nums[i],剩余两数用「盛水」中的对撞双指针找目标和,并在移动时跳重复值去重。这是对撞双指针从两变量扩展到三变量的典型做法。
含负数时平方最大值在两端,左右各放指针从外向内填充结果数组——对撞方向由外向内,但「两端比较决定移动哪侧」本质与前几题完全相同。
同向双指针(快慢指针)将「对撞」改为「同向追赶」:slow 指向待填写位,fast 向前探路,满足条件时才把 fast 的值写入 slow 位置。窗口雏形在此出现:fast-slow 之间的区间就是被「跳过」的元素。定长滑动窗口是固定 fast-slow=k 的特例:右进一个、左出一个,窗口整体平移。
对撞换成同向:slow 指向待写入位,fast 向右扫描,遇非零就写入 slow 并推进 slow。fast-slow 之间聚集被跳过的零,窗口雏形初现。
上一题 slow 跳过「等于零」的元素,这题跳过「与 slow 前一位相同」的重复:判断条件变了,快慢指针「fast 探路、slow 写入」框架完全相同。
快慢间距固定为 k,滑动时加右端元素、减左端元素,O(1) 更新窗口和。这是定长滑动窗口「进一出一」模板的最简形态,由动态间距收口为固定间距。
定长窗口长度固定为 p.length,但比较条件从「窗口和」升级为「字符频次相等」:用 need/window 两个计数表替代标量,右进左出同时维护计数表。
变长窗口的核心模板:右指针持续右扩,窗口满足条件后左指针收缩到恰好不满足,每步更新答案。「最长」类在收缩前记录答案(扩张是目标);「最短」类在收缩后记录答案(收缩是目标)。最小覆盖子串是该模板的最终压测:need 计数表 + formed 变量精确跟踪覆盖状态,是变长窗口最通用的完整实现。
不再固定窗口大小:右扩直到窗口和 ≥ target,左收缩到刚好不满足,收缩前记最短长度。「右扩满足、左缩维持」的变长窗口核心节奏在此首次出现。
上一题条件是数值约束,这题条件是集合约束:右扩时 HashSet 检测重复,出现重复就收左直到消除,每步收缩后记最长长度。最长型变长窗口的标准范式。
约束条件与上一题完全相同(无重复),只把「记录长度」换成「记录窗口元素和」。直接复用无重复变长窗口模板,换一个聚合指标,强化模板复用意识。
合法条件变为「窗口长度 - 最高频字符数 ≤ k」:右扩时更新频次,不合法时窗口整体平移而非缩小。变长最长窗口在「允许替换 k 次」场景的关键变体。
need 字典记录目标频次、formed 跟踪已覆盖字符种数:右扩达标后左收缩记最短。这是覆盖类变长窗口的通用完整实现,前面所有技巧在此全部收口。