【算法】双指针(上)

news2025/3/29 16:33:24

目录

双指针

左右指针(对撞指针)

快慢指针

移动零

双指针解题

复写零

暴力解题

双指针解题(快慢指针)

快乐数

双指针解题(快慢指针)

盛最多水的容器

暴力解题(会超时)

双指针解题(左右指针)

有效三角形的个数

暴力解题

双指针解题(左右指针)


双指针

常见的双指针有两种形式,一种是左右指针(对撞指针),一种是快慢指针

左右指针(对撞指针)

·左右指针从顺序结构的两端向中间移动。一个指针从最左端开始,另一个从最右端开始,然后逐渐往中间逼近

·左右指针的终止条件无非就只有两种情况(也可能在循环内部找到结果直接跳出循环)

·left==right(两个指针指向同一个位置)

·left>right(两个指针错开)

快慢指针

又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动

这种方法对于处理环形链表或数组非常有用

其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想

快慢指针的实现方式有很多种,最常用的一种就是:

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

【数组分两块】是非常常见的一种提醒,主要就是根据一种划分方式,将数组的内容分成左右两部分。这种类型的题,一般就是使用双指针来解决

移动零

题目链接:283. 移动零 - 力扣(LeetCode)

题目描述:

给定⼀个数组 nums ,编写⼀个函数将所有 0 移动到数组的末尾,同时保持⾮零元素的相对顺

序。

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

⽰例 1:

输⼊: nums = [0,1,0,3,12]

输出: [1,3,12,0,0]

⽰例 2:

输⼊: nums = [0]

输出: [0]

双指针解题

解题思路(快排的思想:数组划分区间--数组分成两块):

使用双指针。用一个cur指针来遍历整个数组,另一个dest指针用来记录非零数序列的最后一个位置。然后根据cur指针遍历到的数进行分类处理。

大致流程为:

1.初始化cur=0,dest=-1。

注意:我们这里初始化dest=-1。这里不将dest设置为0的原因是:如若将dest设置为0,dest的指向便不再是非零数序列的最后一个位置了。(因为下标为0的话,也占一个元素啊)

2.令cur遍历数组,遍历到的元素有两种情况:

·遇到的元素不是0,dest++。并交换cur和dest位置处的元素,之后让cur++。

这里dest++有两个含义:首先是因为cur指针找到一个非0数,所以dest后移一位,表示非0数的序列+1。其次是因为dest++之后,指向的元素就是0元素(因为非0元素区间末尾的后一个元素就是0)。

·遇到的元素是0,cur直接++。

解题代码

class Solution {
    public void moveZeroes(int[] nums) {
        for(int cur=0,dest=-1;cur<nums.length;cur++){
            if(nums[cur]!=0){
                dest++;
                //交换cur和dest位置处的元素
                int temp=nums[cur];
                nums[cur]=nums[dest];
                nums[dest]=temp;
            }
        }
    }
}

我们上述代码是初始化dest= -1。让dest指向非0序列的最后一个位置。

现在我们初始化dest=0。看看代码有哪里不同

class Solution {
    public void moveZeroes(int[] nums) {
        for(int cur=0,dest=0;cur<nums.length;cur++){
            if(nums[cur]!=0){
                int temp=nums[cur];
                nums[cur]=nums[dest];
                nums[dest]=temp;
                dest++;
            }
        }
    }
}

我们发现,表面来看,不过是dest++ 一个放前面和一个后面。但我们要清楚里面的门道。将dest初始为0的,则令dest指向非0序列末尾的后一个元素。

复写零

题目链接:1089. 复写零 - 力扣(LeetCode)

题目描述:

给你⼀个⻓度固定的整数数组 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]

暴力解题

思路:让指针L1遍历数组,一旦遇到元素0,便从此处开始,一直到数组末尾,每个元素后移一位。由于元素后移一位,实现复写0,所以指针L1需要格外进行一次++,跳过复写的0,不然会导致后续元素都为0.

注意:如果我们只用一个指针L1无法【从前向后】实现所有元素后移一位。因为,如果【从前向后】实现元素后移的话,会把未移动的元素给覆盖掉。所以我们这里【从后向前】实现元素后移

解题代码

class Solution {
    public void duplicateZeros(int[] arr) {
        int l1=0;
        int length=arr.length;
        for(;l1<length;l1++){
            if(arr[l1]==0){
                for(int j=length-1;j>l1;j--){
                    arr[j]=arr[j-1];
                }
                l1++;
            }
        }
    }
}

