算法:双指针

算法:双指针

    • 双指针
    • 快慢指针
    • 对撞指针
    • 总结


双指针

LeetCode 283.移动零

在这里插入图片描述

以上题目要求我们把所有0移动到数组的末尾,也就是说,我们要把数组转化为以下状态:

[ 非0区域 ] [ 0区域 ]

像这种把一个数组划分为多个区域的题型,就是数组划分。而数组划分非常适合使用双指针算法

对于这道题,我们可以用两个指针,对数组进行动态的区域划分。

在这里插入图片描述

其中dest指针维护一段区域,表示非0区域cur指针与dest之间的区域是0区域。而cur后面的区域是待处理区域

也就是我们的数组被划分为了以下情况:

[ 非0区域 ] [ 0区域 ] [ 待处理区域 ]

当我们的cur移动后,对于一个新的数据,就要根据条件来决定把它放到哪一个区域去。当cur指针走到末尾,数组就被处理为了:

[ 非0区域 ] [ 0区域 ]

也就是题目要求的样子了。

对于本题而言,如果新插入的数据是0,那就放到cur维护的区域里面,此时直接cur++即可;如果新插入的数据是非0,那么就放到dest的末尾,为了保证dest不会覆盖掉cur原本维护的0,因此要进行一个交换操作。

在这里插入图片描述

代码如下:

class Solution
{
public:
    void moveZeroes(vector<int>& nums) 
    {
        int dest = -1;
        int cur = 0;
        int sz = nums.size();

        while (cur < sz)
        {
            if (nums[cur])
                swap(nums[++dest], nums[cur]);
            cur++;
        }
    }
};

LeetCode 75.颜色分类

在这里插入图片描述

这一题也是一个区域划分问题,根据上一题的思路,我们可以把数组划分为以下状态:

[ 0区域 ] [ 1区域 ] [ 2区域 ]

相比于上一题,这次我们要划分出三个区域来,毫无疑问就需要更多的指针来维护区域,大致可以划分为:

[ 0区域 ] [ 1区域 ] [ 待处理 ] [ 2区域 ]

我们让维护0区域的指针left向右拓展,维护2区域的指针right向左拓展,以及利用cur指针向右遍历数组:

在这里插入图片描述

上图中,left指针左侧维护了一段0区域leftcur之间维护了一段1区域curright之间是待处理区域,right右侧是2区域。当cur往后遍历,如果遇到一个新的数据,就要决定把它放到哪一个区域去:

  1. 遇到0,就交换++leftcur的值,把0放到left后面一位,拓展left指针维护的区域
  2. 遇到1,就直接++cur,把1放到curleft之间
  3. 遇到2,就交换--rightcur的值,把2放到right的前面一位,拓展right维护的区域

要注意的是,当把0交给left后,cur++;但是当把2交给right后,cur不能移动。因为left后面的一位数一定是符合cur范围要求的,也就是区间(left, cur]之间的数据被交换到了cur的位置,此时不用判断被交换过来的数据。而right的前面一位数据是不确定的,也就是把区间(cur, right)之间的数据,交换给了cur。而(cur, right)之间的数据是待处理的不确定数据,因此还需要额外判断一次。比如上图中,right的前一位数据就是0,交换后状态如下:

在这里插入图片描述

可以看到,cur得到的数据是0,应该被放到left维护的区域去。

总代码如下:

class Solution
{
public:
    void sortColors(vector<int>& nums)
    {
        int left = -1;
        int right = nums.size();
        int cur = 0;

        while (cur < right)
        {
            if (nums[cur] == 0)
                swap(nums[++left], nums[cur++]);
            else if (nums[cur] == 2)
                swap(nums[--right], nums[cur]);//交换后不能cur++,进入下一轮循环在判断一次cur的值
            else
                cur++;
        }
    }
};

快慢指针

LeetCode 202.快乐数

在这里插入图片描述

该题的意思是,每个数字的下一个数字是该数字所有位的平方和,比如 19 的下一位数字就是 1 2 + 9 2 = 82 1^{2} + 9^{2} = 82 12+92=82。一直以这个规则操作下去,会出现两种情况:

  1. 最后该数字变为1,那么该数字是快乐数
  2. 该数字陷入死循环,那么该数字不是快乐数
  • 比如19 -> 82 -> 68 -> 100 -> 1 ,最后19变成了1,所以19是一个快乐数
  • 比如2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4,可以发现其陷入了一个死循环,从4开始又以4结束,那么2就不是一个快乐数

