【leetcode】双指针算法题
文章目录
- 1.算法思想
- 2.移动零
- 3.复写零
- 方法一
- 方法二
- 4.快乐数
- 5.盛水最多的容器
- 方法一(暴力求解)
- 方法二(左右指针)
- 6.有效三角形的个数
- 方法一(暴力求解)
- 方法二(左右指针)
- 7.两数之和
- 8.三数之和
- 9.四数之和
1.算法思想
常见的双指针有两种形式,⼀种是左右指针,⼀种是快慢指针。
- 左右指针:⼀般用于有序的结构中,也称左右指针。
- 左右指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
- 左右指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
left == right (两个指针指向同⼀个位置)
left > right (两个指针错开)
- 快慢指针:⼜称为龟兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
这种方法对于处理环形链表或数组非常有用。
其实不单单是环形链表或者是数组,如果研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。
快慢指针的实现⽅式有很多种,最常用的⼀种就是:
- 在⼀次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现⼀快⼀慢。
废话不多说,我们来做题。
2.移动零
leetcode 283.移动零
题目要求我们将数组中的0全部移动到数组的末尾,并且其它元素的相对顺序不变,而且不允许开辟额外的数组
。
那我们应该如何来解决这一题呢?
算法思路:
在本题中,我们可以⽤⼀个cur 指针来扫描整个数组,另⼀个dest 指针⽤来记录⾮零数序列的最后⼀个位置。
根据cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。
在cur遍历期间,使[0, dest] 的元素全部都是⾮零元素,[dest + 1, cur - 1] 的元素全是零。
最初,我们不知道非零序列的位置,所以将dest置为-1,cur置为0。cur进行扫描,在扫描过程中:
- 若cur对应元素不为0,cur后移
- 若cur对应元素为0,dest先后移,然后再交换cur与dest,最后cur再后移。
class Solution {
public:void moveZeroes(vector<int>& nums) {int dest = -1;int cur = 0;int n = nums.size();while(cur < n){//cur不为0,交换if(nums[cur] != 0){dest++;swap(nums[dest],nums[cur]);}//cur为0,继续后移cur++;}}
};
这样咱们就过啦。
3.复写零
leetcode 1089.复写零
方法一
先统计数组中0的个数,计算复写后数组的大小,使用reserve为数组重新开新的空间,在新空间上直接进行复写,不存在数组越界问题;在复写完成后,再使用对数组进行缩容,使其空间保持原状。
class Solution {
public:void duplicateZeros(vector<int>& arr) {int count = 0;int n = arr.size();int cur = 0;while(cur < n){if(arr[cur] == 0)count++;cur++;}//开辟新空间arr.reserve(n+count);//此时cur == n,cur--;//重新指向最后一个元素int dest = n+count-1;while(cur >= 0){if(arr[cur]){//不是0,无需复写arr[dest--] = arr[cur--];}else{//复写0arr[dest--] = 0;arr[dest--] = 0;cur--;}}arr.reserve(n);//恢复数组原状}
};
简单分析一下复杂度:只遍历了数组,时间复杂度为O(N);由于使用了reserve开辟了新空间,空间复杂度:O(N)
方法二
能不能在O(1)的空间复杂度下完成该题呢?
我们可以使用两个指针,cur指向最后一个要写的元素,dest指向数组的最后一个位置。
- 若cur为0,复写0,dest移动两次
- 若cur不为0,复写,dest移动一次
那现在的问题就是如何找最后一个要复写的位置。
通过模拟我们可以发现,cur = 0 ,dest = -1;让cur与dest同时走,若cur不为0,则都移动一次;若cur为0,cur移动一次,dest移动两次;直到dest走到数组的末尾,此时cur位置就是最后一个要写的位置
public:void duplicateZeros(vector<int>& arr) {int cur = 0;int dest = -1;int n = arr.size();while(dest < n-1){if(arr[cur] == 0)dest += 2;elsedest++;cur++;}}for( ; cur >=0; cur--){if(arr[cur])arr[dest--] = arr[cur];else{arr[dest--] = 0;arr[dest--] = 0;}}}
};
此时我们发现程序没过,情况又没想全
class Solution {
public:void duplicateZeros(vector<int>& arr) {int cur = 0;int dest = -1;int n = arr.size();while(cur < n){if(arr[cur] == 0)dest += 2;elsedest++;//防止越界if(dest >= n-1)break;cur++;}//已经越界,修正if(dest == n){arr[n-1] = 0;dest-=2;cur--;}//从后向前复写for( ; cur >=0; cur--){if(arr[cur])arr[dest--] = arr[cur];else{arr[dest--] = 0;arr[dest--] = 0;}}}
};
此时,该代码地时间复杂度为O(N);空间复杂度为:O(1)
4.快乐数
leetcode 202.快乐数
根据题意,通过画图我们可以发现,这就是一种循环往复地题目。此时我们就可以考虑双指针算法。
看到这个环形,是不是会想起链表那里有一个判断环形链表的题目,这两题很相似。
我们可以知道,当重复执行x的时候,数据会陷⼊到⼀个「循环」之中。
而「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的(证明参考链表部分),也就是说他们总会相遇在⼀个位置上。如果相遇位置的值是1,那么这个数⼀定是快乐数;如果相遇位置不是1的话,那么就不是快乐数。
class Solution {
public:int Sum(int n){int sum = 0;while(n){sum += (n%10)*(n%10);n/=10;}return sum;}bool isHappy(int n) {int slow = n;int fast = Sum(n);//让fast先走一步while(fast != slow){slow = Sum(slow);fast = Sum(Sum(fast));}//当二者相等时,若为1,则是快乐数,否则则不是return slow == 1;}
};
5.盛水最多的容器
leetcode 11.盛水最多的容器
首先我们要明白,这个容器的容量是:两个柱子之间的距离×两柱中较矮的一个
所以此题我们可以利用双指针,寻找两柱中组成的容器中最大的一个即可。
方法一(暴力求解)
枚举出能构成的所有容器,找出其中容积最大的值。
容器容积的计算⽅式:
设两指针分别指向水槽板的最左端以及最右端,此时容器的宽度为j - i
由于容器的⾼度由两板中的短板决定,因此可得容积公式:v = (j - i) * min( height[i], height[j])
class Solution {
public:int maxArea(vector<int>& height) {int v = 0;int n = height.size();for(int i=0; i<n; i++){for(int j = i+1; j<n; j++){v = max(v,((j-i)*min(height[i],height[j])));}}return v;}
};
方法二(左右指针)
观察暴力解法以后,我们可以发现一个规律:
当使用最开始的左右区间算出来一个V1后,我们没必要使用这两个区间中较小的一个去和其它数枚举,因为枚举出来的结果一定是小于V1的。
所以,可以按照以下步骤:
- 先根据当前两柱计算V
- 舍去两柱子中较小的一个
- 根据新的柱子再计算体积tmp,V = max(V,tmp)
class Solution {
public:int maxArea(vector<int>& height) {int v = 0;int left = 0;int right = height.size()-1;while(left < right){//先计算当前两柱组成的大小int tmp = (right-left) * min(height[left],height[right]);v = max(v,tmp);if(height[left] < height[right])left++;elseright--;}return v;}
};
6.有效三角形的个数
leetcode 611.有效三角形的个数
我们都知道,组成三角形的条件是:任意两边之和大于第三边。
方法一(暴力求解)
但是使用这个条件只想到一个暴力解法,虽然说是暴⼒求解,但是还是想优化⼀下
判断三⻆形的优化:
- 如果能构成三⻆形,需要满⾜任意两边之和要大于第三边。但是实际上只需让较小的两条边之和大于第三边即可。
- 因此我们可以先将原数组排序,然后从小到大枚举三元组,一方面省去枚举的数量,另一方面方便判断是否能构成三⻆形。
class Solution {
public:int triangleNumber(vector<int>& nums) {int n = nums.size();sort(nums.begin(),nums.end());int count = 0;for(int i=0; i<n; i++){for(int j=i+1; j<n; j++){for(int k = j+1; k<n; k++){if(nums[i] + nums[j] > nums[k])count++;}}}return count;}
};
方法二(左右指针)
将数组排序以后我们可以发现,如果我们固定最右侧数为第三条边,就只需使用左右指针找另外两条即可。
而且如果最左侧的数和右侧数加起来已经大于第三边了,那么左侧和右侧之间的数一定大于第三边,无需再枚举。
class Solution {
public:int triangleNumber(vector<int>& nums) {//先按照升序排序sort(nums.begin(),nums.end());int max = nums.size() - 1;int ret = 0;//至少要存在3条边while(max >= 2){int left = 0;int right = max - 1;while(left < right){if(nums[left] + nums[right] > nums[max]){ret += right - left;right--;}elseleft++;}max--;}return ret;}
};
7.两数之和
leetcode 179.和为target的两个数
由于该数组是升序的,那么我们可以直接使用双指针,计算左右指针之和
- 若和小于target,左指针右移
- 若和大于target,右指针左移
class Solution {
public:vector<int> twoSum(vector<int>& price, int target) {vector<int> ret;int left = 0;int right = price.size()-1;while(left < right){if(price[left]+price[right] < target)left++;else if(price[left] + price[right] > target)right--;else{break;}}return {price[left],price[right]};}
};
8.三数之和
leetcode 15.三数之和
这一题和上一题两数之和的解法非常类似,唯一的不同点就是:该题不允许有重复的三元组。
- 先排序
- 固定一个数aim
- 使用两数之和法,找和为 - aim 的两个数即可
- 不漏
- 找到一个数以后,left++,right- -继续找
- 去重
- left 与right均需要去重,如果left++后任然和之前的相同,那就继续++;right同理,仍需要 - -
- aim去重,如果aim移动后,仍然和上一个相等,那就继续移动
- 不漏
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> ret;sort(nums.begin(),nums.end());int n = nums.size();int i = 0;while(i < n){if(nums[i] > 0)//若num对应的都已经大于0,那么left 与right就更大,和不可能为0了break;int aim = -nums[i];//固定一个数,取其相反数int left = i+1;int right = n-1;while(left < right){int sum = nums[left] + nums[right];if(sum > aim)right--;else if(sum < aim)left++;else{ret.push_back({nums[i],nums[left],nums[right]});//避免遗漏,继续找left++;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;}
};
9.四数之和
leetcode 18.四数之和
该题是在三数之和的基础之上的变形。
仍然还是采用上面的做法,
- 先排序
- 固定一个数a
- 利用三数之和,固定一个数b,找和为target - nums[a] - nums[b]的数
- 不漏
- 找到一个数以后,left++,right- -继续找
- 去重
- left 与right均需要去重,如果left++后任然和之前的相同,那就继续++;right同理,仍需要 - -
- b去重,如果b移动后,仍然和上一个相等,那就继续移动
- a去重,如果a移动后,仍然和上一个相等,那就继续移动
- 不漏
class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(),nums.end());vector<vector<int>> ret;int n = nums.size();int a = 0;while(a < n){int b = a+1;while(b < n){long long aim = (long long)target -nums[a] - nums[b];int left = b+1;int right = n-1;while(left < right){if(nums[left] + nums[right] < aim)left++;else if(nums[left] + nums[right] > aim)right--;else{ret.push_back({nums[a],nums[b],nums[left],nums[right]});left++;right--;while(left < right && nums[left] == nums[left-1])left++;while(left< right && nums[right] == nums[right+1])right--;}}b++;while(b < n && nums[b] == nums[b-1])b++;}a++;while(a < n && nums[a] == nums[a-1])a++;}return ret;}
};
相关文章:

【leetcode】双指针算法题
文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一(暴力求解)方法二(左右指针) 6.有效三角形的个数方法一(暴力求解)方法二(左右指针) 7.两数之和8.…...

vue-router 源码分析——8.重定向
这是对vue-router 3 版本的源码分析。 本次分析会按以下方法进行: 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时…...

CAN总线协议
CAN总线协议,全程为控制器局域网(Controller Area Network)协议,是一种用于实时应用的串行通讯协议。该协议由德国某公司专门为汽车行业开发,并逐渐成为一种标准,这是国际上应用最广泛的现场总线之一。 一…...

NLP篇1
场景:假设给你一篇文章。 目标:说白了,就是数学的分类。但是如何实现分类呢。下面将逐步一 一 分析与拆解。先把目标定好了和整体框架定好了。而不是只见树木而不见森林。 情感分类(好评、差评,中性) 整体…...

【一念发动便是行】念头,就是命运
一个个恶念累积就是负能量,念头就是命运,克除恶念,防范念头,念头都有能量,学圣学须内外庄严检肃,言语有灵 多数人的问题都是出在念头上,念头,就是自己的命运; 当我们对自…...

Django + Vue 实现图片上传功能的全流程配置与详细操作指南
文章目录 前言图片上传步骤1. urls 配置2. settings 配置3. models 配置4. 安装Pillow 前言 在现代Web应用中,图片上传是一个常见且重要的功能。Django作为强大的Python Web框架,结合Vue.js这样的现代前端框架,能够高效地实现这一功能。本文将…...

【介绍下R-tree,什么是R-tree?】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...

每天10个js面试题(二)
1.事件轮询? JavaScript 是单线程的,同一时间只能做一件事。所有任务都需要排队,前一个任务结束,才会执行后一个任务,为了保证任务有序的执行,事件轮询就是单线程任务调度的一种方式,单线程任务…...

深入理解【 String类】
目录 1、String类的重要性 2、常用方法 2、1 字符串构造 2、2 String对象的比较 2、3 字符串查找 2、4字符转换 数值和字符串转换: 大小写转化: 字符串转数组: 格式转化: 2、5 字符串替换 2、6字符串拆分 2、7 字符串…...

Nacos 2.x 系列【20】集群部署
文章目录 1. 前言2. 部署服务端2.1 准备工作2.2 集群节点配置2.3 鉴权配置2.4 配置数据源2.5 配置 IP2.6 配置端口2.7 启动集群 3. 部署模式3.1 直连模式3.2 地址服务器模式3.2.1 地址服务器3.2.2 配置 3.3 VIP 模式(推荐)3.3.1 Nginx3.3.1 域名 1. 前言…...

LeetCode刷题记录:(15)三角形最小路径和
知识点:倒叙的动态规划 题目传送 解法一:二维动态规划【容易理解】 class Solution {public int minimumTotal(List<List<Integer>> triangle) {int n triangle.size();if (n 1) {return triangle.get(0).get(0);}// dp[i][j]:走到第i层第…...

【大数据面试题】35 Spark 怎么做优化?
一步一个脚印,一天一道大数据面试题 博主希望能够得到大家的点赞收,藏支持!非常感谢~ 点赞,收藏是情分,不点是本分。祝你身体健康,事事顺心! Spark 如何做优化一直是面试过程中常问的问题。那么这次也仅以此篇文章总结梳理,希望对大家有帮助。 通用优化 Spark 一般遇…...

2024年保安员职业资格考试题库大数据揭秘,冲刺高分!
186.安全技术防范是一种由探测、()、快速反应相结合的安全防范体系。 A.保安 B.出警 C.延迟 D.监控 答案:C 187.安全技术防范是以()和预防犯罪为目的的一项社会公共安全业务。 A.预防灾害 B.预防损失 C.预防失…...

怎么搭建个人博客教程,附云主机选购指南
一、搭建个人博客教程 1. 规划博客内容与技术栈 确定博客主题:首先明确博客的定位和主题,这将影响后续的技术选择和内容规划。选择技术栈:根据个人偏好和技术背景,选择合适的建站技术。例如,可以使用WordPress&#…...

使用Llama3/Qwen2等开源大模型,部署团队私有化Code Copilot和使用教程
目前市面上有不少基于大模型的 Code Copilot 产品,部分产品对于个人开发者来说可免费使用,比如阿里的通义灵码、百度的文心快码等。这些免费的产品均通过 API 的方式提供服务,因此调用时均必须联网、同时需要把代码、提示词等内容作为 API 的…...

C语言_结构体初阶(还未写完)
结构体的声明 1. 什么是结构?结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量 数组:一组相同类型元素的集合 结构体:一组不一定相同类型元素的集 2. 结构的声明 struct tag //tag根据实际情况给名字…...

MyBatis-Plus:快速入门
1. 概念 MyBatis-Plus(简称 MP)是一个MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。其突出的特性如下: * **无侵入**:只做增强不做改变,引入它不会对现有…...

【高级篇】第9章 Elasticsearch 监控与故障排查
9.1 引言 在现代数据驱动的应用架构中,Elasticsearch不仅是海量数据索引和搜索的核心,其稳定性和性能直接影响到整个业务链路的健康度。因此,建立有效的监控体系和掌握故障排查技能是每一位Elasticsearch高级专家的必备能力。 9.2 监控工具:洞察与优化的利器 在Elastics…...

【前端】上传和下载zip文件,有进度条(el-progess)
文章目录 上传下载进度条 场景:要上传一个zip,调用接口,然后下载一个zip。调用接口的接口响应要显示在进度条中。 上传 上传用的是input原生控件,在页面中隐藏。accept"application/zip"限制只能上传zip。 点击button…...

2024年软件测试面试题,精选100+,附答案+文档
🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 Part1 1、你的测试职业发展是什么? 测试经验越多,测试能力越高。所以我…...

在vue项目的.gitignore文件忽略不想要提交到git仓库的文件
在Vue项目中,使用.gitignore文件来忽略不需要提交到Git仓库的文件是一个常见的做法。.gitignore文件包含了一系列的规则,这些规则告诉Git哪些文件或目录应该被忽略。以下是一些Vue项目中常用的.gitignore文件示例和具体规则说明: 示例 .gitig…...

时序(流式)图谱数据仓库AbutionGraph功能介绍-Streaming Graph OLAM Database
AbutionGraph是一款端到端的流式数据实时分析的图谱数据库,实时(流式写入实时、高QPS决策分析实时、流式预处理实时)表现在: 构建实时查询QPS响应时长与历史数据量无关的图模型;接入流式数据并实时更新图计算指标&…...

windows实现Grafana+Loki+loki4j轻量级日志系统,告别沉重的ELK
文章目录 Loki下载Loki下载安装Loki添加Loki数据源springboot日志推送 Loki下载 下载地址:https://github.com/grafana/loki/releases/ 找到loki-windows-amd64.exe.zip点击开始下载,我这里下载的2.9.9版本 Loki下载 下载地址:https://gr…...

跟《经济学人》学英文:2024年06月01日这期 The side-effects of the TikTok tussle
The side-effects of the TikTok tussle tussle:美 [ˈtəsəl] 激烈扭打;争夺 注意发音 side-effects:副作用;(side-effect的复数) As the app’s future hangs in the balance, the ramifications of …...

Ubuntu安装PostgreSQL
Ubuntu(在线版) 更新软件源 sudo apt-get update 添加PostgreSQL官方数字签名 wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 将地址添加到系统的软件包源列表中 echo "deb http://apt.postgresql.org/pub/repos/a…...

【HarmonyOS NEXT】鸿蒙如何让List组件不满一屏时,还要能滑动和回弹
当List组件不满一屏时,还要能滑动和回弹,就向系统设置 - 移动网络 页面一样 List设置如下属性: .edgeEffect(EdgeEffect.Spring, {alwaysEnabled: true}) edgeEffect edgeEffect(value: EdgeEffect, options?: EdgeEffectOptions) 设置边缘滑动效果。…...

JDK-SPI-服务提供者接口
归档 GitHub: JDK-SPI-服务提供者接口 SPI 源码说明 java.util.ServiceLoader /*** 服务加载器:给定接口,查找实现类。实现可迭代接口 */ public final class ServiceLoader<S> implements Iterable<S> {/*** 返回 ServiceLoader 实例 *…...

【docker】容器内配置环境变量
背景: 我要把下面的环境变量写到bash脚本里,起名叫environment_start.sh。 目的: 用于每次进入容器dev_into.sh的时候,让系统获取到环境变量。 操作步骤: 先在容器外找个合适的位置写环境变量bash脚本,…...

Java 乐观锁与悲观锁
1. 前言 本节内容主要是对 Java 乐观锁与悲观锁进行更加深入的讲解,本节内容更加偏重于对乐观锁的讲解,因为 synchronized 悲观锁对于大部分学习者并不陌生,本节主要内容如下: 乐观锁与悲观锁的概念,之前有所讲解,这里用很小的篇幅进行知识的回顾,巩固;乐观锁与悲观锁…...

python学习2-数据结构与算法-链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指…...