算法【Java】—— 双指针算法

news2024/12/28 18:42:23

双指针算法

常见的双指针有对撞指针,快慢指针以及前后指针(这个前后指针是指两个指针都是从从一个方向出发,去往另一个方法,也可以认为是小学学习过的两车并行,我也会叫做同向指针),在前后指针这里还有一个经典的算法叫做滑动窗口,滑动窗口会在下一篇算法文章中提到

对撞指针可以叫做左右指针,就是一个指针在最左端,另一个指针在最右端,然后两个指针开始向中间逼近。

快慢指针:相信大家在链表的时候就已经学到过,就是一个指针每次走一步(这就是慢指针),另一个指针每次走两步(这是快指针)。一般快慢指针是用来处理处理环形链表或数组。

前后指针:在不涉及到滑动窗口的使用的时候,我们一般会用于数组的分区

如果大家学过排序,想必对双指针应该十分了解,正好在学习双指针算法的时候,可以回顾一下排序内容。

题目实战

下面题目讨论的时间复杂度没有进行化简,目的是让大家更好地感受算法对性能的优化。

移动零

https://leetcode.cn/problems/move-zeroes/description/

在这里插入图片描述

解法:前后指针
题目要求我们将数组前面的零移动到后面,将非零的元素按照原本的相对位置存放到数组的前面。
这时候这个数组显而易见地被分成了两个部分,一个是非零区域,一个是零区域

我们可以使用前后指针法,第一个指针从下标0开始遍历数组,第二个指针初始值设置为-1,当第一个指针遇到非零元素时,第二个指针向后移动一步,然后交换两个指针所对应的数组的元素,否则第二个指针原地不动。

在这里插入图片描述

通过前后指针,我们可以将数组分成上述的区域,这就是为什么前后指针一般用于数组的分区。

class Solution {
    public void moveZeroes(int[] nums) {
        int len = nums.length;
        int j = -1;
        for(int i = 0; i < len; i++) {
            if(nums[i] != 0) {
                j++;
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }
    }
}

如果可以使用额外的空间的话,还是使用两个指针,一个指针指向一个数组,时间复杂度为O(2N),空间复杂度为O(N),但是直接使用双指针算法,时间复杂度O(2N),空间复杂度为O(1)

快乐数

https://leetcode.cn/problems/happy-number/

在这里插入图片描述

解法:快慢指针
在快乐数的循环中,我们知道循环最后的结果要么是无限循环,要么是 1,那无限循环是为什么?

以 n = 2 为例:2 ^ 2 = 4,4 ^ 2 = 16,1 ^ 2 + 6 ^ 2 = 37,3 ^ 2 + 7 ^ 2 = 58,5 ^ 2 + 8 ^ 2 = 89,8 ^ 2 + 9 ^ 2 = 145, 1 ^ 2 + 4 ^ 2 + 5 ^ 2 = 42,4 ^ 2 + 2 ^ 2 = 20,2 ^ 2 + 0 = 2,最后你会发现这个数它又回去了,也就是意味着这个循环形成了一个环

最后循环的结果还有一种可能就是出现1,如果出现 1 的时候,我们不停止循环的话,那么这个循环将会一直得到1,这也是一个环。

相信大家到这里想到了使用快慢指针,当两个指针相遇时,如果指针对应的是1 的话,那么就是快乐数,否则就不是快乐数。

class Solution {
    public boolean isHappy(int n) {
        int slow = n;
        int fast = sum(sum(n));

        while(slow != fast) {
            slow = sum(slow);
            fast = sum(sum(fast));
        }

        if(slow == 1) {
            return true;
        } else {
            return false;
        }
    }
    private int sum(int n) {
        int sum = 0;
        while(n != 0) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
}

盛最多水的容器

https://leetcode.cn/problems/container-with-most-water/description/

在这里插入图片描述

解法:对撞指针

分析题意:数组的每一个元素是该下标的高,而容器的底长为两个下标之间的差值,题目要求我们找到容器的最大值,这个数值等于 高 x 底长

我们使用对撞指针,一个在最左端,一个在最右端,然后两个指针开始向中间遍历,首先由于两个指针是向中间靠拢,所以对应的宽度就会减少,指针移动的目的是为了获取容器的最大值,所以,我们现在要思考指针如何移动?

因为容器的高度是由最小的高度决定的,所以这时候,我们可以从两个指针获取到最小的元素,我们让对应最小的高度的指针向中间移动,看看能不能找到更大的高度,以此来进行遍历,然后通过 Math.max 这个函数就可以获取到最大值。

class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int maxV = Math.min(height[left],height[right]) * (right - left);
        while(left < right) {
            int index = (height[left] > height[right] ? right : left);
            if(index == left) {
                left++;
            } else {
                right--;
            }
            int tmp = Math.min(height[left],height[right]) * (right - left);
            maxV = Math.max(maxV,tmp);
        }
        return maxV;        
    }
}