这题的难点不在于如何计算下一位快乐数字,而在于如何判断一个数字陷入了死循环

对于这种循环问题,就可以使用快慢指针

快慢指针规则如下:

  1. 定义两个变量fastslow
  2. fast2步,slow走一步
  3. 如果fast走到终点,说明没有循环
  4. 如果fastslow相遇,说明陷入了循环

一开始fastslow都在起点:

在这里插入图片描述

fast走两步,slow走一步:

在这里插入图片描述

由于这是一个循环结构,当fast进入第二圈循环,slow还没有走完第一圈:

在这里插入图片描述

这个时候,由于slow的速度为1fast的速度为2,那么每次行动fastslow的距离就会缩短1,最后fast一定可以遇到slow

在这里插入图片描述

此时两者相遇,说明这是一个循环结构

因此代码逻辑如下:

  1. 定义两个变量,fastslowfast走两步,slow走一步
  2. 如果fast变成1,那么该数字是快乐数,返回true
  3. 如果fastslow相遇,那么有循环结构,该数字不是快乐数,返回false

代码如下:

class Solution 
{
public:
    int getNextNum(int n) //到下一个数字的函数
    
    {
        int num = 0;

        while (n)
        {
            int tmp = n % 10;
            num += tmp * tmp;
            n /= 10;
        }

        return num;
    }

    bool isHappy(int n) 
    {
        int fast = n;
        int slow = n;

        while (fast != 1)
        {
            fast = getNextNum(getNextNum(fast));//fast走两步
            slow = getNextNum(slow);//slow走一步

            if (fast == slow && fast != 1)//两者相遇,有循环
                return false;
        } 
            
        return true;//到这里说明fast = 1,是快乐数
    }
};

其中有一个注意点,就是条件fast == slow && fast != 1,这里一种情况就是fast虽然和slow相等,但是两者都为1,此时是快乐数。

比如数字1010 -> 1 -> 1slow走第一步就是1了,fast走两步也是1,此时两者相等。


LeetCode 141.环形链表

在这里插入图片描述

本题要求判断一个链表中有没有环结构,那就是一个循环问题,要使用快慢指针

我们刚刚已经讲解过快慢指针了,现在我们直接说明代码逻辑:

  1. 定义两个指针fastslowfast走两步,slow走一步
  2. 如果fastnullptr,说明链表无环,返回true
  3. 如果fastslow相遇,说明有环,返回false

代码如下:

class Solution {
public:
    bool hasCycle(ListNode* head)
    {
        ListNode* slow = head;
        ListNode* fast = head;

        while (fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;

            if (fast == slow)
                return true;
        }

        return false;
    }
};

注意事项:

  1. 进入循环时,因为fast要走两步,因此要判断fast->next是不是空指针,否则fast->next-->next就是访问空指针行为

LeetCode 142.环形链表II

在这里插入图片描述

这道题要求我们求出环形链表的环入口,这就麻烦了,通过之前的fastslow的快慢指针追击问题,我们已经可以判断是否存在环结构了,那么我们要如何求环入口呢?

看到以下示意图:

在这里插入图片描述

现在slowfast刚好相遇,参数含义如下:

L:从起点到环入口的距离
C:环周长
X:相遇点和环入口的距离

由于fast的速度是slow的两倍,当slow走的距离是L + X,因此此时fast走了2(L + X)的距离。

又由于fast一定经过了起点到入口的L距离,因此此时fast在环中走了2(L + X) - L = L + 2X的距离。

假设fast已经在环中转了n圈,因此此时fast在环中走了nC + X的距离

那么可以列出公式:

n C + X = L + 2 X nC + X = L + 2X nC+X=L+2X

变形得到:

n C − X = L \mathbf{{\color{Red} nC - X = L} } nCX=L

这个公式具有重大意义,L代表起点到入口的距离,nC代表n个环周长,X代表当前相遇点与入口的距离。

nC -X整体可以看成(n - 1)C + (C - X)C - X就代表相遇点开始与环入口的优弧的长度

此时如果让一个指针A从起点开始走L的距离所用的时间,和让一个指针B从相遇点开始走n - 1圈外加一个C - X的距离所用的时间是一致的。由于相遇点与环入口距离已经是X了,那么指针B最后刚好会走到环入口。而指针A从入口开始走L到达的地方也是环入口,因此两者相遇!

通过这个公式可知:让一个指针从起点开始走,一个指针从相遇点开始走,两者会在环入口相遇

