【力扣热题100学习笔记】 - 双指针

【力扣热题100学习笔记】 - 双指针

📚 适合人群 :编程基础较弱,想要系统学习双指针算法的同学

🎯 学习目标:掌握双指针的核心思想,能够独立解决类似问题


一、什么是双指针?

双指针 是一种算法技巧,使用两个变量(指针)在数组或链表中移动,通过协作来解决问题。

核心思想

  • 用两个指针指向不同的位置
  • 根据题目要求,两个指针以某种方式移动
  • 通常能将时间复杂度从 O(n²) 优化到 O(n)

二、双指针的两种经典模式

模式 1:快慢指针(同向移动)

特点

  • 两个指针都从头开始
  • 快指针走得快,慢指针走得慢
  • 用于数组内元素重排问题

典型题目:移动零


模式 2:左右指针(相向移动)

特点

  • 一个指针在开头,一个在末尾
  • 两个指针相向而行
  • 用于查找满足条件的两个元素

典型题目:盛最多水的容器、三数之和


三、题目详解


📝 题目 1:移动零(快慢指针)

题目描述

给定一个数组 nums,将所有 0 移动到数组末尾,保持非零元素的相对顺序。

示例

复制代码
输入:[0,1,0,3,12]
输出:[1,3,12,0,0]

解题思路

步骤分解

  1. 定义两个指针

    • slow:慢指针,记录下一个非 0 元素应该放的位置
    • fast:快指针,遍历整个数组
  2. 快指针的作用

    • 从头到尾遍历数组
    • 检查每个元素是不是 0
  3. 慢指针的作用

    • 只有遇到非 0 元素时才移动
    • 记录非 0 元素应该放的位置
  4. 移动规则

    • fast 每次都向前走
    • 如果 nums[fast] != 0,把它放到 slow 位置,然后 slow++
    • 如果 nums[fast] == 0,跳过

代码实现
java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {
        int slow = 0;  // 慢指针:下一个非 0 元素的位置
        
        // 快指针遍历数组
        for (int fast = 0; fast < nums.length; fast++) {
            if (nums[fast] != 0) {
                // 把非 0 元素放到 slow 位置
                nums[slow] = nums[fast];
                slow++;  // slow 向前移动
            }
        }
        
        // 把后面的位置都填 0
        for (int i = slow; i < nums.length; i++) {
            nums[i] = 0;
        }
    }
}

复杂度分析
  • 时间复杂度:O(n) - 遍历两遍数组
  • 空间复杂度:O(1) - 只用了常数个变量

关键点总结

为什么叫快慢指针?

  • fast 每次都走 → 走得快
  • slow 只有遇到非 0 才走 → 走得慢

为什么要用两个指针?

  • 一个负责遍历(找非 0 元素)
  • 一个负责记录位置(放非 0 元素)

为什么能保持相对顺序?

  • 因为是从左到右遍历,非 0 元素按原来的顺序依次往前放

📝 题目 2:盛最多水的容器(左右指针)

题目描述

给定 n 条垂线,找出两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

示例

复制代码
输入:[1,8,6,2,5,4,8,3,7]
输出:49

解题思路

面积公式

复制代码
面积 = 宽度 × 高度
宽度 = right - left
高度 = min(height[left], height[right])  ← 短板效应

步骤分解

  1. 定义两个指针

    • left:最左边(下标 0)
    • right:最右边(下标 n-1)
  2. 为什么要从两端开始?

    • 这时候宽度最大
    • 有机会找到更大的面积
  3. 移动规则(贪心思想):

    • 如果 height[left] < height[right] → 移动 left
    • 如果 height[left] > height[right] → 移动 right
    • 谁短移动谁
  4. 为什么移动短的?

    • 面积由短板决定
    • 移动长的,高度最多还是这么高(甚至更矮),宽度还变小了
    • 移动短的,可能遇到更高的板子,面积可能变大

代码实现
java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int maxArea = 0;
        
        while (left < right) {
            // 计算当前面积
            int currentArea = Math.min(height[left], height[right]) * (right - left);
            
            // 更新最大面积
            maxArea = Math.max(maxArea, currentArea);
            
            // 移动短板(谁短移谁)
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        
        return maxArea;
    }
}