这道题的对撞指针算法的时间复杂度为O(N) ,性能十分地好

两数之和

https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/

在这里插入图片描述

解法:排序 + 双指针

由于我们只要返回两个数(两数之和为 target 即可)。如果我们直接使用两个指针的话,就是使用暴力枚举,使用了两层循环,实践复杂度为O(N^2),但是这里是算法篇目,我们应该尽量优化我们的算法,将算法的效率提高。

如果数组本身就是有序的话,我们再这个基础上使用双指针就会好写很多,因为就三种情况,要么两数之和小于taregt ,两数之和如果等于 tareget 的话,直接返回即可,如果两个数大于 target 。

也就是我们要处理指针的移动就是大于和小于的情况,如果是大于的话,我们可以将指针左移减小两数之和,如果是小于的话,可以将指针右移扩大两数之和,那这就意味着我们要使用的是对撞指针。

class Solution {
    public int[] twoSum(int[] price, int target) {
    	Arrays.sort(price);
        int left = 0;
        int right = price.length - 1;
        while(left < right) {
            if(price[left] + price[right] == target) {
                break;
            } else if(price[left] + price[right] > target) {
                right--;
            } else {
                left++;
            }
        }
        int[] ans = {price[left],price[right]};
        return ans;
    }
}

时间复杂度分析,使用Arrays.sort ,这个底层是快速排序,时间复杂度为O(N * logN),对撞指针时间复杂度为 O(N), 整体时间复杂度为 O(N + N * logN),比暴力枚举好多了。

三数之和

https://leetcode.cn/problems/3sum/

在这里插入图片描述

解法:双指针

在做这道题目的时候,我们一开始想到的就是直接暴力求解,使用三个循环,时间复杂度为 O(N ^ 3) ,作为一个算法题,我们需要优化它的性能,我们可以使用双指针来代替两个循环,这样最多只需要遍历 2N 个元素,加上一个循环,时间复杂度就可以变为 O(N ^ 2)

我们可以借助两数之和这道题目的算法思路,通过对撞指针找到 两数之和为 taregt ,而target 可以由 0 - 第三个数字获得,使用这个思路的时候,需要先对数组排好序。

这里有一个方法 能将一串数字转化为 链表 —— Arrays.asList(…),这样就不用这么麻烦添加元素到链表上了

这里要避免重复的三元组的出现,再取完三元组后,双指针各自向中间靠拢一步,然后进行去重操作,避免和上回的元素相同。
在每次执行完双指针算法的时候,可以再对 最外层循环的变量先 ++ ,再去重,因为这个元素的三元组已经找过了。

为什么去重之后,三元组就不会重复?
因为数组是有序的,双指针是向中间靠拢的,左指针所对应的数值一定的大于上回的数值,右指针对应的数值一定的小于上回的数值,并且左指针一定小于右指针,所以右指针不可能去到左指针上回的数值,这就保证有一个数字是一定不重复的,也就保证三元组的不重复性

经过双指针的优化,时间复杂度为O(N^2 + N * logN)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> list = new ArrayList<>();
        int len = nums.length;
        for(int i = 0; i < len - 2;) {
            int target = 0 - nums[i];
            for(int left = i + 1, right = len - 1; left < right;) {
                if(nums[left] + nums[right] == target) {
                    List<Integer> l = new ArrayList<>(Arrays.asList(nums[i],nums[left],nums[right]));
                    list.add(l);
                    left++;
                    right--;
                    while(left < right && nums[right+1] == nums[right]) {
                        right--;
                    }
                    while(left < right && nums[left-1] == nums[left]) {
                        left++;
                    }
                } else if (nums[left] + nums[right] > target) {
                    right--;
                } else {
                    left++;
                }
            }

            //去重
            i++;
            while(i < len && nums[i-1] == nums[i]) {
                i++;
            }
        }

        return list;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2046119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python3网络爬虫开发实战(10)模拟登录(需补充账号池的构建)

