优选算法——双指针1

news2025/1/21 21:53:56

双指针

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

对撞指针:⼀般⽤于顺序结构中,也称左右指针。

  1. 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼 近。
  2. 对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循 环),也就是:
  • left == right (两个指针指向同⼀个位置)
  • left > right (两个指针错开)

快慢指针:⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列 结构上移动。

 这种⽅法对于处理环形链表或数组⾮常有⽤。

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

快慢指针的实现⽅式有很多种,最常⽤的⼀种就是: 

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

注:双指针只是一种思想,我们不一定真的要定义指针来实现双指针,我们可以使用特定的数值来表示指针,比如说数组的下标,某元素的值

第一题——移动零

「数组分两块」是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部 分。这种类型的题,⼀般就是使⽤「双指针」来解决。

题目来源. - 力扣(LeetCode) 

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

在本题中,我们可以⽤⼀个 cur 指针来扫描整个数组,另⼀个 dest 指针⽤来记录⾮零数序列的最后一个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。

在 cur 遍历期间,使 [0, dest] 的元素全部都是⾮零元素, [dest + 1, cur - 1] 的 元素全是零。

那我们要怎么做呢?很简单

我们直接看例子

我们发现1——5步均符合下面这个区间的要求

 代码

void swap(int*a,int*b)
{
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
void moveZeroes(int* nums, int numsSize) {
         for(int cur = 0, dest = -1; cur < numsSize; cur++)
                if(nums[cur]) // 处理⾮零元素
                 swap(&nums[++dest], &nums[cur]);
    
}

第二题——复写零

题目来源:. - 力扣(LeetCode)

我们先审题,它这个题目到底要我们干什么,我们看下面这个图就明白了

假设上面这个是传入的数组,下面这个就是完成复写零的数组

思路:

来看看另外开辟一个数组的做法 

 把异地操作模拟成就地操作

我们先看看从前往后的双指针法行不行

显然不行

我们再看看从后往前的方式 

完全可以嘿!那我们怎么找最后一个被复写的数呢?我们还是用双指针去找

 

 但是,这个算法还不健全,它在下面这个情况会发生越界访问

代码:

void duplicateZeros(int* arr, int arrSize) {

    // 1. 先找到最后⼀个数
    int cur = 0, dest = -1, n = arrSize;
    while (cur < n)
    {
        if (arr[cur]) 
            dest++;
        else 
            dest += 2;
        if (dest >= n - 1)
            break;
        cur++;
    }
    
    // 2. 处理⼀下边界情况
    if (dest == n)
    {
        arr[n - 1] = 0;
        cur--; dest -= 2;
    }
    
    // 3. 从后向前完成复写操作
    while (cur >= 0)
    {
        if (arr[cur]) 
            arr[dest--] = arr[cur--];
        else
        {
            arr[dest--] = 0;
            arr[dest--] = 0;
            cur--;
        }
    }
}

第三题——快乐数

题目来源:. - 力扣(LeetCode)

 思路

先审题

要么变到一,要么循环,我们直接把它抽象成第一种情况为最后是形成所有元素都是一的环,第二种情况是所有元素都不是1的环

这不就是判断链表里是不是有环的问题吗?

 有人就想了,我们怎么定义指针?

这里我们就要注意了,双指针只是一种思想,我们不一定真的要定义指针来实现双指针,我们可以使用特定的数值来表示指针,比如说数组的下标,但是在这里我们可以用数字来表示指针

我们先看slow指针

fast指针也是同理

我们就抽象成下面这个 

 

有人就想,万一它一直不成环,一直无限循环下去怎么办?

 但是,这是不可能的

我们来证明一下

我们直接取10个9,这是超过题目范围的,而且这个值产生拆分后的数一定是超过题目的 

 所以假设题目的数的范围和这个一样大,那它的取值范围是【1,810】,也就是说在这个数值范围产生的拆分后的数值一定是在【1,810】的,也就是说它最多循环810次,到811次之后一定会产生重复的值的,所以一直循环的情况不存在

根据上述的题⽬分析,我们可以知道,当重复执⾏ x 的时候,数据会陷⼊到⼀个「循环」之中。 ⽽「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的,也就是说他们总会 相遇在⼀个位置上。如果相遇位置的值是 1 ,那么这个数⼀定是快乐数;如果相遇位置不是 1 的话,那么就不是快乐数。

补充知识:如何求⼀个数n每个位置上的数字的平⽅和。

a. 把数 n 每⼀位的数提取出来: 循环迭代下⾯步骤:

  • int t = n % 10 提取个位;
  • n /= 10 ⼲掉个位; 直到 n 的值变为 0 ;

b. 提取每⼀位的时候,⽤⼀个变量  tmp 记录这⼀位的平⽅与之前提取位数的平⽅和

tmp = tmp + t * t

代码

int bitSum(int n) // 返回n 这个数每⼀位上的平⽅和
{ 
        int sum = 0;
        while(n)
        {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    
}

bool isHappy(int n)     
{
        int slow = n, fast = bitSum(n);
        while(slow != fast)
        {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
        return slow == 1;
}

第四题——盛最多水的容器

题目来源:盛最多水的容器 

思路 

我们可以使用暴力枚举

但是这个会超时 

我们先拿一个数组来试试,取边界为墙,我们知道啊,容器的容积是取决于容器最矮的那一端,这里也是类似的,这里容器的容量取决于最小的——4,我们就想把4变成大点的数容器的容积不就上去了吗?于是我们向内枚举,情况如下

我们发现,我们取6为墙的时候,右边的数向左枚举,没有一个容器的容积比原来的大的

 我们再看看一个例子

算法思路: 设两个指针 left , right 分别指向容器的左右两个端点,此时容器的容积:

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

为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」。

如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:

  •  容器的宽度⼀定变⼩。
  •  由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是⼀定不会超 过右边的柱⼦⾼度,因此容器的容积可能会增⼤。
  • 如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会 超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。

由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继 续去判断下⼀个左右边界。 当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到 遇。期间产⽣的所有的容积⾥⾯的最⼤值,就是最终答案。 

class Solution {
public:

int maxArea(vector<int>& height)
{
    int left = 0, right = height.size() - 1, ret = 0;
    while (left < right)
    {
        int v = min(height[left], height[right]) * (right - left);
        ret = max(ret, v);
        // 移动指针
            if (height[left] < height[right]) 
                left++;
            else 
                right--;
    }
    return ret;
}
};

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

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

相关文章

Linux提权--Rsync(未授权访问) Docker 组挂载

免责声明:本文仅做技术学习与交流... 目录 Rsync&#xff08;未授权访问&#xff09; 介绍: 靶场及过程: 提权过程&#xff1a; Docker 组挂载 原理: 复现&#xff1a; 利用&#xff1a; 具体操作: 1-确定是否有docker服务 2-查看用户是否在docker组里面 3-执行命…

Raft论文阅读笔记+翻译:In Search of Understandable Consensus Algorithm

In Search of Understandable Consensus Algorithm 摘要 Raft是一种管理复制日志的共识算法。它产生与&#xff08;多&#xff09;Paxos等效的结果&#xff0c;并且与Paxos一样高效&#xff0c;但其结构与Paxos不同。这使得Raft比Paxos更易理解&#xff0c;也为构建实际系统提供…

面试官:假如有几十个请求,如何去控制并发?

控制并发请求是一个重要的问题&#xff0c;特别是在面对高并发情况时&#xff0c;合理地管理请求可以有效地维护系统的稳定性和性能。以下是一些常见的方法来控制并发请求&#xff1a; 1. 线程池&#xff1a;使用线程池来管理并发请求&#xff0c;通过限制线程数量和队列大小&…

ComfyUI相见恨晚的提示词插件,简直堪称神器!

之前我曾介绍过一款专为SD设计的中文提示词插件——prompt-all-in-one&#xff0c;想必使用过的小伙伴们都已经感受到了它的便捷与实用吧。 不过&#xff0c;那款插件是基于webUI版本的&#xff0c;而现在&#xff0c;越来越多的朋友开始探索ComfyUI这一新选择。 假如在Comfy…

C++基础与深度解析 | 数组 | vector | string

文章目录 一、数组1.一维数组2.多维数组 二、vector三、string 一、数组 1.一维数组 在C中&#xff0c;数组用于存储具有相同类型和特定大小的元素集合。数组在内存中是连续存储的&#xff0c;并且支持通过索引快速访问元素。 数组的声明&#xff1a; 数组的声明指定了元素的…

virtualBox不能创建虚拟文件夹

问题如下图&#xff0c;在点击下一步时提示不能创建虚拟机文件夹 问题原因是使用了virtualBox的安装目录&#xff0c;在全局设定中设置虚拟电脑位置&#xff0c;不再使用virtualBox的安装目录 再次点击新建&#xff0c;就可以创建了。

无线网卡网络老断网

无线网卡网络老断网 设置 Intel AX210 无线网卡 路由器华为 AX3 问题及解决 问题 无线网卡连接到 wifi &#xff0c;连接不通&#xff0c;或者连接上后网络很慢&#xff0c;延时大&#xff0c;掉包。 解决方案 调整如下界面&#xff0c;调整信道后&#xff0c;连接正常。…

亚马逊卖家,如何打造爆款,如何提高产品权重、曝光、流量?

新老卖家们要知道&#xff0c;亚马逊A9算法影响产品排名的关键因素&#xff1a;产品相关性、销售排名、产品价格、点击率、转化率、产品图片、买家评论、买家满意度、QA的答复情况、搜索结果页详细信息级别。亚马逊A9算法&#xff0c;是根据卖家提供的listing文案信息进行收录、…

常见加解密算法02 - RC4算法分析

RC4是一种广泛使用的流密码&#xff0c;它以其简洁和速度而闻名。区别于块密码&#xff0c;流密码特点在于按位或按字节来进行加密。 RC4由Ron Rivest在1987年设计&#xff0c;尽管它的命名看起来是第四版&#xff0c;实际上它是第一个对外发布的版本。 RC4算法的实施过程简洁…

动态el-form表单以及动态禁用

当右侧下拉框选中为 长期有效,那么左侧输入框为禁用状态; <el-form-item label"证明有效期" class"is-required"><div v-for"(item,index) in form.arrayDat" :key"index" style"width: 100%;display: flex;justify-co…

深度解读《深度探索C++对象模型》之虚继承的实现分析和效率评测(二)

目录 通过子类的指针存取虚基类成员的实现分析 通过第一基类的指针存取虚基类成员的实现分析 通过第二基类的指针存取虚基类成员的实现分析 通过虚基类的指针存取虚基类成员的实现分析 小结 存取虚基类成员与普通类成员的效率对比 接下来我将持续更新“深度解读《深度探索…

WS2812B-2020 智能控制LED集成光源芯片IC

一般说明 WS2812B-2020是一款智能控制LED光源&#xff0c;它的外部采用了最新的模压封装技术&#xff0c;控制电路和RGB芯片集成在一个2020组件中。其内部包括智能数字端口数据锁存器和信号整形放大驱动电路。还包括一个精密的内部振荡器和一个电压可编程恒流控制部分&…

对关系型数据库管理系统的介绍

1.数据库的相关介绍 关系型数据库管理系统&#xff1a;&#xff08;英文简称&#xff1a;RDBMS&#xff09; 为我们提供了一种存储数据的特定格式&#xff0c;所谓的数据格式就是表&#xff0c; 在数据库中一张表就称为是一种关系. 在关系型数据库中表由两部分组成&#xf…

嵌入式和单片机的区别在哪?

嵌入式和单片机是两个不同的概念&#xff0c;它们在很多方面都存在着差异。嵌入式系统是一种专用的计算机系统&#xff0c;通常用于控制和监测其他设备。它通常由微处理器、存储器、输入/输出接口和其他外围设备组成。嵌入式系统可以运行各种操作系统&#xff0c;如 Linux、Win…

TCP/UDP通信中的部分函数

UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;和TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网协议套件中最常用的两种传输层协议&#xff0c;它们负责在互联网中端到端地传输数据。尽管它们服务…

web网页录音(recorder.js)并上传后端语音转文字(Vosk)

我是一个后端开发人员&#xff0c;现在都快进化成全栈了。操了&#xff0c;是谁有好的项目让我跳跳槽&#xff0c;转转行吧 写在前面&#xff0c;很重要 这是官方文档的说明 翻译如下&#xff1a; 我们有两种型号-大型号和小型号&#xff0c;小型号非常适合在移动应用程序上执…

IT行业现状与未来趋势分析

IT行业现状与未来趋势显示出持续的活力和变革&#xff0c;以下是上大学网&#xff08;www.sdaxue.com&#xff09;关于IT行业现状与未来趋势分析&#xff0c;供大家参考。 当前现状&#xff1a; 市场需求持续增长&#xff1a;随着信息时代的深入发展&#xff0c;各行各业对信息…

一条查询SQL的执行过程

1.1 假设 查询语句为&#xff1a;mysql> select * from T where ID 10 1.2 总体执行流程 1.2.1 连接器 -> 连接 作用&#xff1a;负责跟客户端建立连接、获取权限、维持和管理连接等工作流程&#xff1a; 一个用户成功建立连接后&#xff0c;如果客户端太长时间没有请…

跨ROS系统通信:使用TCP实现节点间的直连

当涉及到在机器人操作系统&#xff08;ROS&#xff09;环境中的通信时&#xff0c;标准做法通常是在同一个ROS网络内通过话题和服务进行。但在某些特定情况下&#xff0c;比如当你有两个分布在不同网络中的ROS系统时&#xff0c;标准的通信方法可能不太适用。此时&#xff0c;一…

SpringBoot集成Seata分布式事务OpenFeign远程调用

Docker Desktop 安装Seata Server seata 本质上是一个服务&#xff0c;用docker安装更方便&#xff0c;配置默认&#xff1a;file docker run -d --name seata-server -p 8091:8091 -p 7091:7091 seataio/seata-server:2.0.0与SpringBoot集成 表结构 项目目录 dynamic和dyna…