双指针解题(快慢指针)

解题思路

由于复写0会使元素+1,然而数组长度不变,数组便从末尾开始舍弃元素。所以我们只需找到 新复写数组的最后一个元素(即旧数组中最后一个复写的数)。然后进行复写

核心步骤

·先找到最后一个复写的数

·然后【从后向前】进行复写

注意:由于【从前向后】进行原地复写操作,会导致没有复写的数【被覆盖掉】。所以这里选择【从后往前】进行复写

大致流程为

a.初始化两个指针cur=0,dest=-1

b.找到最后一个复写的数

·当cur<数组长度,执行下面循环

1.若cur位置处的元素为0,dest后移两位,否则后移一位。注意:这里的dest指针可以看作记录元素个数(每移动一步相等于记录一个元素)。(保证移动步数=元素个数即可,但当最后一个复写的数为0时,移动步数会多1步,dest指针会越界)

2.判断dest指针此时是否已经到结束位置(数组末尾亦或者是越界),如果到了便终止循环

3.如果没有结束,cur++,继续循环判断

c.判断dest是否越界(最后一个复写的数是否为0),如果越界,便执行下面三步:

  1. 将数组最后一个元素的值修改成0
  2. cur指针向前移动一步(跳过最后一个复写的数(0)
  3. dest指针向前移动两步(指向倒数第二个数--跳过数组最后一个元素)

注意:我们要知道,数组最后一个元素和最后一个复写的数,这两个名词并不等同

d.从cur位置开始往前遍历原数组,还原出复写后的数组

解题代码

class Solution {
    public void duplicateZeros(int[] arr) {
        int cur=0;
        int dest=-1;
        int n=arr.length;
        //找到最后一个复写的数
        while(cur<n){
            if(arr[cur]!=0){
                dest++;
            }else{
                dest+=2;
            }
            if(dest>=n-1)break;//结束条件
            cur++;
        }
        //处理越界情况--即最后一个复写的数为0
        if(dest==n){
            arr[n-1]=0;
            cur--;
            dest-=2;
        }
        //从后往前还原出复写后的数组
        for(;cur>=0;cur--){
            if(arr[cur]!=0){
                arr[dest--]=arr[cur];
            }else{
                arr[dest--]=0;
                arr[dest--]=0;
            }
        }
    }
}

快乐数

题目链接:202. 快乐数 - 力扣(LeetCode)

题目描述:

编写⼀个算法来判断⼀个数 n 是不是快乐数。

「快乐数」 定义为:

对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和。

然后重复这个过程直到这个数变为 1,也可能是⽆限循环但始终变不到 1

如果这个过程 结果为 1 ,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false

⽰例 1:

输⼊: n = 19

输出: true

解释:

19 -> 1 * 1 + 9 * 9 = 82

82 -> 8 * 8 + 2 * 2 = 68

68 -> 6 * 6 + 8 * 8 = 100

100 -> 1 * 1 + 0 * 0 + 0 * 0 = 1

⽰例 2:

输⼊: n = 2

输出: false

解释:(这⾥省去计算过程,只列出转换后的数)

2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16

往后就不必再计算了,因为出现了重复的数字,最后结果肯定不会是 1

题目解析

我们将 【对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和】这一操作用 function方法来解决。

重点是:题目定义中的【无限循环】。

意思是:

1.如果这个数n不是快乐数,就会【无限循环】但始终变不到1。

2.然而,如果这个数是快乐数,即最终会变到1,此时1便会【无限循环】。

所以,无论这个数n是不是快乐数,都会【无限循环】

所以,当我们不断循环function方法,计算一定会【无限循环】,【无限循环】的方式有两种:

情况1:在旧的数据中【无限循环】,但始终变不到1。

情况2:一直在1中进行【无限循环】

【无限循环】原理解释:

由于int最大值2^31-1=2147483647,哪怕是9999999999这样大的数据,各个位置上的数字的平方和为810。所以在int范围内的数n,其各个位置上的数字的平方和一定在[1,810]区间内。极限情况下,一个数变化811次之后,必然会形成一个循环。因此使用【快慢指针】来解决本题

双指针解题(快慢指针)

解题思路

当我们重复使用function方法时,数据会陷入【无限循环】中。

【快慢指针】的一个特性就是,在一个圆圈中,快指针总是会追上慢指针的。也就是说,它们总会相遇在一个位置上。如果这个位置是1,那么这个数一定是快乐数;否则,这个数便不是快乐数

重点:相遇位置以及该数是不是1

大致流程为:

  1. 写一个function方法:用来求数n每个位置上的数字的平方和
  2. 让slow每次走一步,fast每次走两步,判断相遇位置处的元素是否为1。如果是1,输出true;否则,输出false

题目代码

class Solution {
    public static boolean isHappy(int n) {
        int slow=n
        int fast=function(n);//注意,这里初始化fast!=slow是因为,我们要求它俩相遇的位置
        //当fast指针追上slow指针,并且都为1时,返回true
        while(slow!=fast){
            slow=function(slow);//走一步
            fast=function(function(fast));//走两步
        }
        return slow==1;
    }
    private static int function(int n) {
        int sum=0;
        while(n!=0){
            int g=n%10;
            sum+=g*g;
            n/=10;
        }
        return sum;
    }
}

盛最多水的容器

题目链接:11. 盛最多水的容器 - 力扣(LeetCode)

题目描述:

给定⼀个⻓度为 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

暴力解题(会超时)

思路:一个一个进行遍历,找出其中容积(V)最大的值

容积公式=长*宽

设两指针i,j,初始化i=0,j=i+1,我们知道两指针之间的距离=宽度=j-i。由于容器的高度由两板中的短板决定,所以容积公式为:v=(j-i)*min(height[i],height[j])

解题代码

class Solution {
    public int maxArea(int[] height) {
        int n=height.length;
        int v=0;//v=(j-i)*min(height[i],height[j])
        int sum=0;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                v=(j-i)*Math.min(height[i],height[j]);
                if(v>=sum){
                    sum=v;
                }
            }
        }
        return sum;
    }
}