文章目录 一、基于 Cookie 的模拟登录二、基于 JWT 模拟登入三、账号池四、基于 Cookie 模拟登录爬取实战五、基于JWT 的模拟登录爬取实战六、构建账号池 很多情况下&#xff0c;网站的一些数据需要登录才能查看&#xff0c;如果需要爬取这部分的数据&#xff0c;就需要实现模拟…

KNN图像识别实例--手写数字识别

目录 前言 一、导入库 二、导入图像并处理 1.导入图像 2.提取出图像中的数字 3.将列表转换成数组 4.获取特征数据集 5.获取标签数据 三、使用KNN模型 1.创建KNN模型并训练 2.KNN模型出厂前测试 3.使用测试集对KNN模型进行测试 四、传入单个图像&#xff0c;使用该模…

叉车高位盲区显示器 无线摄像头免打孔 视线遮挡的解决方案

叉车作业货叉叉货时&#xff0c;货叉升降无法看清位置&#xff0c;特别是仓储的堆高车&#xff0c;司机把头探出去才勉强可以靠经验找准方位&#xff01;一个不小心就可能叉歪了&#xff0c;使货物倾斜、跌落等等&#xff0c;从而发生事故&#xff01;如何将隐患扼杀&#xff0…

【JAVA入门】Day21 - 时间类

【JAVA入门】Day21 - 时间类 文章目录 【JAVA入门】Day21 - 时间类一、JDK7前的时间相关类1.1 Date1.2 SimpleDateFormat1.3 Calendar 二、JDK8新增的时间相关类2.1 Date 相关类2.1.1 ZoneId 时区2.1.2 Instant 时间戳2.1.3 ZoneDateTime 带时区的时间 2.2 DateTimeFormat 相关…

刷题DAY7

三个数的排序 题目&#xff1a;输入三个整数x&#xff0c;y&#xff0c;z&#xff0c;请把这三个数由小到大输出 输入&#xff1a;输入数据包含3个整数x&#xff0c;y&#xff0c;z&#xff0c;分别用逗号隔开 输出&#xff1a;输出由小到大排序后的结果&#xff0c;用空格隔…

O2OA开发知识-后端代理/接口脚本编写也能像前端一样用上debugger

在o2oa开发平台中&#xff0c;后端代理或者接口的脚本编写也能像前端一样用上debugger&#xff0c;这是来自藕粉社区用户的宝贵技术支持。 感谢藕粉社区论坛用户提供的技术分享&#xff01;tzengsh_BTstthttps://www.o2oa.net/forum/space-uid-4410.html 论坛地址&#xff1a…

【Kubernetes】k8s集群图形化管理工具之rancher

目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实…

2024年高教社杯数学建模国赛A题思路解析+代码+论文

2024年高教社杯全国大学生数学建模竞赛&#xff08;以下简称国赛&#xff09;将于9月5日晚6时正式开始。 下文包含&#xff1a;2024国赛思路解析​、国赛参赛时间及规则信息说明、好用的数模技巧及如何备战数学建模竞赛 C君将会第一时间发布选题建议、所有题目的思路解析、相…

Axure:引领智慧时代的数据可视化原型设计先锋

在数字化转型的浪潮中&#xff0c;智慧农业、智慧城市、智慧社区、智慧水务等概念如雨后春笋般涌现&#xff0c;它们不仅重塑了我们的生活空间&#xff0c;也对数据可视化提出了前所未有的要求。作为原型设计领域的佼佼者&#xff0c;Axure RP凭借其强大的交互设计能力和直观的…

关于Nachi机器人自动运行上电条件

