【c++算法篇】双指针(上)

Alt

🔥个人主页Quitecoder

🔥专栏算法笔记仓

Alt

朋友们大家好啊,本篇文章我们来到算法的双指针部分

目录

  • `1.移动零`
  • `2.复写零`
  • `3.快乐数`
  • `4.盛水最多的容器`

1.移动零

题目链接:283.移动零
题目描述
在这里插入图片描述

算法原理

这里运用的是数据分块的原理,我们将这个数组分为三个部分

在这里插入图片描述
两个指针的作用

  • cur:从左往右扫描数组,遍历数组
  • dest:已处理的区间内,非零元素的最后一个位置

在这里插入图片描述
cur右边的部分是待处理的部分,左边是已经处理好的部分

处理好的区间,分为两个部分,左边为非零元素,右边全部为零,所以dest是一个分界线

在这里插入图片描述
所以划分为三个区间,[0,dest],[dest+1,cur-1],[cur,n-1],最左边全为非零元素,中间部分为0,右边为待处理元素,当cur指针移动到n为止时,区间划分完毕

代码如下:

class Solution {
public:void moveZeroes(vector<int>& nums) {int dest = -1;  // 初始化 dest 为 -1,表示还没有遇到非零元素for (int cur = 0; cur < nums.size(); ++cur) {if (nums[cur] != 0)swap(nums[++dest],nums[cur]);}}
};

对于示例一:[0,1,0,3,12],详细过程如下:

  1. dest 初始化为 -1,表示当前还没有处理任何非零元素

  2. 开始遍历数组 nums,使用变量 cur 从索引 0 开始:

    • cur = 0nums[cur]0。由于是零值,它不与 dest + 1 交换。dest 保持 -1。数组不变,仍然是 [0,1,0,3,12]

    • cur = 1nums[cur]1,非零。由于 dest = -1,先执行 ++dest (现在 dest0),然后把 nums[cur] (1) 和 nums[dest] (0) 交换位置。数组变为 [1,0,0,3,12]

    • cur = 2nums[cur]0。由于是零值,它不与 dest + 1 交换。dest 保持 0。数组不变

    • cur = 3nums[cur]3,非零。dest 递增为 1,然后将 nums[cur] (3) 和 nums[dest] (0) 交换位置。数组变为 [1,3,0,0,12]

    • cur = 4nums[cur]12,非零。dest 递增为 2,然后将 nums[cur] (12) 和 nums[dest] (0) 交换位置。数组变为 [1,3,12,0,0]

  3. 完成遍历后,所有非零数 [1, 3, 12] 都位于数组的前端,并且它们的相对顺序保持不变。所有的零都被移动到了数组末尾 [0,0]

指针 dest跟踪最后一个找到的非零元素的位置,每次找到非零元素时,就把这个元素交换到 dest 现在的位置。这样一来,所有的零都会被替换到交换过非零元素位置的后面

2.复写零

题目链接:1089.复写零
题目描述
在这里插入图片描述

在这里插入图片描述

遇到0写两遍,不能越界

算法原理

双指针算法,先根据异地操作,然后优化成双指针下的就地操作

我们正面复写的话会覆盖掉需要继续读取的数,所以这道题我们采用反向复写

我们第一步,首先找到最后一个要被复写的数

int dest = -1, cur = 0, n = arr.size();while (cur < n) {if (arr[cur] != 0) dest++;else dest += 2;if (dest >= n - 1) break;cur++;}

while 循环的目的是遍历数组 arr 来判断如果按照题目要求复写零(每个0都复写一次),最终数组中最后一个会被复写的元素是什么。这里,变量 dest 用来估计在复写零后数组可能会达到的索引位置,而变量 cur 是当前正在遍历的原数组中的元素的索引

具体逻辑如下:

  1. 初始化两个变量:curdestcur 从索引 0 开始向数组 arr 的末端移动,而 dest 初始化为 -1以适应首次遇到的元素是零的情况

  2. 遍历数组,逐项检查每个元素。