图解过程

[1,8,6,2,5,4,8,3,7] 为例:

复制代码
初始:left=0, right=8
    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
____|____________________|____
 0                    8
  
高度 = min(1, 7) = 1
宽度 = 8 - 0 = 8
面积 = 1 × 8 = 8

left 短,移动 left → left=1

    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
    |                    |
____|____________________|____
   1                    8
  
高度 = min(8, 7) = 7
宽度 = 8 - 1 = 7
面积 = 7 × 7 = 49 ✓ 最大面积!

继续移动...最终找到最大面积 49

复杂度分析
  • 时间复杂度:O(n) - 只需要遍历一遍
  • 空间复杂度:O(1) - 只用了两个指针

关键点总结

为什么从两端开始?

  • 宽度最大,有机会找到更大的面积

为什么移动短板?

  • 移动长板:面积肯定变小(宽度变小,高度不变或变小)
  • 移动短板:面积可能变大(可能遇到更高的板子)

这是贪心算法吗?

  • 是的!每一步都做出当前看起来最优的选择(移动短板)

📝 题目 3:三数之和(左右指针 + 排序)

题目描述

找出数组中所有和为 0 的三元组,答案中不可以包含重复的三元组。

示例

复制代码
输入:[-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

解题思路

问题转化

  • 固定一个数 nums[i]
  • 问题变成:在剩下的数里找两个数,让它们的和等于 -nums[i]
  • 这就变成了两数之和问题!

步骤分解

  1. 排序(关键!):

    • 排序后相同的数会挨在一起,方便去重
    • 可以用左右指针
  2. 固定一个数

    • 遍历数组,固定 nums[i] 作为第一个数
  3. 双指针找另外两个数

    • left = i + 1(从 i 的下一个开始)
    • right = n - 1(从最后一个开始)
    • 计算 sum = nums[left] + nums[right]
    • 如果 sum == -nums[i],找到一组!
    • 如果 sum < -nums[i]left++(和太小)
    • 如果 sum > -nums[i]right--(和太大)
  4. 去重(关键!):

    • 如果 nums[i] == nums[i-1],跳过(避免重复的三元组)
    • 找到一组后,跳过重复的 leftright

代码实现
java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        
        // 排序(关键!)
        Arrays.sort(nums);
        
        // 遍历每个数,固定它作为第一个数
        for (int i = 0; i < nums.length; i++) {
            // 剪枝:如果当前数大于 0,后面的数都比它大,三数之和不可能为 0
            if (nums[i] > 0) break;
            
            // 去重:如果和前一个数相同,跳过
            if (i > 0 && nums[i] == nums[i-1]) continue;
            
            // 双指针找另外两个数
            int left = i + 1;
            int right = nums.length - 1;
            
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                
                if (sum == 0) {
                    // 找到一组
                    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 < 0) {
                    // 和太小,左指针右移
                    left++;
                } else {
                    // 和太大,右指针左移
                    right--;
                }
            }
        }
        
        return result;
    }
}

图解过程

https://leetcode.cn/problems/3sum/solutions/12307/hua-jie-suan-fa-15-san-shu-zhi-he-by-guanpengchn

[-1,0,1,2,-1,-4] 为例:

复制代码
1. 排序后:[-4, -1, -1, 0, 1, 2]

2. i=0,固定 -4:
   left=1(-1), right=5(2)
   sum = -4 + (-1) + 2 = -3 < 0 → left++
   ... 找不到和为 0 的组合

3. i=1,固定 -1:
   left=2(-1), right=5(2)
   sum = -1 + (-1) + 2 = 0 ✓
   找到 [-1, -1, 2]
   
   继续找:
   left=3(0), right=4(1)
   sum = -1 + 0 + 1 = 0 ✓
   找到 [-1, 0, 1]

4. i=2,nums[2]=-1,但 nums[2]==nums[1],跳过!(去重)

5. i=3,固定 0:
   left=4(1), right=5(2)
   sum = 0 + 1 + 2 = 3 > 0 → right--
   ... 找不到

