双指针算法详解:从入门到精通(Java版)

目录

一、什么是双指针?

[1.1 对撞指针(左右指针)](#1.1 对撞指针(左右指针))

[1.2 快慢指针(龟兔赛跑算法)](#1.2 快慢指针(龟兔赛跑算法))

二、经典题目实战

[2.1 移动零(LeetCode 283)](#2.1 移动零(LeetCode 283))

[2.2 复写零(LeetCode 1089)](#2.2 复写零(LeetCode 1089))

[2.3 快乐数(LeetCode 202)](#2.3 快乐数(LeetCode 202))

[2.4 盛水最多的容器(LeetCode 11)](#2.4 盛水最多的容器(LeetCode 11))

[2.5 有效三角形的个数(LeetCode 611)](#2.5 有效三角形的个数(LeetCode 611))

[2.6 和为s的两个数字(剑指Offer 57)](#2.6 和为s的两个数字(剑指Offer 57))

[2.7 三数之和(LeetCode 15)](#2.7 三数之和(LeetCode 15))

[2.8 四数之和(LeetCode 18)](#2.8 四数之和(LeetCode 18))

三、双指针算法总结


一、什么是双指针?

双指针是一种常用的算法技巧,通过使用两个指针在数组或链表等数据结构上进行移动,来解决特定问题。根据指针移动方式的不同,双指针主要分为两种形式:

1.1 对撞指针(左右指针)

对撞指针从两端向中间移动。一个指针从最左端开始,另一个从最右端开始,然后逐渐往中间逼近。

终止条件

  • left == right:两个指针指向同一个位置

  • left > right:两个指针错开

1.2 快慢指针(龟兔赛跑算法)

快慢指针使用两个移动速度不同的指针在序列结构上移动。通常在一次循环中,慢指针每次移动一位,快指针每次移动两位。

适用场景

  • 处理环形链表或数组

  • 研究出现循环往复的问题


二、经典题目实战

2.1 移动零(LeetCode 283)

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

算法思路

使用两个指针 curdestcur用于遍历数组,dest指向非零元素序列的最后一个位置。

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {
        for (int cur = 0, dest = -1; cur < nums.length; cur++) {
            if (nums[cur] != 0) {
                // 处理非零元素
                dest++;
                int temp = nums[dest];
                nums[dest] = nums[cur];
                nums[cur] = temp;
            }
        }
    }
}

核心思想 :遍历过程中,使 [0, dest]区间全为非零元素,[dest+1, cur-1]区间全为零。


2.2 复写零(LeetCode 1089)

题目描述:将数组中每个零都复写一遍,其余元素向右平移,不能超过数组长度。

算法思路

采用"从后向前"的复写策略,避免从前向后复写时数据被覆盖。

java 复制代码
class Solution {
    public void duplicateZeros(int[] arr) {
        int cur = 0, dest = -1, n = arr.length;
        
        // 1. 先找到最后一个需要复写的数
        while (cur < n) {
            if (arr[cur] == 0) {
                dest += 2;
            } else {
                dest += 1;
            }
            if (dest >= n - 1) break;
            cur++;
        }
        
        // 2. 处理边界情况
        if (dest == n) {
            arr[n - 1] = 0;
            cur--;
            dest -= 2;
        }
        
        // 3. 从后向前完成复写操作
        while (cur >= 0) {
            if (arr[cur] != 0) {
                arr[dest--] = arr[cur--];
            } else {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
}

2.3 快乐数(LeetCode 202)

题目描述:判断一个数是否为快乐数。快乐数定义为:每次将该数替换为它每个位置上的数字的平方和,重复这个过程直到变为1,或进入无限循环。

算法思路

使用快慢指针检测循环。如果相遇位置的值是1,则是快乐数;否则不是。

java 复制代码
class Solution {
    public boolean isHappy(int n) {
        int slow = n, fast = bitSum(n);
        while (slow != fast) {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
        return slow == 1;
    }
    
    private int bitSum(int n) {
        int sum = 0;
        while (n != 0) {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }
}

2.4 盛水最多的容器(LeetCode 11)

题目描述:找出两条垂线,使它们与x轴构成的容器能容纳最多的水。

算法思路

使用对撞指针,每次移动较短的边界,因为移动较长边界只会使容积变小。

java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int left = 0, right = height.length - 1, ret = 0;
        while (left < right) {
            int v = Math.min(height[left], height[right]) * (right - left);
            ret = Math.max(ret, v);
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return ret;
    }
}

2.5 有效三角形的个数(LeetCode 611)

题目描述:返回数组中可以组成三角形三条边的三元组个数。

算法思路

先排序,固定最长边,使用对撞指针在剩余区间中寻找满足两边之和大于第三边的组合。

java 复制代码
class Solution {
    public int triangleNumber(int[] nums) {
        // 1. 排序
        Arrays.sort(nums);
        
        // 2. 利用双指针解决问题
        int ret = 0, n = nums.length;
        for (int i = n - 1; i >= 2; i--) {  // 固定最大的数
            int left = 0, right = i - 1;
            while (left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    ret += right - left;
                    right--;
                } else {
                    left++;
                }
            }
        }
        return ret;
    }
}

2.6 和为s的两个数字(剑指Offer 57)

题目描述:在递增排序的数组中查找两个数,使它们的和等于目标值。

算法思路

利用数组有序的特性,使用对撞指针,根据当前和与目标值的大小关系移动指针。

java 复制代码
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum > target) {
                right--;
            } else if (sum < target) {
                left++;
            } else {
                return new int[]{nums[left], nums[right]};
            }
        }
        return new int[]{-1, -1};
    }
}

2.7 三数之和(LeetCode 15)

题目描述:找出数组中所有和为0且不重复的三元组。

算法思路

先排序,固定一个数,在其后的区间中使用双指针找两数之和等于目标值,注意去重。

java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        
        // 1. 排序
        Arrays.sort(nums);
        
        // 2. 利用双指针解决问题
        int n = nums.length;
        for (int i = 0; i < n; ) {  // 固定数 a
            if (nums[i] > 0) break;  // 小优化
            
            int left = i + 1, right = n - 1, target = -nums[i];
            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    ret.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--;
                }
            }
            
            // 去重
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
}

2.8 四数之和(LeetCode 18)

题目描述:找出数组中所有和为target且不重复的四元组。

算法思路

在三数之和的基础上再套一层循环,固定两个数,使用双指针找剩余两数。

java 复制代码
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        
        // 1. 排序
        Arrays.sort(nums);
        
        // 2. 利用双指针解决问题
        int n = nums.length;
        for (int i = 0; i < n; ) {  // 固定数 a
            for (int j = i + 1; j < n; ) {  // 固定数 b
                int left = j + 1, right = n - 1;
                long aim = (long)target - nums[i] - nums[j];
                
                while (left < right) {
                    int sum = nums[left] + nums[right];
                    if (sum > aim) {
                        right--;
                    } else if (sum < aim) {
                        left++;
                    } else {
                        ret.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        left++;
                        right--;
                        
                        // 去重
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }
                }
                
                // 去重
                j++;
                while (j < n && nums[j] == nums[j - 1]) j++;
            }
            
            // 去重
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
}

三、双指针算法总结

题目 指针类型 关键技巧
移动零 快慢指针 数组分块,维护非零区间
复写零 前后指针 从后向前避免覆盖
快乐数 快慢指针 检测循环
盛水容器 对撞指针 移动较短边界
三角形个数 对撞指针 排序后固定最长边
两数之和 对撞指针 利用有序性
三数之和 对撞指针 固定一个数+去重
四数之和 对撞指针 固定两个数+去重

双指针的核心思想:通过分析问题的二段性,利用指针移动来减少不必要的枚举,将时间复杂度从 O(n²) 优化到 O(n)。

使用技巧

  1. 对撞指针常用于有序数组或字符串

  2. 快慢指针常用于环形结构或需要检测循环的场景

  3. 注意边界条件和指针移动的时机

  4. 涉及去重问题时,注意跳过重复元素

相关推荐
不穿鞋的懒羊羊5 小时前
dfs深度优先搜索
算法
OYYHXPJR5 小时前
【算法】区间重叠模板和矩形重叠模板
算法
shehuiyuelaiyuehao5 小时前
算法46,分治快排,排序数组
java·算法·排序算法·排序
圣保罗的大教堂5 小时前
leetcode 3483. 不同三位偶数的数目 简单
leetcode
远航计算机6 小时前
GEO 选题从哪来?用一份 Query 词库把一年内容排出来
大数据·人工智能·算法·aigc
宣宣猪的小花园.6 小时前
【机器学习】神经网络与表征:为什么深度学习能自动提取特征
人工智能·嵌入式硬件·算法·机器学习
mmmmath_36 小时前
LeetCode.018.四数之和
数据结构·算法·leetcode
AIGCmagic社区6 小时前
腾讯混元 AuK 技术报告拆读:1.5B 统一语音生成与编辑,蒸馏后 4 步无 CFG 提速 4.5 倍
人工智能·算法·aigc·ai多模态
大熊背6 小时前
基于照明色度表征的颜色恒常性的白平衡算法实现
算法·白平衡·色温·色度坐标·普朗克曲线
高亦真6 小时前
今天是学习嵌入式的第38天
linux·学习·算法