因此我们的代码逻辑如下:

  1. 定义两个指针fastslowfast走两步,slow走一步
  2. 如果fastnullptr,说明链表无环,返回nullptr
  3. 如果fastslow相遇,说明有环,开始找环入口
  • 在相遇点定义一个指针B,也就是B = slow;在起点定义一个指针A,也就是A = head
  • 两个指针各自行动,速度一致,最后AB相遇的地方就是环入口
  • 返回AB

代码如下:

class Solution
{
public:
    ListNode* detectCycle(ListNode* head)
    {
        ListNode* fast = head;
        ListNode* slow = head;

        while (fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;

            if (slow == fast)  // 有环,开始找入口
            {
                ListNode* A = head;
				ListNode* B = slow;
				
                while (A != B)//A与B相遇点,就是环入口
                {
                    A = A->next;
                    B = B->next;
                }

                return A;
            }
        }

        return nullptr; //没有环,返回空指针
    }
};


对撞指针

LCR 179.查找总价格为目标值的两个商品

在这里插入图片描述

这道题给了一个升序数组,要求我们求出数组中任意两个值的和刚好为target

该数组有一个特点:有序数组。像这种在一个有序区间中查找两个元素,计算得到固定值的题型,就可以使用对撞指针

如果我们直接暴力解法的话,那就是枚举所有两两一对的元素组,直到某一对刚好和为target,时间复杂度为 O ( N 2 ) O(N^{2}) O(N2)

假设现在的数组为[8, 21, 27, 34, 52]target = 61

因为数组具有单调性,我们可以一开始就枚举最左侧和左右侧的两个元素

在这里插入图片描述

当前8 + 52 = 6061小,说明我们需要一个更大的值来增大当前总和。那么由于数组升序,我们只需要left++即可:

在这里插入图片描述

当前21 + 52 = 7361大,说明我们需要一个更小的值来缩小当前总和。那么由于数组升序,我们只需要right--即可:

在这里插入图片描述

当前21 + 34 = 5561小,说明我们需要一个更大的值来增大当前总和。那么由于数组升序,我们只需要left++即可:

在这里插入图片描述

当前27 + 34 = 61,我们就找到了目标值,返回leftright下标即可。

正是由于数组单调性,我们可以通过比大小来进行指针的调整,来决定当前总值要变大还是变小,如果最后leftright相遇了,说明不存在这样的和。

为什么可以这样优化呢?看到这个例子:

在这里插入图片描述
对于34而言,其要去枚举除了自己以外的所有值,但是当前21 + 34 = 5561小,如果把21改为8,那只总和只会更小,因此我们只需要再枚举比21大的值即可。也就是通过单调性,免去了很多不必要的枚举行为

代码如下:

class Solution 
{
public:
    vector<int> twoSum(vector<int>& price, int target)
    {
        int left = 0;
        int right = price.size() - 1;

        while (left < right)
        {
            if (price[left] + price[right] > target) //和比target大,right--
                right--;
            else if (price[left] + price[right] < target) //和比target小,left++
                left++;
            else
                return { price[left], price[right] };
        }

        return { 0, 0 };
    }
};

LeetCode 11.盛水最多的容器

在这里插入图片描述

本题要求存储的最大水量,其实也就是两个下标之间的距离right - leftmin(height[left], height[right])的乘积。

如果暴力枚举,那么就是枚举出每一对下标,然后求出最大面积,时间复杂度为 O ( N 2 ) O(N^{2}) O(N2)

本题也可以使用双指针的算法,水量的值与right - leftmin(height[left], height[right])两个变量正相关,如果我们利用两个指针向内枚举,那么right - left这个值是一直在减小的,也就是有一项有单调性

对于这种,如果利用对撞指针下标向内枚举,对组成结果的某一项变量的影响是单调的,就可以考虑对撞指针

假设我们已经在边界上定义好了leftright指针:

在这里插入图片描述

第一个问题就是,应该以什么规则进行向内移动指针?

移动的过程中,水的宽度的一直递减的,那么我们就要尽可能找到height高的元素,来拔高水量。

比如说当前的两个高度为37,由于水的高度取决于较低的那个元素,因此高度为7的指针无论怎么向内移动,水高度都不会超过3,而这个过程中宽度又是单调递减的,因此right指针向内移动,所有的结果都小于当前的水量。

比如这个情况:

在这里插入图片描述

