优选算法---双指针

注:自2023下半年开始,力扣里"剑指 Offer"前缀统一改成了"LCR"

目录

[283. 移动零 - 力扣(LeetCode)](#283. 移动零 - 力扣(LeetCode))

[1089. 复写零 - 力扣(LeetCode)](#1089. 复写零 - 力扣(LeetCode))

[202. 快乐数 - 力扣(LeetCode)](#202. 快乐数 - 力扣(LeetCode))

[11. 盛最多水的容器 - 力扣(LeetCode)](#11. 盛最多水的容器 - 力扣(LeetCode))

[611. 有效三角形的个数 - 力扣(LeetCode)](#611. 有效三角形的个数 - 力扣(LeetCode))

[1. 两数之和 - 力扣(LeetCode)](#1. 两数之和 - 力扣(LeetCode))

[LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)](#LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode))

[LCR 007. 三数之和 - 力扣(LeetCode)](#LCR 007. 三数之和 - 力扣(LeetCode))

[18. 四数之和 - 力扣(LeetCode)](#18. 四数之和 - 力扣(LeetCode))


283. 移动零 - 力扣(LeetCode)


🍉算法逻辑:

①定义两个变量des、cur,起始位置分别为-1、0

②规定cur在遇到非零元素时,交换"des+1"位置的元素,des++

cur在遇到零元素时不做处理

③这样一来就达到了以下图示的样子:


🌰代码演示:

java 复制代码
class Solution1 {
    public void moveZeroes(int[] nums) {
        int destination = -1;
        for (int cur = 0; cur < nums.length; cur++) {
            if(nums[cur] != 0){
                swap(nums,destination+1,cur);
                destination++;
            }
        }
    }
    public void swap(int[]array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

1089. 复写零 - 力扣(LeetCode)


🍉算法逻辑:

① 初始阶段演示:

如果 cur 指向非零元素curdes 各向前走一步

如果 cur 指向零元素cur 向前走一步,des 向前走两步(因为要复写一个零)

⬇️

推演一遍之后,可以看到复写完成后的数组应该是这个样子:

des 大多数情况下会超出数组范围,只有少数特例恰好停在末尾(比如现在这个😆)。

因此用 des >= n - 1 作为终止条件,可以同时覆盖"到达末尾"和"超出范围"两种情况。


②解决完寻找复写位置的问题,就需要解决一个特殊情景:

即des刚好处于数组末尾后一个位置(n),说明数组末尾位置(n-1)需要被复写为 0

直接手动设置,并将指针前移两位,避免后续循环中访问 arr[destination] 时(destination = n)发生数组越界

③ 上述两步处理完成后,从 destination 指向的位置开始,按既定规则从后向前复写元素,直到数组全部填满


🌰代码演示:

java 复制代码
  public void duplicateZeros(int[] arr) {
        int destination = -1;
        int cur = 0;
        int n = arr.length;
        //1、寻找最终cur跟des的位置
        for (; cur < n; cur++) {
            if (arr[cur] == 0) {
                destination += 2;
            } else {
                destination++;
            }
            if (destination >= n - 1) {
                break;
            }
        }
        //2、解决边界情况
        if(destination == n){
            arr[n - 1] = 0;
            cur--;
            destination -= 2;
        }
        
        //3、从复写位置开始后移
        while (cur >= 0){
            if(arr[cur] != 0){
                arr[destination] = arr[cur];
                destination--;
            }else {
                arr[destination] = arr[cur];
                arr[destination - 1] = arr[cur];
                destination -= 2;
            }
            cur--;
        }
    }

202. 快乐数 - 力扣(LeetCode)


🍉算法逻辑:

①根据题目红线描述,一个数最终只有两种结果:

1、变成 1(快乐数)

2、进入循环(非快乐数)

②因此循环的终止条件就是:

要么当前数为 1

要么检测到环(快慢指针相遇)

③使用快慢指针时,fast 的初始值应该比 slow 快一步(即先计算一次平方和),否则两者起点相同,循环无法进入。

④另外要注意:每次更新指针时,必须用指针自身的值 来计算下一步,而不能一直用原始的 n,否则指针永远不会移动,导致死循环。


🌰代码演示:

java 复制代码
class Solution3{
    //计算数字各位的平方和
    public int getSquare(int n){
        int sum = 0;
        while (n > 0){
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }

    public boolean isHappy(int n){
        int slow = n;
        int fast = getSquare(n);

        while (fast != 1 && slow != fast){
            slow = getSquare(slow); // 用slow更新slow
            fast = getSquare(getSquare(fast)); // 用fast更新fast
        }
        return fast == 1;
    }
}

这个是采用存放元素的形式,没有使用双指针的写法:

java 复制代码
class Solution3 {
    public boolean isHappy(int n) {

        LinkedList<Integer> x = new LinkedList<>();

        //不断拿到新的n值并判断
        while (n != 1 && !x.contains(n)){
            x.add(n);
            n = getSquare(n);
        }

        return n == 1;
    }

    //计算数字各位的平方和
    public int getSquare(int n){
        int sum = 0;
        while (n > 0){
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
}

11. 盛最多水的容器 - 力扣(LeetCode)


🍉算法逻辑:

① 这道题代码实现很简单,但背后的思想值得注意:

它利用了单调性原理,定义左右双指针从两端向中间移动。

每次比较两端高度,移动较矮的那一侧指针,因为容器的宽度一直在减小,只有放弃当前较短的边,才可能遇到更高的边来弥补宽度的损失。如此重复直到指针相遇,即可得到最大面积


🌰代码演示:

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

611. 有效三角形的个数 - 力扣(LeetCode)


🍉算法逻辑:

这题逻辑上也很简单,排序后,只要两小边之和 ≤ 最大边,就不能构成三角形(单调性原理)

② 所以先排序,然后从右往左枚举最大边。每一轮用双指针:若 nums[left] + nums[right] > max,则累加 (right - left) 并将右指针左移(尝试更小的右边);否则将左指针右移(尝试更大的左边)。最后累加所有结果即可


🌰代码演示:

java 复制代码
   public int triangleNumber(int[] nums) {
        //1、排序
        //Java内置排序
        Arrays.sort(nums);

        int maxIndex = nums.length - 1;
        int sum = 0;
        //2、每一轮最大值都在换
        while (maxIndex >= 2) {
            //保证left与right每次循环都更新
            int left = 0;
            int right = maxIndex - 1;
            while (left < right) {
                if (nums[left] + nums[right] > nums[maxIndex]) {
                    //累加
                    sum += right - left;
                    right--;
                } else {
                    left++;
                }
            }
            maxIndex--;
        }
        return sum;
    }

1. 两数之和 - 力扣(LeetCode)

LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)


🍉算法逻辑:

① 两数之和(LeetCode 1)本质是无序数组的暴力枚举。

而第二题(LCR 179 / 剑指 Offer 57)虽然也可以用暴力枚举,但由于数组有序,可以利用单调性提前结束循环,比无序数组的写法更优。

这里直接采用最优解法---双指针

② 利用单调性,总和大于target则right--,总和小于target则left++


🌰代码演示:

暴力枚举:

java 复制代码
  //暴力枚举无序数组
    public int[] twoSum2(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if(nums[i] + nums[j] == target){
                    return new int[]{i,j};
                }
            }
        }
        //照顾编译器
        return new int[]{};
    }

双指针:

java 复制代码
public int[] subarraySum(int[] nums, int target) {
        //1、排序
        Arrays.sort(nums);

        int left = 0;
        int right = nums.length - 1;

        while (left < right){
            if( nums[left] + nums[right] == target ){
                return new int[] { nums[left], nums[right] };
            }else if( nums[left] + nums[right] > target ){
                right--;
            }else {
                left++;
            }
        }
        //照顾编译器
        return new int[]{};
    }

LCR 007. 三数之和 - 力扣(LeetCode)


🍉算法逻辑:

①三数之和,又相当于双指针解决两数之和的延申。

数组排序

固定一个数 nums[i] 作为 target

在当前target之后,用双指针寻找满足条件的两数之和

②需要着重注意的两点:不漏、去重

不漏:当找到一组符合条件的组合时,左右指针继续移动 (左指针右移、右指针左移),继续寻找当前 target 下的其他解,而不是直接 break

去重:1、左边去重、右边去重、target去重 2、要考虑到去重时的边界情况


🌰代码演示:

暴力枚举+set去重(两个deBuff):

java 复制代码
//写法一:暴力枚举
    public List<List<Integer>> threeSum(int[] nums) {
        //1、排序
        Arrays.sort(nums);

        //2、利用Set去重
        Set<List<Integer>> result = new HashSet<>();

        //3、三重循环枚举所有三元组
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                for (int k = j + 1; k < nums.length; k++) {
                    if(nums[i] + nums[j] + nums[k] == 0){
                        //4、将满足条件的三元组转换成List放入Set
                        result.add(Arrays.asList(nums[i],nums[j],nums[k]));
                    }
                }
            }
        }
        return new ArrayList<>(result);
    }

双指针+手动去重:

java 复制代码
//写法二:双指针
    public List<List<Integer>> threeSum2(int[] nums) {
        //1、排序
        Arrays.sort(nums);

        List<List<Integer>> list = new LinkedList<>();

        //2、双指针
        int left;
        int right;
        int i = 0;
        int n = nums.length - 2;
        while (i < n){
            //小优化
            if(nums[i] > 0){
                break;
            }
            left = i + 1;
            right = nums.length - 1;
            while (left < right){
                if(nums[left] + nums[right] + nums[i] == 0){
                    list.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--;
                    }
                }else if(nums[left] + nums[right] + nums[i] > 0){
                    right--;
                }else {
                    left++;
                }
            }
            i++;
            //对i去重
            //为啥不写成i<left? 因为left不固定,一直在移动
            while (i < n && nums[i] == nums[i - 1]){
                i++;
            }
        }
        return list;
    }

18. 四数之和 - 力扣(LeetCode)


🍉算法逻辑:

①比三数之和多固定一个数 ,因此外层多一层循环

②每多固定一个数,就多一次去重操作

③最内层依然使用双指针解决两数之和


🌰代码演示:

java 复制代码
public List<List<Integer>> fourSum(int[] nums, int target) {
        //1、排序
        Arrays.sort(nums);

        List<List<Integer>> list = new LinkedList<>();

        //2、双指针
        int left;
        int right;
        int x = 0;
        int i = 0;
        int n = nums.length - 2;

        while (x < n - 1){
            i = x + 1;
            while (i < n){
                left = i + 1;
                right = nums.length - 1;
                while (left < right){
                    long sum = (long) nums[x] + nums[left] + nums[right] + nums[i];
                    if(sum == target){
                        list.add(Arrays.asList(nums[x],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--;
                        }
                    }else if(sum > target){
                        right--;
                    }else {
                        left++;
                    }
                }
                i++;
                //对i去重
                while (i < n && nums[i] == nums[i - 1]){
                    i++;
                }
            }
            x++;
            //对x去重
            //注意这里的边界
            while (x < n - 1 && nums[x] == nums[x - 1]){
                x++;
            }
        }
        return list;
    }

本专题完

相关推荐
闲适达人1 小时前
nginx传递url的获取方案
java·服务器·前端
折哥的程序人生 · 物流技术专研1 小时前
《Java 100 天进阶之路》第21篇:Java Object类
java·开发语言·后端·面试·哈希算法
小O的算法实验室1 小时前
2026年IEEE TSMC,基于Q学习平衡全局与局部搜索的防空资源分配问题进化算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
谙弆悕博士1 小时前
快速学C语言——第17章:多文件编程与头文件规范
c语言·开发语言·算法·学习方法·头文件·多文件编程
27669582922 小时前
阿里图像修复验证码自动化分析
java·前端·自动化·阿里滑块·drssionpage·阿里图像修复验证码·阿里图像复原
happymaker06262 小时前
Spring学习日记——DAY04(复杂对象创建,AOP静态代理)
java·开发语言·spring
小江的记录本2 小时前
【MySQL】《MySQL日志面试背诵版+思维导图》(核心考点 + MySQL 8.0最新优化)
java·数据库·后端·python·sql·mysql·面试
水蓝烟雨2 小时前
2359. 找到离给定两个节点最近的节点
算法·leetcode
我命由我123452 小时前
Android Framework P2 - 开机启动 Zygote 进程、Zygote 的预加载机制
android·java·开发语言·python·java-ee·intellij-idea·zygote