双指针解题(左右指针)

解题思路

设两个指针left,right分别指向容器的左右两个端点。

此时容器的容积v=(right-left)*min(height[left],height[right])

容器的【左边界】为height[left],【右边界】为height[right]

我们先假设【左边界】小于【右边界】

如果我们固定一个边界,改变另一个边界,容积v会有以下变化形式:

   1.容器的宽度一定变小

   2.由于【左边界】小,所以决定了容器的高度。如果改变【左边界】,容器的高度便不确定(相较于原先的【左边界】),但是一定不会超过【右边界】的高度,因此容器的容积v可能会增大

   3.如果改变右边界,无论【右边界】移动到哪里,新容器的高度一定不会超过【左边界】,也就是容器的高小于等于【左边界】,但是容器的宽度随着【右边界】的移动而减少,因此容器的容积v一定会变小

我们这里可以把【左边界】替换为较小值,【右边界】替换为较大值。

根据上述假设,移动【右边界】会导致容积v变小,所以,我们这里只移动【较小值】,并比较容积v,便能得到最大值

解题代码

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

有效三角形的个数

题目链接:611. 有效三角形的个数 - 力扣(LeetCode)

题目描述:

给定⼀个包含⾮负整数的数组 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

解释:

4,2,3

4,2,4

4,3,4

2,3,4

暴力解题

解题思路:三层for循环枚举出所有的三元组

构成三角形的条件:需要满足任意两边之和大于第三边。但是实际上只需让较小的两条边之和大于第三边即可。因此我们这里先排序,再枚举。

解题代码

lass Solution {
    public static int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int n=nums.length;
        int count=0;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                for(int t=j+1;t<n;t++){
                    if(nums[i]+nums[j]>nums[t]){
                        count++;
                    }
                }
            }
        }
        return count;
    }
}

双指针解题(左右指针)

由于我们左右指针只能表示两个元素,但为了找出这个三元组,可以尝试固定一条边,这样只需要找到一个二元组即可。

解题思路

1.先将数组排序

2.我们这里选择固定一个【最长边】,然后在比【最长边】小的有序数组中找出一个二元组,使这个二元组之和大于这个【最长边】。由于数组是有序的,我们这里采用【左右指针】。

  假设最长边在下标i处,区间[left,right]是i位置左边的区间--也就是比i小的区间:

  如果nums[left]+nums[right]>nums[i]:

   ·说明[left,right-1](因为left处的元素是最小的)区间上的所有元素都可以和nums[right]构成一个二元组,使其之和大于nums[i]

   ·满足条件的有(right-1-left)+1=right-left种

   ·此时right处的元素的所有情况相当于全部考虑完毕,right--,进入下一轮判断

 如果nums[left]+nums[right]<nums[i]:

   ·说明left处的元素是不能和[left+1,right](因为right处的元素已经是最大的了)区间上的元素构成一个二元组,使其之和大于nums[i]

   ·left处的元素舍去,left++