虽然right向内移动,找到了比7更大的元素,但是由于此时水高度取决于3,而且宽度还变小了,最后总水量没有变大。

因此每次都把比元素值较小的那个指针向内移动

代码如下:

class Solution
{
public:
    int maxArea(vector<int>& height)
    {
        int left = 0;
        int right = height.size() - 1;
        int ret = -1;

        while (left <= right)
        {
            int w = right - left;//宽度
            int h = min(height[left], height[right]);//高度

            ret = max(ret, w * h);//更新最大水量

			//每次都让较小的元素向内枚举
            if (height[left] > height[right])
                right--;
            else
                left++;
        }

        return ret;
    }
};

总结

双指针

  • 数组划分使用双指针算法。

一般处理为[A区域][B区域]...[待处理]这样的格式,当新元素符合哪一个区域特性,就放到哪一个区域中。

快慢指针

  • 循环问题,利用快慢指针来判断是否有循环。

相遇就是有环,如果fast走到结尾就是无环。

如果要找循环开始节点,一个指针从头开始走,一个指针从相遇点开始走,最后会在环入口相遇。

对撞指针

  • 在一个有序区间中查找两个元素,计算得到固定值的题型,使用对撞指针。

  • 如果利用对撞指针下标向内枚举,对组成结果的影响是单调的,就可以考虑对撞指针。

对撞指针的特点在于利用单调性减小搜索范围。


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT 会被 OpenAI 的版权诉讼摧毁吗?|TodayAI

未来人工智能是否能与人类融合成为一个引人关注的问题&#xff0c;但目前&#xff0c;ChatGPT等人工智能技术可能首先需要面对一个更紧迫的挑战&#xff1a;大规模的版权侵权诉讼。近期&#xff0c;ChatGPT因涉嫌在未经授权的情况下使用大量作者的作品进行训练&#xff0c;而被…

【Linux】网络基础(一)

文章目录 一、计算机网络背景1. 网络发展2. 认识“协议” 二、网络协议初识1. 协议分层2. OSI七层模型3. TCP/IP五层&#xff08;或四层&#xff09;模型 三、网络传输基本流程1. 同局域网的两台主机通信数据包封装和分用封装分用 2. 跨网络的两台主机通信 四、网络中的地址管理…

轻量带屏解决方案之恒玄芯片移植案例

本文章基于恒玄科技BES2600W芯片的欧智通 Multi-modal V200Z-R开发板 &#xff0c;进行轻量带屏开发板的标准移植&#xff0c;开发了智能开关面板样例&#xff0c;同时实现了ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF等部件基于OpenHarmony LiteOS-M内…

线性代数

标量、向量、张量 标量占据的是零维空间向量占据的是一维数据&#xff0c;例如语音信号矩阵占据的是二维数组&#xff0c;例如灰度图像张量占据的是三维乃至更高维的数组&#xff0c;例如RGB图像和视频 内积(点乘)概述 内积(inner product) 计算的则是两个向量之间的关系 两…

掌握JMeter HTTP 请求头:简单易懂

在深入研究 JMeter 的过程中&#xff0c;任何涉及性能测试或接口验证的专业人员都会认识到&#xff0c;合理配置HTTP请求头部信息是实现精确测试的关键步骤之一。不同情景下&#xff0c;如数据提交形式的不同&#xff08;例如 JSON、XML 等&#xff09;&#xff0c;或是需要通过…

利用正射影像对斜射图像进行反向投影

在图像投影和映射领域,有两种类型的投影:正向投影和反向投影。正向投影涉及使用内部方向(即校准相机参数)将 3D 点(例如地面上的物体)投影到 2D 图像平面上。另一方面,向后投影是指根据 2D 图像确定地面物体的 3D 坐标的过程。 为了匹配倾斜图像和正射影像并确定相机位置…

Kubernetes(k8s):深入理解k8s中的亲和性(Affinity)及其在集群调度中的应用

Kubernetes&#xff08;k8s&#xff09;&#xff1a;深入理解k8s中的亲和性&#xff08;Affinity&#xff09;及其在集群调度中的应用 1、什么是亲和性&#xff1f;2、节点亲和性&#xff08;Node Affinity&#xff09;2.1 硬性节点亲和性规则&#xff08;required&#xff09;…

