双指针与滑动窗口算法精讲:从原理到高频面试题实战

引言:算法选择的十字路口

在算法面试中,双指针和滑动窗口如同两把瑞士军刀,能高效解决80%以上的数组和字符串问题。本文将深入解析这两种技术的核心差异,结合力扣高频题目,提供可直接复用的代码。

一、算法核心思想解析

1. 双指针技术

双指针算法是通过维护‌两个指针变量‌(通常为数组/链表索引)协同遍历数据结构的高效策略,主要用于优化暴力解法的时空复杂度。

三种经典模式‌:

  • 对撞指针‌:首尾指针向中间收敛(如三数之和)
  • 快慢指针‌:同向移动但速度不同(如环形链表检测)
  • 滑动窗口‌:动态维护子区间(特殊双指针实现)

2. 滑动窗口技术

作为双指针的特殊形式,滑动窗口具有以下特点:

  • 两个指针‌同向移动‌且‌维护一个连续区间
  • 通过窗口扩张/收缩动态调整解空间

3. 算法特性对比

|---------------|-------------|-----------|
| 特性 | 双指针算法 | 滑动窗口算法 |
| ‌指针移动方向 ‌ | 可对撞/同向 | 严格同向移动 |
| ‌数据要求 ‌ | 常需排序 | 原始数据即可 |
| ‌时间复杂度 ‌ | O(n)~O(n²) | O(n) |
| ‌典型问题 ‌ | 有序数组组合问题 | 子串/子数组最优解 |
| ‌状态维护 ‌ | 通常不需要 | 需要哈希表记录状态 |

二、高频面试题分类精讲