解题代码

class Solution {

public static int triangleNumber(int[] nums) {
    Arrays.sort(nums);
    int n=nums.length;
    int count=0;
    //左右指针:固定一个最长边(i)---下标[2,n-1]
    for(int i=n-1;i>=2;i--){
        int left=0;
        int right=i-1;
        while(left<right){
            if(nums[left]+nums[right]>nums[i]){
                //[left,right-1]区间上的元素 和nums[right]的和都大于nums[i]
                count+=right-left;//注意:这里不用count++,因为
                right--;//选择较小的数 判断是否大于固定的最长边
            }else{
                left++;
            }
        }
    }
    return count;
   }
}

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

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

相关文章

深度学习模型常用激活函数集合

激活函数是深度学习模型中的关键组成部分&#xff0c;用于引入非线性特性&#xff0c;使神经网络能够学习复杂的模式和映射关系&#xff1b;神经网络本质上是一个复合函数。如果没有激活函数&#xff0c;无论网络有多少层&#xff0c;其输出都只是输入的线性组合。激活函数通过…

WebAssembly 3.0发布:浏览器端高性能计算迎来新突破!

“WebAssembly 3.0来了&#xff0c;浏览器端的高性能计算将彻底改变&#xff01;”2025年&#xff0c;WebAssembly&#xff08;Wasm&#xff09;迎来了重大更新——WebAssembly 3.0正式发布。这次更新不仅支持多线程和SIMD指令集&#xff0c;还优化了内存管理&#xff0c;让浏览…

ERP对制造业务有何价值?

ERP 的定义 在定义 ERP 之前&#xff0c;我们先从其首字母缩写说起&#xff0c;ERP 代表企业资源规划。我们可以将 ERP 定义为一种企业软件&#xff0c;它帮助组织管理日常业务。从根本上讲&#xff0c;ERP 将客户管理、人力资源、商业智能、财务管理、库存以及供应链功能整合…

哈希表(C语言版)

文章目录 哈希表原理实现(无自动扩容功能)代码运行结果 分析应用 哈希表 如何统计一段文本中&#xff0c;小写字母出现的次数? 显然&#xff0c;我们可以用数组 int table[26] 来存储每个小写字母出现的次数&#xff0c;而且这样处理&#xff0c;效率奇高。假如我们想知道字…

亚马逊企业购大客户业务拓展经理张越:跨境电商已然成为全球零售电商领域中熠熠生辉的强劲增长点

2024年12月26日-27日&#xff0c;由中国产业海外发展协会上合-海湾双链专委会指导、极新主办的「重度垂直2024极新AIGC峰会」先后在深圳、香港两地顺利开幕。本届峰会以AI的垂直应用与出海为核心主题&#xff0c;旨在深入探讨AI技术在全球范围内的融合应用与发展趋势&#xff0…

VirtualBox 中使用 桥接网卡 并设置 MAC 地址

在 VirtualBox 中使用 桥接网卡 并设置 MAC 地址&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;设置桥接网卡 打开 VirtualBox&#xff0c;选择你的虚拟机&#xff0c;点击 “设置” (Settings)。进入 “网络” (Network) 选项卡。在 “适配器 1” (Adapt…

idea无法联网,离线安装插件

插件地址&#xff1a;https://plugins.jetbrains.com/ JetBrains Marketplace 如果无法进入&#xff0c;可以试试 配置hosts 3.163.125.103 plugins.jetbrains.com ip 变了&#xff0c;可以查询个最新的&#xff1a; https://tool.chinaz.com/speedtest/plugins.jetbrai…

网络安全中的机器学习

当涉及到网络安全时&#xff0c;技术一直是保护系统免受攻击和数据泄露的关键。在这篇论文中&#xff0c;我将介绍一些当前在网络安全领域使用的关键技术&#xff0c;包括加密&#xff0c;身份验证和防火墙。 首先&#xff0c;加密是网络安全中最常见的技术之一。加密是指使用算…

halcon 条形码、二维码识别、opencv识别

一、条形码 函数介绍 create_bar_code_model * 1.创建条码读取器的模板 * 参数一&#xff1a;通用参数的名称&#xff0c;针对条形码模型进行调整。默认值为空 * 参数二&#xff1a;针对条形码模型进行调整 * 参数三&#xff1a;条形码模型的句柄。 create_bar_code_model (…