Nachi 机器人有两种控制柜&#xff0c;分别为 FD 控制柜和 CFD 控制柜。 对于 FD 控制器&#xff0c;执行以下操作。 1.旋转控制柜钥匙&#xff0c;使其对准标注位置①。 2.旋转示教器旋钮至下图所示位置。然后依次单击绿色按钮与白色按钮&#xff0c;机器人上电运行。 对于…

2025大数据毕业设计/计算机毕业设计创新必过选题(建议收藏)

一、大数据题目 项目架构模式&#xff1a; 1、数据Python爬虫&#xff1a;selenium、requests、DrissionPage等爬虫框架 2、hadoop、Spark、Flink&#xff08;PyFlink&#xff09;数据分析【可vmvare虚拟机可windwos电脑】 3、springboot、vue.js前后分离构建系统主体 4、…

排序篇——递归实现快速排序(hoare版-挖坑法-前后指针版)

目录 前言 一、key&#xff1f; 二、思路及代码实现 1.hoare版 2.挖坑法 3.前后指针版本 总结 前言 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。它会选出一个基准值(key)&#xff0c;把它放到正确的位置(排序之后的位置)。 提示&#xff1a;以下是本篇…

c语言学习,tolower ()函数分析

1&#xff1a;tolower() 函数说明&#xff1a; 检查参数c&#xff0c;为大写字母返回对应的小写字母 2&#xff1a;函数原型&#xff1a; int toascii(int c) 3&#xff1a;函数参数&#xff1a; 参数c&#xff0c;为检测整数 4&#xff1a;返回值&#xff1a; 返回转换的小…

【Python】生成二维迷宫的算法

前言 哈里最近因为一个小插曲打算写一个设计迷宫的算法。为了锻炼脑力&#xff0c;特地没有上网搜索而是自己摸索出一个迷宫设计算法。 概述 1、需求 哈里准备实现下图的迷宫。 2、分析 可以看到&#xff0c;图里凡是x和y坐标为单数时&#xff0c;总是白色。于是哈里得到下…

二分查找专题(总)

1、经典二分查找模板 int search(vector<int>& nums, int target) {int right nums.size() - 1;int left 0;while(left < right){int mid (left right)/2;if(nums[mid] > target){right mid-1;}else if(nums[mid] < target){left mid1;}else {return…

c语言-经典例题

C语言-经典例题 一、单项选择题 1、 -- A 2、 -- C y<5 --是关系运算符的优先级大于&& -- 是逻辑运算符 3、 -- B - D选项&#xff1a;c是float类型&#xff0c;所以c/2是1.5 4、 -- C 从后往前执行&#xff08;先算后面的&a…

【uniapp】vue3+vite配置tailwindcss

安装 npm install autoprefixer tailwindcss uni-helper/vite-plugin-uni-tailwind -Dautoprefixer &#xff1a;自动管理浏览器前缀的插件&#xff0c;可以解析css文件并且添加前缀到css内容里。uni-helper/vite-plugin-uni-tailwind: 将 Tailwind CSS 框架集成到使用 Vite 作…

linux系统编程:多任务编程(进程1)

1.进程 进程:(进行中的程序)--正在运行的程序 (动态的) ---内存 程序的一次执行过程&#xff01; 一个程序一个程序 可以 对应多个进程 程序 -- a.out (可执行程序) ---静态的 程序 加载 到内存 运行起来 成为了 进程。 进程是 程序运行的实体。 程序 数据代码 2.进…

背包九讲(求方案数,求具体方案数,有依赖背包)

文章目录 求方案数基本思路代码 背包问题求具体方案基本思路代码 有依赖背包基本思路代码 求方案数 问题描述&#xff1a; 给定n nn个物品&#xff0c;以及一个容量大小为m mm的背包&#xff0c;然后给出n nn个物品的体积及价值&#xff0c;求背包最大价值是多少&#xff0c;也…

递归排序 归并排序 快排

递归 求中点 midL(R-L)/2 midL((R-L)>>1)右移一位更快 子问题等量 mast er公式 T(N) a*T(N/b)O(N^d) T(N):母 T(N/b)&#xff1a;子 &#xff08;是否等量&#xff09; a&#xff1a;调用多少次 O(N^d)&#xff1a;除去子变量之外的时间复杂度 子问题等量 上面…