1. 双指针经典题型**:**三数之和(LeetCode 15

解题思路**:**

1. 暴力解法(不推荐)

最直观的方法是三重循环遍历所有可能的三元组,时间复杂度为O(n³),效率太低。

2. 排序 + 双指针法(推荐)

**‌****步骤如下:**‌

  1. 排序数组‌:首先将数组排序,这样可以利用有序性来优化搜索
  2. 固定一个数‌:遍历数组,将当前元素作为第一个数
  3. 双指针搜索‌:在当前元素后面的部分使用双指针(左指针从当前元素后一位开始,右指针从数组末尾开始)寻找另外两个数
  4. 去重处理‌:跳过重复的元素以避免重复解

时间复杂度‌:O(n²)(排序O(nlogn) + 双指针遍历O(n²))

java 复制代码
public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        int length = nums.length;
        if (nums == null || length < 3) {
            return result;
        }

        Arrays.sort(nums);  // 关键步骤:排序
        for (int i =0; i < length; i++) {
            // 需要和上一次枚举的数不相同
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i+1;
            int right = length - 1;
            int target = -nums[i]; // nums[left] + nums[right] = target
            while(left < right) {
                int sum = nums[left] + nums[right];
                if (sum == target) {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 跳过重复的left和right
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    while (left < right && nums[right] == nums[right - 1]) right--;
                    left++;
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        return result;
    }

2. 滑动窗口经典题型**:**最小覆盖子串(LeetCode 76

算法核心思想

滑动窗口(Sliding Window)算法‌是解决最小覆盖子串问题的最优方法:

  1. 双指针定义窗口‌:使用left和right两个指针表示当前考察的子串窗口
  2. 哈希表记录需求‌:使用两个哈希表分别存储:
  • need:目标字符串T中各字符的需求量
  • window:当前窗口中满足需求的字符数量

3. 动态扩展收缩窗口 ‌:

  • 先扩展right指针寻找可行解
  • 再收缩left指针优化解

​​​​​​​ 4. 有效字符计数 ‌:通过valid变量统计窗口中已满足需求的字符种类数

5. 实时更新结果 ‌:每当找到更小的满足条件的子串时更新结果

复杂度分析

  • 时间复杂度‌:O(n),其中n是字符串S的长度
  • 空间复杂度‌:O(m),其中m是字符集大小(通常为常数级)

Java实现代码

java 复制代码
public String minWindow(String s, String t) {
        // 边界条件检查
        if (s == null || t == null || s.length() < t.length()) {
            return "";
        }

        // 初始化需求哈希表,记录t中每个字符出现的次数
        Map<Character, Integer> need = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        // 初始化窗口哈希表,记录当前窗口中满足需求的字符数量
        Map<Character, Integer> window = new HashMap<>();
        // 记录窗口中满足need条件的字符个数
        int valid = 0;
        // 记录最小覆盖子串的起始位置和长度
        int start = 0, minLen = Integer.MAX_VALUE;
        // 滑动窗口左右指针
        int left = 0, right = 0;
        while (right < s.length()) {
            // 获取右指针当前字符
            char c = s.charAt(right);
            // 右指针向右移动
            right++;

            // 如果当前字符在需求表中,则更新窗口计数
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                // 如果窗口中该字符数量等于需求数量,则valid加1
                if (window.get(c).equals(need.get(c))) {
                    valid++;
                }
            }

            // 当窗口满足所有字符需求时,尝试收缩左边界
            while (valid == need.size()) {
                // 更新最小覆盖子串信息
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }

                // 获取左指针当前字符
                char d = s.charAt(left);
                // 左指针向右移动
                left++;

                // 如果移出的字符在需求表中,则更新窗口计数
                if (need.containsKey(d)) {
                    // 如果窗口中该字符数量刚好等于需求数量,则valid减1
                    if (window.get(d).equals(need.get(d))) {
                        valid--;
                    }
                    window.put(d, window.get(d) - 1);
                }
            }
        }

        // 返回最小覆盖子串,如果没有找到则返回空字符串
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }

三、应用场景选择指南

1. 优先选择双指针的情况

  • 有序数组的组合问题‌:如两数之和、三数之和
  • 链表操作‌:如环形链表检测、链表中点定位
  • 对称性问题‌:如回文串验证、反转字符串‌

2. 优先选择滑动窗口的情况

  • 连续子序列问题‌:如最小覆盖子串、最长无重复子串
  • 区间统计问题‌:如和大于等于目标值的最短子数组
  • 固定窗口大小问题‌:如大小为k的子数组的最大平均值‌

**、力扣经典案例**

4.1 、基础双指针问题

4 . 1 . 1. 对撞指针经典题

4.2、滑动窗口常规问题

4.2.1. 可变窗口基础题
4.2.2. 哈希表辅助窗口题

4.3、字符串匹配问题

4.3.1. 困难级窗口问题
4.3.2. 固定窗口典型题

4.4、链表双指针问题

4.4.1. 快慢指针经典题

4**.4.**2. 前后指针应用

4.5、多维滑动窗口问题

4.5.1. 单调队列配合题
4.5.2. 双堆进阶问题

结语:算法精进的阶梯

掌握双指针和滑动窗口不仅是面试的敲门砖,更是提升算法思维的关键。建议读者按照解题模板→理解原理→独立实现→优化变体的路径系统学习,逐步构建自己的算法兵器库。

附高频核心算法​​​​​​​:

1、动态规划(DP):从核心场景识别到优化技巧

相关推荐
听情歌落俗3 小时前
MATLAB3-1变量-台大郭彦甫
开发语言·笔记·算法·matlab·矩阵
量子炒饭大师3 小时前
收集飞花令碎片——C语言关键字typedef
c语言·c++·算法
澡点睡觉3 小时前
【数据结构与算法Trip第4站】摩尔投票法
算法
行走的bug...5 小时前
用图论来解决问题
算法·图论
岁忧6 小时前
(LeetCode 每日一题) 3541. 找到频率最高的元音和辅音 (哈希表)
java·c++·算法·leetcode·go·散列表
pusue_the_sun6 小时前
每日算法题推送
算法·双指针
KyollBM7 小时前
【Luogu】P9809 [SHOI2006] 作业 Homework (根号算法)
算法
jmxwzy7 小时前
leetcode274.H指数
算法
纪元A梦7 小时前
贪心算法应用:信用评分分箱问题详解
java·算法·贪心算法