【算法专题】双指针

双指针

  • 双指针
    • 1. 移动零
    • 2. 复写零
    • 3. 快乐数
    • 4. 盛水最多的容器
    • 5. 有效三角形的个数
    • 6. 和为s的两个数字
    • 7. 三数之和
    • 8. 四数之和

双指针

常见的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。

  1. 对撞指针:⼀般用于顺序结构中,也称左右指针。
  • 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
  • 对撞指针的终止条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
    left == right (两个指针指向同⼀个位置)
    left > right (两个指针错开)
  1. 快慢指针:其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动。这种方法对于处理环形链表或数组非常有用。

其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想。快慢指针的实现方式有很多种,最常用的⼀种就是:

  • 在⼀次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现⼀快⼀慢

下面我们看练习题目:

1. 移动零

题目链接 -> Leetcode -283.移动零

Leetcode -283.移动零

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

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:
输入: nums = [0, 1, 0, 3, 12]
输出 : [1, 3, 12, 0, 0]

示例 2 :
输入 : nums = [0]
输出 : [0]

提示 :

  • 1 <= nums.length <= 10^4
  • 2^31 <= nums[i] <= 2^31 - 1

其思路的本质是快排的思想:数组划分区间 - 数组分两块;我们可以用⼀个 cur 指针来扫描整个数组,另⼀个 dest 指针用来记录非零数序列的最后⼀个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。在 cur 遍历期间,使 [0, dest] 的元素全部都是非零元素, [dest + 1, cur - 1] 的元素全是零。 代码如下:

		class Solution {public:// 双指针void moveZeroes(vector<int>& nums){// dest 是最后一个非零元素的下标int cur = 0, dest = -1;while (cur < nums.size()){// nums[cur] 不为零先++dest,再交换两个值 if (nums[cur]){swap(nums[++dest], nums[cur]);}cur++;}}};

2. 复写零

题目链接 -> Leetcode -1089.复写零

Leetcode -1089.复写零

题目:给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

示例 1:
输入:arr = [1, 0, 2, 3, 0, 4, 5, 0]
输出:[1, 0, 0, 2, 3, 0, 0, 4]
解释:调用函数后,输入的数组将被修改为:[1, 0, 0, 2, 3, 0, 0, 4]

示例 2:
输入:arr = [1, 2, 3]
输出:[1, 2, 3]
解释:调用函数后,输入的数组将被修改为:[1, 2, 3]

提示:
1 <= arr.length <= 10^4
0 <= arr[i] <= 9

思路:如果从前向后进行原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数被覆盖掉。因此我们选择「从后往前」的复写策略。但是从后向前复写的时候,我们需要找到最后⼀个复写的数,因此我们的大体流程分两步:
i. 先找到最后⼀个复写的数;
ii. 然后从后向前进行复写操作

代码如下:

		class Solution {public:void duplicateZeros(vector<int>& arr){// 先用 dest 指针模拟一遍复写,当 dest == n - 1,dest 最终的位置就是复写零后数组的最终形式,可以用 cur 位置的元素覆盖它int cur = 0, dest = -1, n = arr.size();while (cur < n){if (arr[cur] == 0) dest += 2;else dest++;if (dest >= n - 1) break;cur++;}// 当 dest > n - 1 最后dest肯定走了两步,即最后复写的元素一定是零,所以直接将最后一个元素改成0后,dest向前走两步,cur向前走一步if (dest == n){arr[n - 1] = 0;cur--, dest -= 2;}// 正常的覆盖while (cur >= 0){if (arr[cur] == 0){arr[dest--] = 0;arr[dest--] = 0;cur--;}else{arr[dest--] = arr[cur--];}}}};

3. 快乐数

题目链接 -> Leetcode -202.快乐数

Leetcode -202.快乐数

题目:编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:
输入:n = 2
输出:false

提示:
1 <= n <= 2^31 - 1

思路:为了方便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平方和」这⼀个操作记为 x 操作;
题目告诉我们,当我们不断重复 x 操作的时候,计算⼀定会「死循环」,死循环的方式有两种:
▪ 情况⼀:⼀直在 1 中死循环,即 1 -> 1 -> 1 -> 1…
▪ 情况⼆:在历史的数据中死循环,但始终变不到 1
由于上述两种情况只会出现⼀种,因此,只要我们能确定循环是在「情况⼀」中进行,还是在「情况⼆」中进行,就能得到结果。

代码如下:

		class Solution {public:// 计算每个位上的平方相加,即进行快乐数的计算int bitNum(int n){int sum = 0;while (n > 0){int x = n % 10;sum += x * x;n /= 10;}return sum;}bool isHappy(int n){// 定义快慢指针,慢指针一定可以追上快指针,最后只需判断他们相遇时的结果是否是1即可int slow = n, fast = bitNum(n);while (slow != fast){slow = bitNum(slow);fast = bitNum(bitNum(fast));}if (slow == 1) return true;return false;}};

4. 盛水最多的容器

题目链接 -> Leetcode -11.盛最多水的容器

Leetcode -11.盛最多水的容器

题目:给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是(i, 0) 和(i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。

示例 1:
输入:[1, 8, 6, 2, 5, 4, 8, 3, 7]
输出:49
解释:图中垂直线代表输入数组[1, 8, 6, 2, 5, 4, 8, 3, 7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:
输入:height = [1, 1]
输出:1

提示:
n == height.length
2 <= n <= 10^5
0 <= height[i] <= 10^4

思路:对撞指针,两个指针分别定义在数组的头和尾,假设是 left 和 right,并假设 left < right;此时 left 决定了盛水的容量,此时如果我们舍去 left 并使 left++,有可能下一条边比 left 更长,此时的盛水容量有可能变大;但是如果我们舍去 right,并使 right–,就算下一条边更长,left 还是决定了盛水的容量,所以盛水的容量不变或者减小;所以我们不能舍去长的那一条边,我们可以大胆舍去短的那一边;当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left 与 right 相遇。期间产生的所有的容积里面的最⼤值,就是最终答案。

代码如下:

		class Solution {public:int maxArea(vector<int>& height){int n = height.size(), ret = 0;// 定义双指针int left = 0, right = n - 1;while (left < right){// 盛水是要看最短的那边,所以相乘用最短的板ret = max(ret, (min(height[left], height[right]) * (right - left)));// 如果左边短了,左指针往右移;否则右指针往左移if (height[left] < height[right]) left++;else right--;}return ret;}};

5. 有效三角形的个数

题目链接 -> Leetcode -611.有效三角形的个数

Leetcode -611.有效三角形的个数

题目:给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

示例 1:
输入: nums = [2, 2, 3, 4]
输出 : 3
解释 : 有效的组合是 :
2, 3, 4 (使用第一个 2)
2, 3, 4 (使用第二个 2)
2, 2, 3

示例 2 :
输入 : nums = [4, 2, 3, 4]
输出 : 4

提示 :
1 <= nums.length <= 1000
0 <= nums[i] <= 1000

思路是对数组先排序,每次固定一个数,再使用对撞指针优化;具体的思路参考代码中的注释:

		class Solution {public:int triangleNumber(vector<int>& nums){// 先进行排序sort(nums.begin(), nums.end());int ans = 0;// 先固定一个数for (int i = nums.size() - 1; i >= 2; i--){// 再使用双指针枚举另外两个数// 其中left在从小的开始枚举,right从大的开始枚举int left = 0, right = i - 1;while (left < right){// 如果 nums[left] + nums[right] > nums[i] 说明left到right区间都能和i构成三角形;所以ans累加上区间内的个数// 累加完后left没必要++了,因为数组是有序的,left都能组成三角形,比left大的肯定可以,所以此时让 right--if (nums[left] + nums[right] > nums[i]){ans += (right - left);right--;}// 否则 left++else{left++;}}}return ans;}};

6. 和为s的两个数字

题目链接 -> Leetcode -剑指 Offer 57.和为s的两个数字

Leetcode -剑指 Offer 57.和为s的两个数字

题目:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:
输入:nums = [2, 7, 11, 15], target = 9
输出:[2, 7] 或者[7, 2]

示例 2:
输入:nums = [10, 26, 30, 31, 47, 60], target = 40
输出:[10, 30] 或者[30, 10]

限制:
1 <= nums.length <= 10 ^ 5
1 <= nums[i] <= 10 ^ 6

这道题的思路与上题类似,所以直接给出代码:

		class Solution {public:vector<int> twoSum(vector<int>& nums, int target){// 因为数组已经有序,利用有序使用双指针枚举int left = 0, right = nums.size() - 1;while (left < right){if (nums[left] + nums[right] == target) return { nums[left],nums[right] };else if (nums[left] + nums[right] > target) right--;else left++;}return { -1,-1 };}};

7. 三数之和

题目链接 -> Leetcode -15.三数之和

Leetcode -15.三数之和

题目:给你一个整数数组 nums ,判断是否存在三元组[nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。

请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1, 0, 1, 2, -1, -4]
输出: [[-1, -1, 2], [-1, 0, 1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是[-1, 0, 1] 和[-1, -1, 2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:
输入:nums = [0, 1, 1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:
输入:nums = [0, 0, 0]
输出: [[0, 0, 0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • 10^5 <= nums[i] <= 10^5

此题与两数之和类似,与两数之和稍微不同的是,题目中要求找到所有不重复的三元组。那我们可以利用在两数之和那里用的双指针思想,来对我们的暴力枚举做优化:
i. 先排序;
ii. 然后固定⼀个数 a :
iii. 在这个数后⾯的区间内,使用「双指针算法」快速找到两个数之和等于 -a 即可。
但是要注意,这道题里面需要有「去重」操作:
i. 找到⼀个结果之后, left 和 right 指针要「跳过重复」的元素;
ii. 当使用完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素

代码如下:

		class Solution {public:vector<vector<int>> threeSum(vector<int>& nums){// 先对数组排序sort(nums.begin(), nums.end());vector<vector<int>> ret;// 先固定一个数,再使用双指针for (int i = 0; i < nums.size() - 1; i++){// 去重 nums[i] 相同的元素if (i > 0 && nums[i] == nums[i - 1]) continue;int left = i + 1, right = nums.size() - 1;while (left < right){if (nums[left] + nums[right] == -nums[i]){ret.push_back({ nums[left],nums[right],nums[i] });right--, left++;// 去重 left 和 rightwhile (left < right && nums[right] == nums[right + 1]) right--;while (left < right && nums[left] == nums[left - 1]) left++;}else if (nums[left] + nums[right] > -nums[i]){right--;}else{left++;}}// 因为是排序后的数组,如果 nums[i] 大于0,后面的数都比它大,找不到两数相加等于它的负数的数,所以提前跳出环      if (nums[i] > 0) break;}return ret;}};

8. 四数之和

四数之和的做法也和三数之和类似,大家可以自行尝试一下,题目链接 -> Leetcode -18.四数之和

Leetcode -18.四数之和

题目:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。
请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例 1:
输入:nums = [1, 0, -1, 0, -2, 2], target = 0
输出: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]

示例 2:
输入:nums = [2, 2, 2, 2, 2], target = 8
输出: [[2, 2, 2, 2]]

提示:
1 <= nums.length <= 200

  • 10^9 <= nums[i] <= 10^9
  • 10^9 <= target <= 10^9

下面直接看代码解析:

		class Solution {public:vector<vector<int>> fourSum(vector<int>& nums, int target){sort(nums.begin(), nums.end());vector<vector<int>> ret;int len = nums.size();// 先固定第一个数for (int i = 0; i < len - 3; i++){// 去重1.if (i > 0 && nums[i] == nums[i - 1]) continue;// 固定第二个数int aim1 = target - nums[i];for (int j = i + 1; j < len - 2; j++){// 去重2.if (j > i + 1 && nums[j] == nums[j - 1]) continue;// 使用双指针long long aim2 = (long long)aim1 - nums[j];int left = j + 1, right = len - 1;while (left < right){if (nums[left] + nums[right] > aim2){right--;}else if (nums[left] + nums[right] < aim2){left++;}else{ret.push_back({ nums[i],nums[j],nums[left],nums[right] });left++, right--;// 去重3.while (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;}}}}return ret;}};

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

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

相关文章

【深度学习实验】网络优化与正则化(七):超参数优化方法——网格搜索、随机搜索、贝叶斯优化、动态资源分配、神经架构搜索

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、优化算法0. 导入必要的库1. 随机梯度下降SGD算法a. PyTorch中的SGD优化器b. 使用SGD优化器的前馈神经网络 2.随机梯度下降的改进方法a. 学习率调整b. 梯度估计修正 3. 梯度估计修正&#xff1a;动量法Momen…

【React】React 基础

1. 搭建环境 npx create-react-app react-basic-demo2. 基本使用 JSX 中使用 {} 识别 JavaScript 中的表达式&#xff0c;比如变量、函数调用、方法调用等。 if、switch、变量声明等属于语句&#xff0c;不是表达式。 列表渲染使用 map 。 事件绑定用&#xff1b;on 事件名称…

CentOS安装nodejs

查看可安装的版本 dnf module list nodejs选择需要版本安装 dnf module install nodejs:<stream>查看版本

67基于matlab图像处理,包括颜色和亮度调整、翻转功能、空间滤波和去噪、频域滤波和去噪、噪声添加,形态学操作、边缘检测及示波器集成的GUI图像处理。

基于matlab图像处理&#xff0c;包括颜色和亮度调整、翻转功能、空间滤波和去噪、频域滤波和去噪、噪声添加&#xff0c;形态学操作、边缘检测及示波器集成的GUI图像处理。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 67 matlab图像处理图像降噪 (xiaohon…

DPC15 国产带有 SPI 接口的独立 CAN 控制器兼容替代MCP2551

DPC15是一款独立控制器局域网络&#xff08;Controller Area Network&#xff0c;CAN&#xff09;协议控制器&#xff0c;完全支持CAN V2.0B技术规范。该器件能发送和接收标准和扩展数据帧以及远程帧。 DPC15自带的两个验收屏蔽寄存器和六个验收滤波寄存器可以过滤掉不想要的报…

vue安装three.js并创建第一个入门场景

vue安装three.js&#xff0c;并创建第一个入门场景 安装three.js npm install --save three引入three.js import * as THREE from threethree.js结构 three.js坐标 创建一个场景 scene场景&#xff0c;camera相机&#xff0c;renderer渲染器 创建一个场景 this.scene new T…

C#入门(1):程序结构、数据类型

一、C#程序结构 第一个C#程序 using System;namespace base_01 {class Program{#region 代码折叠块static void Main(string[] args){//控制台输出Console.WriteLine("Hello World!");Console.Write("C#是微软的编程语言"); //不换行输出//Console.Rea…

Pikachu漏洞练习平台之SSRF(服务器端请求伪造)

注意区分CSRF和SSRF&#xff1a; CSRF&#xff1a;跨站请求伪造攻击&#xff0c;由客户端发起&#xff1b; SSRF&#xff1a;是服务器端请求伪造&#xff0c;由服务器发起。 SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&#xff0c;但又没有对目标…

canal1.1.7实战

1.环境搭建 canal可以用来监听mysql数据库的变化&#xff0c;用来同步数据 先下载最新的部署版本&#xff0c;release地址:Releases alibaba/canal GitHub 包下载地址: https://github.com/alibaba/canal/releases/download/canal-1.1.7/canal.deployer-1.1.7.tar.gz 下载…

虚拟路由冗余协议_VRRP

#初次知晓_2023-11-16 #中职在读 #网络_交换机配置部分 虚拟路由冗余协议_VRRP VRRP_概念VRRP_作用VRRP_概述VRRP_两种状态VRRP_配置配置命令&#xff1a; VRRP_概念 VRRP是一种选择协议_LAN接入设备备份协议 它可以把一个虚拟路由器的责任动态分配到局域网上的 VRRP 路由器中…

前端uniapp列表下拉到底部加载下一页列表【下拉加载页面/带源码/实战】

目录 一. 图片1.2. 二.list.vue三.uni-load-more.vue最后 一. 图片 1. 2. 二.list.vue <template><view><!--列表--><scroll-view scroll-y"true" class"scroll-Y" :style"height: scrollviewHigh px;" lower-threshol…

设计模式(二)-创建者模式(3)-抽象工厂模式

一、为什么需要抽象工厂模式&#xff1f; 在工厂模式中&#xff0c;我们需要定义多个继承于共同工厂抽象基类的工厂子类&#xff0c;这些子类负责创建一个对应的对象。工厂模式存在一个缺点就是&#xff1a;每次扩展新的工厂子类&#xff0c;就会增加系统的复杂度。 如果我们…

TP_Link WR886N 硬改闪存16M内存64M,刷入openwrt

一、换内存&#xff0c;拆闪存&#xff1a; 1、先原机开机试试是否功能正常&#xff1b; 2、拆机&#xff0c;比较难拆&#xff0c;容易坏外壳&#xff1b; 3、找到内存和闪存&#xff0c;用胶带把边上的小元件&#xff0c;电阻都贴好&#xff1b; 4、加助焊油&#xff0c;用风…

PC3329L DC-DC降压 10V-100V输入3A大流输出带EN功能实现零功耗只需极少元器件

1. PC3392L特性  通过使能脚关断实现零功耗  宽电压输入范围 10V 至 100V  最大输出电流 3A  集成功率 MOS 管  外围器件少  输出短路保护  温度保护  逐周期限流  输出电压灵活可靠  ESOP8 2. 描述 PC3392L 一款宽电压范围降压型 DC-DC 电源管…

C# 实现腾讯云多路直播流的云端混合录制

目录 应用场景 腾讯云直播和云点播 产品架构 混流显示示例 关键代码 API实现 小结 应用场景 在云考试或视频面试中&#xff0c;除了对考生、考官的实时音视频监控以防止作弊行为的发生以外&#xff0c;对直播流的音视频录制也尤为重要&#xff0c;可做为后期证据材料进…

斯坦福机器学习 Lecture2 (假设函数、参数、样本等等术语,还有批量梯度下降法、随机梯度下降法 SGD 以及它们的相关推导,还有正态方程)

假设函数定义 假设函数&#xff0c;猜一个 x->y 的类型&#xff0c;比如 y ax b&#xff0c;随后监督学习的任务就是找到误差最低的 a 和 b 参数 有时候我们可以定义 x0 1&#xff0c;来让假设函数的整个表达式一致统一 如上图是机器学习中的一些术语 额外的符号&#xf…

C# GC机制

在C#中&#xff0c;垃圾回收&#xff08;Garbage Collection&#xff0c;简称GC&#xff09;是CLR&#xff08;公共语言运行时&#xff09;的一个重要部分&#xff0c;用于自动管理内存。它会自动释放不再使用的对象所占用的内存&#xff0c;避免内存泄漏&#xff0c;减少程序员…

Vue 3 和 Spring Boot 3 的操作流程和执行步骤详解

1.介绍 在本篇博客中&#xff0c;我们将详细介绍Vue 3 和 Spring Boot 3 的操作流程和执行步骤。Vue 3 是一款流行的前端框架&#xff0c;而Spring Boot 3 是一款广泛应用于后端开发的框架。通过结合使用这两个框架&#xff0c;我们可以构建出功能强大的全栈应用。 2.Vue 3 的操…

spring boot加mybatis puls实现,在新增/修改时,对某些字段进行处理,使用的@TableField()

1.先说场景&#xff0c;在对mysql数据库表数据插入或者更新时都得记录时间和用户id 传统实现有点繁琐&#xff0c;这里还可以封装一下公共方法。 2.解决方法&#xff1a; 2.1&#xff1a;使用aop切面编程&#xff08;记录一下&#xff0c;有时间再攻克&#xff09;。 2.2&…

宝塔站点配置

我这里使用的thinkphp 框架部署的