    • 如果当前元素 arr[cur] 是非零的,那么在复写过程中,该元素将向右移动一个位置,所以 dest 自增1(dest++
    • 如果当前元素 arr[cur] 是零,那么在复写过程中,两个零将分别占据 destdest+1 的位置,因此 dest 需要增加2(dest += 2
  3. 如果 dest 的值达到或超过 n - 1,这更正地表示数组在复写零之后达到或超出它的最大容量索引 n - 1(因为数组索引从0开始,所以最大索引是 n-1)。这时,循环停止,并使我们知道最后一个将被复写的原始数组中的数字和复写零后它的索引位置

  4. 在循环的最后,如果 dest 等于 nn-1,则表明最后一个0恰好处在数组的最后一个位置或倒数第二个位置,并且它将被复写。如果 dest 大于 n,最后一个0将不会被复写。

这个逻辑假设所有0都将被复写一次,然而,如果数组的空间不够,某些0可能不会被复写。这就是代码中 dest 可能会超过数组实际长度的情况。最终,cur 还原为最后可复写的元素索引,这样我们就能在下一步的逻辑中从此索引处向前开始复写和移动元素。

对于上面某些零不能复写的情况:

if (dest == n) {
arr[n - 1] = 0;
cur--; dest -= 2;
}

处理一种特殊情况,即当最后一个被处理的元素正好是 0,并且这个 0 被加倍复写后,计算的 dest 索引正好等于数组 arr 的长度 n。由于数组索引是从 0 开始的,有效的最大索引实际上是 n-1。在这种情况下,最后一个 0 只能复写一次(因为在其后已经没有空间放置第二个复写的 0),所以数组最后一个元素必须是 0

这样处理以后的操作需要考虑的几个点是:

  1. 由于 dest == n,所以 arr[n - 1] = 0; 这一行确保了数组的最后一个位置被设置为 0(符合复写操作的预期)。
  2. cur 递减的原因是在逆向复写过程中我们会跳过这个 0,因为它已经被复写并放置在了正确的位置。
  3. dest -= 2; 是因为 dest 原来是准备复写两个 0 的,但现在我们知道只能复写一个,所以我们递减两次 dest(将其回退到还未复写这个 0 的位置)

处理完后,完成剩余的复写即可:

while (cur >= 0) {if (arr[cur] != 0) arr[dest--] = arr[cur--];else {arr[dest--] = 0;if (dest >= 0) {  arr[dest--] = 0;}cur--;}
}

需要注意几个细节:

  1. 边界检查:在复写零的过程中,当 arr[cur]0 时,该代码块将连续两次将 0 写入 dest 指向的位置和它前一个位置(dest - 1)。在进行第二次写入之前,需要检查 dest >= 0 以确保不会对数组进行越界写入

  2. 双重减量:在处理零元素时,dest 指针需要减少两次,因为我们正在复写两个 0(前提是 dest >= 0),cur 只需要减少一次,因为我们只处理了一个 0

  3. 非零元素的移动:如果当前元素 arr[cur] 是非零元素,它只需要移动到 dest 指向的位置,并且 curdest 各自减一次。

  4. 处理数组开头的0:对于数组开头连续的零,它们在复写后可能只有有限的空间,所以对索引 dest 进行边界检查就显得尤为重要。例如,数组 [0,0,1,...] 开头的两个零,当 dest0 时,第二个零只会被复写一次

完整代码如下:

class Solution {
public:void duplicateZeros(vector<int>& arr) {int dest = -1, cur = 0, n = arr.size();while (cur < n) {if (arr[cur] != 0) dest++;else dest += 2;if (dest >= n - 1) break;cur++;}if (dest == n) {arr[n - 1] = 0;cur--; dest -= 2;}while (cur >= 0) {if (arr[cur] != 0) arr[dest--] = arr[cur--];else {arr[dest--] = 0;if (dest >= 0) {  arr[dest--] = 0;}cur--;}}}
};

3.快乐数

题目链接:202.快乐数
题目描述在这里插入图片描述

对于2这个数,最后会进入循环
在这里插入图片描述
对于快乐数,最后也可以当做进入循环,不过循环都是1这里与我们链表是否有环就思路相似了,当快慢指针相遇,判断是否为1即可

如果不是快乐数,它一定会进入一个循环

我们来系统地推导为什么一个不是快乐数的数最终会进入循环。这个推导包括分析数字变化的过程以及如何必然导致循环。下面是详细的步骤:

  1. : 定义快乐数的操作
    快乐数的操作定义为:对一个正整数,重复执行将该数替换为其各位数字的平方和的过程。例如,对于数字 19:

    • (1^2 + 9^2 = 82)
    • 继续对 82 操作,(8^2 + 2^2 = 68),以此类推。
  2. : 分析结果的可能性
    在每一步操作中,一个数将被转换为其各位数字的平方和。因此,我们可以观察到:

    • 这一操作将数字转换为一个新的数,其最大值取决于原数字的位数。例如,四位数的最大平方和为 (92 * 4 = 324)。
    • 随着操作的进行,如果数字不立即收敛到1,它们会逐渐降低到一个更小的范围
  3. : 有限状态和抽屉原理
    因为每步操作后的数字大小有上限,并且数字的总数是有限的(如最大999的平方和也只有243),所以可以推断状态空间(即可能的数字)是有限的。考虑到这个操作是重复执行的

    • 根据抽屉原理(Pigeonhole Principle),如果你有更多的项(这里是操作次数)比抽屉(可能的数字结果)多,至少有一个抽屉必须包含不止一个项。这意味着至 少有一个数字会被重复
    • 一旦一个数字在操作过程中重复出现,后续的操作将重复之前的操作,从而形成一个循环

所以我们先完成每一个位平方和的函数:

int bitsum(int n)
{int sum=0;while(n){int t=n%10;sum+=t*t;n/=10;}return sum;
}

接着完成主要函数,slow一次走一步,fast一次走两步,直到相遇:

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

最后判断相遇位置是否为1即可

4.盛水最多的容器

题目链接:11.盛水最多的容器
题目描述在这里插入图片描述

要解决这个问题,我们使用双指针的方法。一开始,我们将一个指针放在数组的最左边(即 left 指向索引 0),另一个指针放在数组的最右边(即 right 指向索引 n-1)。然后,我们计算由这两个指针指向的线和 x 轴构成的长方形的面积,并尝试找出能够获得更大面积的线对

具体地说,我们将指针向对方移动,并在每一步更新最大面积。由于容器的宽度随着指针的移动而减小,所以为了有可能增加面积,我们只移动指向较短线的指针(因为如果移动指向较长线的指针,面积只会减小或不变)。

实现 maxArea 函数,代码如下:

class Solution {
public:int maxArea(vector<int>& height) {int max_area = 0; // 存储最大面积int left = 0;     // 左指针int right = height.size() - 1; // 右指针while (left < right) {// 计算当前指针所围成的面积int current_area = (right - left) * min(height[left], height[right]);// 更新最大面积max_area = max(max_area, current_area);// 移动指向较短线的指针if (height[left] < height[right]) {left++;} else {right--;}}return max_area; // 返回最大面积}
};

这个解法的时间复杂度为 O(n),因为每个元素只被访问了一次。当 left 指针和 right 指针相遇时,所有可能的容器都已经检查过了。这是一个优化解法,它避免了 O(n2) 的暴力解法,后者需要检查所有可能的线对

本节内容到此结束!!感谢阅读!

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

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

相关文章

【Linux】进程控制 之 进程创建 进程终止 进程等待 进程替换

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

A股上市公司财务松弛数据集(2000-2022年)

01、数据介绍 财务松弛是指企业在运营过程中&#xff0c;由于各种原因导致其财务状况出现一定程度的松弛或宽裕状态。这种状态通常表现为企业持有较多的现金和流动性资产&#xff0c;同时负债相对较少&#xff0c;或者企业有较多的未使用授信额度等。 本数据包括&#xff1a;…

【LeetCode】链表oj专题

前言 经过前面的学习&#xff0c;咋们已经学完了链表相关知识&#xff0c;这时候不妨来几道链表算法题来巩固一下吧&#xff01; 如果有不懂的可翻阅之前文章哦&#xff01; 个人主页&#xff1a;小八哥向前冲~-CSDN博客 数据结构专栏&#xff1a;数据结构【c语言版】_小八哥…

【管理篇】如何处理团队里的老资格员工和高能力员工?

目录标题 两类员工对比&#x1f93a;老资格员工高能力员工 作为领导你应该怎么做&#xff1f; 在管理团队时&#xff0c;处理老资格员工和高能力员工是一项至关重要的任务。这两类员工在团队中扮演着不同的角色和有着不同的需求&#xff0c;因此需要针对性的管理和激励。下面将…

漫谈音频深度伪造技术

作为人工智能时代的新型媒体合成技术&#xff0c;深度伪造技术近年来在网络媒体中的涉及领域越发广泛、出现频次越发频繁。据路透社报道&#xff0c;2023年&#xff0c;社交媒体网站上发布50万个深度伪造的语音和视频。 1、深度伪造技术的五个方面 音频深度伪造技术&#xff…

Java八股文3

3.垃圾回收 1.对象什么时候可以被垃圾器回收 1.垃圾回收的概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c; 在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)…

Unity 性能优化之静态批处理(三)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、静态批处理是什么&#xff1f;二、使用步骤1.勾选Static Batching2.测试静态合批效果 三、静态合批得限制1、游戏对象处于激活状态。2、游戏对象有一…

CMakeLists.txt语法规则:条件判断说明一

一. 简介 前面学习了 CMakeLists.txt语法中的 部分常用命令&#xff0c;常量变量&#xff0c;双引号的使用。 本文继续学习 CMakeLists.txt语法中的条件判断。 二. CMakeLists.txt 语法规则&#xff1a;条件判断 在 cmake 中可以使用条件判断&#xff0c;条件判断形式如下…

STM32 01

1、编码环境 1.1 安装keil5 1.2 安装STM32CubeMX 使用STM32CubeMX可以通过界面的方式&#xff0c;快速生成工程文件 安装包可以从官网下载&#xff1a;https://www.st.com/zh/development-tools/stm32cubemx.html#overview 安装完要注意更新一下固件包的位置&#xff0c;因为…

vivado 在硬件中调试串行 I/O 设计-属性窗口

只要在“硬件 (Hardware) ”窗口中选中 GT 或 COMMON 块、在“链接 (Link) ”窗口中选中链接 &#xff0c; 或者在“扫描 (Scan)”窗口中选中扫描 &#xff0c; 那么就会在“ Properties ”窗口中显示该对象的属性。对于 GT 和 COMMON &#xff0c; 包括这些对象的所有属性、…

电商日志项目(一)

电商日志项目 一、项目体系架构设计1. 项目系统架构2. 项目数据流程二、环境搭建1. NginxLog文件服务1.1. 上传,解压1.2. 编译安装1.3. 启动验证2. Flume-ng2.1. 上传解压2.2. 修改配置文件2.3. 修改环境变量2.4. 验证3. Sqoop3.1. 上传解压3.2. 配置环境变量3.3. 修改配置文件…

如何进行Go语言的性能测试和调优?

文章目录 开篇一、性能测试1. 使用标准库中的testing包2. 使用第三方工具 二、性能调优1. 优化算法和数据结构2. 减少不必要的内存分配和垃圾回收3. 并发和并行 结尾 开篇 Go语言以其出色的性能和简洁的语法受到了广大开发者的喜爱。然而&#xff0c;在实际开发中&#xff0c;…

微服务架构与单体架构

微服务架构与与单体架构比较 微服务架构是一种将应用程序作为一组小的、独立服务的系统架构风格&#xff0c;每个服务运行在其自己的进程中&#xff0c;并通常围绕业务能力组织。这些服务通过定义良好且轻量级的机制&#xff08;通常是HTTP REST API&#xff09;进行通信。微服…

Redis(基础指令和五大数据类型)

文章目录 1.基本介绍1.多种数据结构支持2.应用场景 2.Redis安装&#xff08;直接安装到云服务器&#xff09;1.安装gcc1.yum安装gcc2.查看gcc版本 2.将redis6.2.6上传到/opt目录下3.进入/opt目录下然后解压4.进入 redis-6.2.6目录5.编译并安装6.进入 /usr/local/bin 查看是否有…

智慧文旅开启沉浸式文化体验,科技让旅行更生动:借助智慧技术,打造沉浸式文化体验场景,让旅行者在旅行中深度感受文化的魅力

一、引言 随着科技的飞速发展&#xff0c;传统旅游行业正经历着前所未有的变革。智慧文旅&#xff0c;作为一种新兴的旅游模式&#xff0c;正以其独特的魅力&#xff0c;吸引着越来越多的旅行者。智慧文旅不仅改变了人们的旅行方式&#xff0c;更在深度上丰富了人们的文化体验…

Spring入门及注解开发

1 引言 自定义注解可以用来为代码添加元数据信息,简化配置,提高代码的可读性和可维护性。通过自定义注解,可以实现自定义的业务逻辑、约束条件、配置参数等功能。在Spring中,自定义注解常用于标记组件、配置依赖注入、AOP切面等。 自定义注解可以添加元数据信息,低代码框…

关于图形库

文章目录 1. 概念介绍2. 使用方法2.1 普通路由2.2 命名路由 3. 示例代码4. 内容总结 我们在上一章回中介绍了"使用get显示Dialog"相关的内容&#xff0c;本章回中将介绍使用get进行路由管理.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

AEC Capital Limited:开启可持续金融新纪元

在当今社会&#xff0c;环保和可持续发展已成为全球关注的焦点。在这个背景下&#xff0c;AEC Capital Limited作为香港的一家金融服务公司&#xff0c;以其专业、高端的服务和创新的理念&#xff0c;成为可持续金融领域的引领者。我们致力于将环境保护与金融服务相结合&#x…

观测与预测差值自动变化系统噪声Q的自适应UKF(AUKF_Q)MATLAB编写

简述 基于三维模型的UKF&#xff0c;设计一段时间的输入状态误差较大&#xff0c;此时通过对比预测的状态值与观测值的残差&#xff0c;在相应的情况下自适应扩大系统方差Q&#xff0c;构成自适应无迹卡尔曼滤波&#xff08;AUKF&#xff09;&#xff0c;与传统的UKF相比&…

mac监听 linux服务器可视化(Grafana+Promethus+Node_exporter)

Grafana和promethus(普罗米修斯)的安装和使用 监控系统的Prometheus类似于一个注册中心&#xff0c;我们可以只需要配置一个Prometheus,而在其他服务器&#xff0c;只需要安装node_exporter,它们的数据流转就是通过exporter采集数据信息&#xff0c;然后告诉prometheus它的位置…