最终结果:[[-1,-1,2], [-1,0,1]] ✓

复杂度分析
  • 时间复杂度:O(n²) - 排序 O(n log n) + 双指针 O(n²)
  • 空间复杂度:O(1) - 除了结果,只用了常数个变量

关键点总结

为什么要排序?

  • 方便去重(相同的数挨在一起)
  • 可以用左右指针(有序数组才能判断移动方向)

为什么要去重?

  • 题目要求答案中不可以包含重复的三元组
  • 比如 [-1, 0, 1][0, 1, -1] 算同一个

怎么去重?

  • 固定数去重:如果 nums[i] == nums[i-1],跳过
  • 双指针去重:找到一组后,跳过重复的 leftright

四、三种模式对比

模式 指针位置 移动方向 典型题目
快慢指针 都在开头 同向移动 移动零
左右指针 两端 相向移动 盛最多水的容器
固定 + 双指针 一个固定,两个在剩余部分两端 相向移动 三数之和

五、解题模板

快慢指针模板

java 复制代码
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
    if (满足条件) {
        // 处理 fast 指向的元素
        nums[slow] = nums[fast];
        slow++;
    }
}
// 后续处理(如填 0)

左右指针模板

java 复制代码
int left = 0;
int right = nums.length - 1;
while (left < right) {
    int result = 计算结果;
    if (result == 目标值) {
        // 找到答案
    } else if (result < 目标值) {
        left++;  // 太小,左指针右移
    } else {
        right--;  // 太大,右指针左移
    }
}

六、复习建议

第一遍:理解思路

  1. 看懂每道题的图解过程
  2. 理解为什么要这样移动指针
  3. 不要死记硬背代码

第二遍:独立 coding

  1. 遮住答案,自己写代码
  2. 写不出来就看提示,不要直接看答案
  3. 写完对比标准答案,找出差距

第三遍:总结规律

  1. 三道题有什么共同点?
  2. 双指针的核心思想是什么?
  3. 什么情况下用快慢指针?什么情况下用左右指针?

后续练习

推荐题目:

  • 两数之和 II(输入有序数组)
  • 接雨水
  • 最长回文子串
  • 删除排序数组中的重复项

) {

int result = 计算结果;

if (result == 目标值) {

// 找到答案

} else if (result < 目标值) {

left++; // 太小,左指针右移

} else {

right--; // 太大,右指针左移

}

}

复制代码
---

## 六、复习建议

### 第一遍:理解思路

1. 看懂每道题的图解过程
2. 理解为什么要这样移动指针
3. 不要死记硬背代码

### 第二遍:独立 coding

1. 遮住答案,自己写代码
2. 写不出来就看提示,不要直接看答案
3. 写完对比标准答案,找出差距

### 第三遍:总结规律

1. 三道题有什么共同点?
2. 双指针的核心思想是什么?
3. 什么情况下用快慢指针?什么情况下用左右指针?

### 后续练习

推荐题目:
- 两数之和 II(输入有序数组)
- 接雨水
- 最长回文子串
- 删除排序数组中的重复项

---

## 
相关推荐
wangchunting2 小时前
算法-二分查找
java·数据结构·算法
weixin_456321642 小时前
生产环境下微服务网关选型与实战指南(基于SpringCloud生态)
java·spring cloud
jwn9992 小时前
PHP与C++:Web脚本与系统编程的终极对决
java·开发语言
Kk.08022 小时前
数据结构|排序算法(三)堆排序
java·数据结构·排序算法
hnlgzb2 小时前
Companion Object - 伴生对象 类比java中的什么?
java·开发语言
haiyangyiba2 小时前
学习Spring Ai的摸索实践
学习·spring ai
chase。2 小时前
【学习笔记】cuRoboV2——为高自由度机器人打造的动力学感知运动生成框架
笔记·学习·机器人
南境十里·墨染春水2 小时前
C++ 笔记 多重继承 菱形继承(面向对象)
开发语言·c++·笔记
小红的布丁2 小时前
Redis 内存淘汰与过期策略
java·spring·mybatis