linux 磁盘分区Inode使用率达到100%,导致网站无法创建文件报错 failed:No space leftondevice(

linux 磁盘分区Inode使用率达到100%&#xff0c;导致网站无法创建文件报错 failed:No space left on device 由于这问题直接导致了&#xff0c;网站无法正常运行&#xff01; 提交工单求助阿里后&#xff0c;得到了答案&#xff01; 工程师先让我执行 df -h 和 df -i 通过分析…

Harmony鸿蒙南向外设驱动开发-Codec

功能简介 OpenHarmony Codec HDI&#xff08;Hardware Device Interface&#xff09;驱动框架基于OpenMax实现了视频硬件编解码驱动&#xff0c;提供Codec基础能力接口给上层媒体服务调用&#xff0c;包括获取组件编解码能力、创建组件、参数设置、数据的轮转和控制、以及销毁…

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题1

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题1 信息安全管理与评估 网络系统管理 网络搭建与应用 云计算 软件测试 移动应用开发 任务书&#xff0c;赛题&#xff0c;解析等资料&#xff0c;知识点培训服务 添加博主wx&#xff1a;liuliu548…

【微信小程序】canvas开发笔记

【微信小程序】canvasToTempFilePath:fail fail canvas is empty 看说明书 最好是先看一下官方文档点此前往 如果是canvas 2d 写canvas: this.canvas,&#xff0c;如果是旧版写canvasId: ***, 解决问题 修改对应的代码&#xff0c;如下所示&#xff0c;然后再试试运行&#x…

Java 基于微信小程序的汽车4S店客户管理小程序,附源码

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

Python——详细解析目标检测xml格式标注转换为txt格式

本文简述了目标检测xml格式标注的内容&#xff0c;以及yolo系列模型所需的txt格式标注的内容。并提供了一个简单的&#xff0c;可以将xml格式标注文件转换为txt格式标注文件的python脚本。 1. xml格式文件内容 <size>标签下为图片信息&#xff0c;包括 <width> …

排序算法之选择排序

目录 一、简介二、代码实现三、应用场景 一、简介 算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度排序方式稳定性选择排序O(n^2 )O(n^2)O(n^2)O(1)In-place不稳定 稳定&#xff1a;如果A原本在B前面&#xff0c;而AB&#xff0c;排序之后A仍然在B的前面&#xff1…

RREA论文阅读

Relational Reflection Entity Alignment 关系反射实体对齐 ABSTRACT 实体对齐旨在识别来自不同知识图谱(KG)的等效实体对&#xff0c;这对于集成多源知识图谱至关重要。最近&#xff0c;随着 GNN 在实体对齐中的引入&#xff0c;近期模型的架构变得越来越复杂。我们甚至在这…

低频电磁仿真 | 新能源汽车性能提升的利器

永磁同步电机 新能源汽车的心脏 近年来&#xff0c;全球变暖的趋势日益加剧&#xff0c;极端天气事件层出不穷&#xff0c;这些现象都反映出当前气候形势的严峻性。为了应对这一全球性挑战&#xff0c;各国纷纷采取行动&#xff0c;制定了一系列降碳、减碳的措施。中国在2020年…

PostgreSQL15 + PostGis + QGIS安装教程

目录 下载1、PostgreSQL安装1.1、环境变量配置 2、PostGIS安装2.1、安装插件 3、QGIS下载3.1、安装3.2、测试 下载 PostgreSQL15安装&#xff1a;下载地址 PostGIS安装&#xff1a;下载地址&#xff08;倒数第二个&#xff09; 1、PostgreSQL安装 下载安装包之后一直点下一步…

MySQL中的SQL高级语句[二]

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 拖动表名到查询文件中就可以直接把名字拉进来以下是使用脚本方法&#xff0c;也可以直接进行修改中括号&#xff0c;就代表可写可不写 有些地方的代…

基于单片机的智能锁芯报警系统设计

摘 要:在传统的智能锁芯报警系统中,存在响应时间较长的问题,为此,提出一种基于单片机的智能锁芯报警系统。通过控制模块、智能锁芯设置模块、报警模块、中断模块、液晶模块等,建立系统总体框架,根据系统总体框架,通过单片机、电源适配器、智能锁芯、报警器、LED灯等…

CMake构建OpenCv并导入QT项目过程中出现的问题汇总

前言 再此之前请确保你的环境变量是否配置&#xff0c;这是总共需要配置的环境变量 E:\cmake\bin E:\OpenCv\opencv\build\x64\vc15\bin F:\Qt\Tools\mingw730_64\bin F:\Qt\5.12.4\mingw73_64\bin 问题一&#xff1a; CMake Error: CMake was unable to find a build program…