双指针算法面试通关指南:从入门到精通

双指针算法面试通关指南:从入门到精通

双指针是面试中最常考的算法技巧之一,本文系统梳理快慢指针、对撞指针、滑动窗口三大类型,助你轻松应对算法面试。

前言

在算法面试中,双指针技巧出现的频率极高。它不仅能将 O(n²) 的暴力解法优化到 O(n),而且代码简洁优雅,是面试官非常青睐的考察点。

本文将从原理到实战,带你彻底掌握双指针算法。


一、什么是双指针?

双指针是指在遍历对象的过程中,使用两个指针进行访问,而不是普通的单个指针。

根据指针移动方式的不同,双指针可以分为三类:

类型 特点 典型场景
快慢指针 一快一慢,相对移动 链表判环、找中点、去重
对撞指针 左右指针,相向移动 两数之和、反转数组、盛水容器
滑动窗口 左右指针,同向移动 子串问题、最小覆盖子串

二、快慢指针

2.1 核心思想

快指针每次走多步,慢指针每次走一步,通过速度差解决问题。

2.2 经典题目:判断链表是否有环

javascript 复制代码
function hasCycle(head) {
    let slow = head;
    let fast = head;
    
    while (fast && fast.next) {
        slow = slow.next;        // 慢指针走一步
        fast = fast.next.next;   // 快指针走两步
        
        if (slow === fast) {
            return true;  // 相遇,有环
        }
    }
    return false;  // 快指针到达末尾,无环
}

原理:如果链表有环,快指针一定会追上慢指针;无环则快指针先到达末尾。

2.3 经典题目:寻找链表的中点

javascript 复制代码
function middleNode(head) {
    let slow = head;
    let fast = head;
    
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;  // 慢指针指向中点
}

2.4 经典题目:删除排序数组中的重复项