平板作为电脑拓展屏

有线串流&#xff08;速度更快&#xff09; spacedesk 打开usb对安卓的连接 用usb线直接连接电脑和平板 无线串流&#xff08;延迟高&#xff0c;不推荐&#xff09; todesk pc和手机端同时下载软件&#xff0c;连接后可以进行远程控制或扩展屏幕 spacedesk 连接到同一个…

【算法与数据结构】字典树(Trie)详解

目录 一&#xff0c;字典树的定义 二&#xff0c;字典树的代码实现 完整代码详细注释&#xff1a; 测试用例测试结果&#xff1a; 三&#xff0c;处理其他字符 四&#xff0c;内存优化与扩展 1. 内存优化 2. 扩展功能 五&#xff0c;扩展功能支持通配符匹配 六&…

【JavaEE进阶】MyBatis通过注解实现增删改查

目录 &#x1f343;前言 &#x1f340;打印日志 &#x1f334;传递参数 &#x1f38b;增(Insert) &#x1f6a9;返回主键 &#x1f384;删(Delete) &#x1f332;改(Update) &#x1f333;查(Select) &#x1f6a9;起别名 &#x1f6a9;结果映射 &#x1f6a9;开启驼…

Simulink Ststeflow教程 — 2 创建和编辑状态

目录 2.1 创建和编辑状态 2.1.1 状态的创建 2.1.2 创建连接节点 2.1.3 转移 2.1.5 默认转移 在Stateflow模型中&#xff0c;将包含有状态的Stateflow框图称状态图而将不包含任何状态的Stateflow框图称为流程图。其中&#xff0c;状态图是Stateflow最常用的一种形式&#x…

Fiddler笔记

文章目录 一、与F12对比二、核心作用三、原理四、配置1.Rules:2.配置证书抓取https包3.设置过滤器4、抓取App包 五、模拟弱网测试六、调试1.线上调试2.断点调试 七、理论1.四要素2.如何定位前后端bug 注 一、与F12对比 相同点&#xff1a; 都可以对http和https请求进行抓包分析…

线上就医全流程医药机构接入文档接口代码-医保就医接口php-demo版本

2025年2月18日11:28:03 国密算法开发库推荐 lpilp/guomi 我测试过php 7.2 - 8.0都可以兼容&#xff0c;如果有能力可以自己开发 目前已经开发了核心的接口的测试demo,并且封装了工具类直接写业务逻辑即可&#xff0c;并且已经有线上项目在使用&#xff0c;如果需要demo代码可…

智能选路+NAT实验 作业

拓扑图 配置ip 防火墙安全区域划分 用户配置 dns透明

LLM:RAG

原文链接&#xff1a;LLM&#xff1a;RAG 1、RAG 概览 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种结合了信息检索&#xff08;IR&#xff09;和 LLM 的技术。它的核心思想是在 LLM 生成回答之前&#xff0c;通过检索相关文档…

基于Qt 和微信小程序的用户管理系统:WebSocket + SQLite 实现注册与登录

目录 一. 概要 二. 技术栈 三. 系统功能设计 3.1 功能模块 3.2 数据表设计 四. 具体实现 4.1 Qt 服务端 4.1.1 初始化 WebSocket 服务器 4.1.2 用户管理界面 4.2 微信小程序端 4.2.1 注册功能 4.2.2 登录功能 五. 运行效果 六. 源码下载 一. 概要 在物联网和智能设备…

策略+适配器模式详解

文章目录 1.策略模式1.目录结构2.Strategy.java 策略接口3.StrategyA.java 策略A4.StrategyB.java 策略B5.StrategyC.java 策略C6.Context.java 策略上下文7.Client.java 客户端8.小结 2.适配器模式1.目录结构2.CustomPaymentProcessor.java 自己的支付接口3.PayPalPaymentServ…

未来游戏:当人工智能重构虚拟世界的底层逻辑

未来游戏&#xff1a;当人工智能重构虚拟世界的底层逻辑 在《赛博朋克2077》夜之城的霓虹灯下&#xff0c;玩家或许已经注意到酒吧里NPC开始出现微表情变化&#xff1b;在《艾尔登法环》的开放世界中&#xff0c;敌人的战术包抄逐渐显露出类人智慧。这些细节预示着游戏产业正站…