javascript 复制代码
function removeDuplicates(nums) {
    if (nums.length === 0) return 0;
    
    let slow = 0;
    for (let fast = 1; fast < nums.length; fast++) {
        if (nums[fast] !== nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }
    return slow + 1;  // 新数组长度
}

三、对撞指针

3.1 核心思想

一个指针从头部开始,一个从尾部开始,向中间移动,直到相遇。

3.2 经典题目:两数之和 II(有序数组)

javascript 复制代码
function twoSum(numbers, target) {
    let left = 0;
    let right = numbers.length - 1;
    
    while (left < right) {
        const sum = numbers[left] + numbers[right];
        
        if (sum === target) {
            return [left + 1, right + 1];
        } else if (sum < target) {
            left++;   // 和太小,左指针右移
        } else {
            right--;  // 和太大,右指针左移
        }
    }
    return [];
}

为什么有序数组可以用对撞指针?

  • 数组有序,可以通过比较和与 target 的大小,决定移动哪个指针
  • 时间复杂度 O(n),空间复杂度 O(1)

3.3 经典题目:盛最多水的容器

javascript 复制代码
function maxArea(height) {
    let left = 0;
    let right = height.length - 1;
    let maxArea = 0;
    
    while (left < right) {
        // 面积 = 宽度 * 高度(取两边较小值)
        const area = (right - left) * Math.min(height[left], height[right]);
        maxArea = Math.max(maxArea, area);
        
        // 移动高度较小的一边,才可能找到更大的面积
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    return maxArea;
}

关键思路:移动高度较小的一边,因为宽度在减小,只有高度增加才可能使面积变大。

3.4 经典题目:验证回文串

javascript 复制代码
function isPalindrome(s) {
    let left = 0;
    let right = s.length - 1;
    
    while (left < right) {
        // 跳过非字母数字字符
        while (left < right && !isAlphaNum(s[left])) left++;
        while (left < right && !isAlphaNum(s[right])) right--;
        
        if (s[left].toLowerCase() !== s[right].toLowerCase()) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

四、滑动窗口

4.1 核心思想

维护一个窗口,左右指针同向移动,右指针扩展窗口,左指针收缩窗口。

4.2 经典题目:无重复字符的最长子串

javascript 复制代码
function lengthOfLongestSubstring(s) {
    const set = new Set();
    let left = 0;
    let maxLen = 0;
    
    for (let right = 0; right < s.length; right++) {
        // 右指针扩展,遇到重复字符则收缩左边界
        while (set.has(s[right])) {
            set.delete(s[left]);
            left++;
        }
        
        set.add(s[right]);
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}

4.3 经典题目:最小覆盖子串

javascript 复制代码
function minWindow(s, t) {
    const need = new Map();  // 需要的字符及数量
    const window = new Map(); // 窗口中的字符及数量
    
    for (const c of t) {
        need.set(c, (need.get(c) || 0) + 1);
    }
    
    let left = 0, right = 0;
    let valid = 0;  // 窗口中满足 need 条件的字符数
    let start = 0, len = Infinity;
    
    while (right < s.length) {
        const c = s[right];
        right++;
        
        if (need.has(c)) {
            window.set(c, (window.get(c) || 0) + 1);
            if (window.get(c) === need.get(c)) {
                valid++;
            }
        }
        
        // 窗口满足条件,尝试收缩
        while (valid === need.size) {
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            
            const d = s[left];
            left++;
            
            if (need.has(d)) {
                if (window.get(d) === need.get(d)) {
                    valid--;
                }
                window.set(d, window.get(d) - 1);
            }
        }
    }
    
    return len === Infinity ? '' : s.substring(start, start + len);
}

滑动窗口解题框架:

  1. 初始化左右指针、窗口数据、结果变量
  2. 右指针扩展窗口,更新窗口数据
  3. 当窗口满足条件时,收缩左指针,更新结果
  4. 重复直到右指针到达末尾

五、面试高频题目汇总

题目 类型 难度 考察点
141. 环形链表 快慢指针 简单 链表判环
876. 链表的中间结点 快慢指针 简单 找中点
26. 删除有序数组中的重复项 快慢指针 简单 数组去重
167. 两数之和 II 对撞指针 中等 有序数组
11. 盛最多水的容器 对撞指针 中等 贪心思想
125. 验证回文串 对撞指针 简单 字符串处理
3. 无重复字符的最长子串 滑动窗口 中等 窗口维护
76. 最小覆盖子串 滑动窗口 困难 窗口收缩
424. 替换后的最长重复字符 滑动窗口 中等 窗口条件

六、解题技巧总结

6.1 如何选择指针类型?

复制代码
题目涉及链表?
  ├─ 判环、找中点、倒数第K个 → 快慢指针
  └─ 反转、重排 → 对撞指针

题目涉及数组/字符串?
  ├─ 有序数组、两数之和 → 对撞指针
  ├─ 子串、子数组、连续序列 → 滑动窗口
  └─ 原地修改、去重 → 快慢指针

6.2 代码模板速记

快慢指针模板:

javascript 复制代码
let slow = 0, fast = 0;
while (fast < n) {
    // 处理逻辑
    if (满足条件) {
        slow++;
    }
    fast++;
}

对撞指针模板:

javascript 复制代码
let left = 0, right = n - 1;
while (left < right) {
    if (满足条件) return result;
    else if (条件A) left++;
    else right--;
}

滑动窗口模板:

javascript 复制代码
let left = 0;
for (let right = 0; right < n; right++) {
    // 右指针扩展,加入窗口
    window.add(s[right]);
    
    // 收缩左边界
    while (需要收缩) {
        window.remove(s[left]);
        left++;
    }
    
    // 更新结果
    result = Math.max(result, right - left + 1);
}

七、总结

双指针算法是面试中的高频考点,掌握以下要点:

  1. 快慢指针:用于链表问题,通过速度差解决问题
  2. 对撞指针:用于有序数组,从两端向中间逼近
  3. 滑动窗口:用于子串问题,维护动态窗口
  4. 识别场景:根据题目特征快速判断使用哪种指针
  5. 代码模板:熟记三种指针的基本框架,面试时快速写出

双指针的精髓在于减少不必要的遍历,将暴力解法的 O(n²) 优化到 O(n)。多刷题、多总结,面试遇到双指针问题就能游刃有余。


如果本文对你有帮助,欢迎点赞、收藏、评论!有任何问题可以在评论区留言讨论。

标签算法 面试 双指针 LeetCode JavaScript 前端

相关推荐
SimpleLearingAI1 小时前
PyTorch & Numpy 实现线性回归详解
人工智能·算法·多模态大模型
papership1 小时前
【入门级-数据结构-1、线性结构:链 表(单链表、双向链表、循环链表 )】
数据结构·算法·链表
Omics Pro1 小时前
P4医学4大支柱需绑定4大数字技术才可落地
人工智能·python·算法·机器学习·plotly
csdn_aspnet1 小时前
C++ 霍尔分区算法(Hoare‘s Partition Algorithm)
数据结构·c++·算法
郝学胜-神的一滴1 小时前
力扣 144:二叉树前序遍历的优雅实现
java·数据结构·c++·python·算法·leetcode·职场和发展
MegaDataFlowers1 小时前
543.二叉树的直径
算法
Kurisu5752 小时前
深度拆解:从 CPU 乱序执行到内存屏障,无锁编程的底层防线
算法
GIOTTO情2 小时前
智能舆情处置系统技术方案:基于NLP语义算法的全链路风险处置落地
人工智能·算法·自然语言处理