【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.mfbz.cn/a/594197.html

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

相关文章

Python量化炒股的数据信息获取—获取上市公司分红送股数据信息

Python量化炒股的数据信息获取—获取上市公司分红送股数据信息 上市公司分红送股数据&#xff0c;都存放在STK_XR_XD表中&#xff0c;该表保存在finance包中。要查看表中的数据信息&#xff0c;需要使用query()函数。 单击聚宽JoinQuant量化炒股平台中的“策略研究/研究环境”…

微服务---gateway网关

目录 gateway作用 gateway使用 添加依赖 配置yml文件 自定义过滤器 nacos上的gateway的配置文件 我们现在知道了通过nacos注册服务&#xff0c;通过feign实现服务间接口的调用&#xff0c;那对于不同权限的用户访问同一个接口&#xff0c;我们怎么知道他是否具有访问的权…

Grafana:云原生时代的数据可视化与监控王者

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Grafana&#xff1a;让数据说话的魔术师》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Grafana简介 2、Grafana的重要性与影响力 …

开发体育赛事直播平台,研发技术选型与架构设计实现方案

本文将深入探讨“东莞梦幻网络科技”现成体育直播源码的技术实现方案&#xff0c;如何为用户提供流畅、互动、个性化的观赛体验。 一、技术栈选择&#xff1a;强强联合的基石1、后端开发&#xff1a;采用Java与PHP作为主要开发语言。Java以其强大的企业级应用支持&#xff0c;保…

双向冒泡法,可以只求最大最小值

int BiBubbleSort(int Arr[],int n,int maxnum){int left0,rightn-1;int i;bool notDone true;int temp;if(n<2)return -1;while(left<right&&notDone){ notDone false; //设置未发生交换标志 for(ileft;i<right;i){if(Arr[i]>Arr[i1]){//swap(Arr[…

初识指针(1)<C语言>

前言 指针是C语言中比较难的一部分&#xff0c;大部分同学对于此部分容易产生“畏难情结”&#xff0c;但是学习好这部分对C语言的深入很大的帮助&#xff0c;所以此篇主要以讲解指针基础为主。 指针概念 变量创建的本质就是在内存中申请空间&#xff0c;找到这个变量就需要地址…

tiny-Tcmalloc(高并发内存池)

项目地址&#xff08;绝对可运行&#xff09; 一. 初识高并发内存池 1、项目介绍 当前项目是实现一个高并发的内存池&#xff0c;它的原型是google的一个开源项目tcmalloc&#xff0c;tcmalloc全称Thread-Caching Malloc&#xff0c;即线程缓存的malloc&#xff0c;实现了高…

Pytorch 实现 GAN 对抗网络

GAN 对抗网络 GAN&#xff08;Generative Adversarial Network&#xff09;对抗网络指的是神经网络中包括两个子网络&#xff0c;一个用于生成信息&#xff0c;一个用于验证信息。下面的例子是生成图片的对抗网络&#xff0c;一个网络用于生成图片&#xff0c;另一个网络用于验…

Bookends for Mac:文献管理工具

Bookends for Mac&#xff0c;一款专为学术、研究和写作领域设计的文献管理工具&#xff0c;以其强大而高效的功能深受用户喜爱。这款软件支持多种文件格式&#xff0c;如PDF、DOC、RTF等&#xff0c;能够自动提取文献的关键信息&#xff0c;如作者、标题、出版社等&#xff0c…

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

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

Kannala-Brandt 鱼眼相机模型

最近在学习 ORB-SLAM3 的源代码&#xff0c;并模仿、重构了相机模型的实现 在学习的过程中发现针孔相机 (Pinhole) 与鱼眼相机 (Fisheye) 都有畸变参数&#xff0c;但是鱼眼相机无法使用 cv::undistort 函数去畸变 在对鱼眼相机的深度归一化平面进行可视化后&#xff0c;发现…

CNN实现卫星图像分类(tensorflow)

使用的数据集卫星图像有两类&#xff0c;airplane和lake&#xff0c;每个类别样本量各700张&#xff0c;大小为256*256&#xff0c;RGB三通道彩色卫星影像。搭建深度卷积神经网络&#xff0c;实现卫星影像二分类。 数据链接百度网盘地址&#xff0c;提取码: cq47 1、查看tenso…

swift微调多模态大语言模型

微调训练数据集指定方式的问题请教 Issue #813 modelscope/swift GitHubQwen1.5微调训练脚本中&#xff0c;我用到了--dataset new_data.jsonl 这个选项&#xff0c; 可以训练成功&#xff0c;但我看文档有提到--custom_train_dataset_path这个选项&#xff0c;这两个有什么…

C语言中字符串输入的3种方式

Ⅰ gets() 函数 gets() 函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间 # include <stdio.h> int main(void) {char a[256] {0};gets(a);printf("%s",a);return 0; }Ⅱ getchar() # include <stdio.h> int mai…

「 网络安全常用术语解读 」通用安全通告框架CSAF详解

1. 简介 通用安全通告框架&#xff08;Common Security Advisory Framework&#xff0c;CSAF&#xff09;通过标准化结构化机器可读安全咨询的创建和分发&#xff0c;支持漏洞管理的自动化。CSAF是OASIS公开的官方标准。开发CSAF的技术委员会包括许多公共和私营部门的技术领导…

VsCode插件 -- Power Mode

一、安装插件 1. 首先在扩展市场里搜索 Power Mode 插件&#xff0c;如下图 二、配置插件 设置 点击小齿轮 打上勾 就可以了 第二种设置方法 1. 安装完成之后&#xff0c;使用快捷键 Ctrl Shift P 打开命令面板&#xff0c;在命令行中输入 settings.json &#xff0c; 选择首…

流畅的python-学习笔记_数据结构

概念 抽象基类&#xff1a;ABC, Abstract Base Class 序列 内置序列类型 分类 可分为容器类型和扁平类型 容器类型有list&#xff0c; tuple&#xff0c; collections.deque等&#xff0c;存储元素类型可不同&#xff0c;存储的元素也是内容的引用而非内容实际占用内存 …

.排序总讲.

在这里赘叙一下我对y总前四节所讲排序的分治思想以及递归的深度理解。 就以788.逆序对 这一题来讲&#xff08;我认为这一题对于分治和递归的思想体现的淋淋尽致&#xff09;。 题目&#xff1a; 给定一个长度为 n&#x1d45b; 的整数数列&#xff0c;请你计算数列中的逆序对…

易语言IDE界面美化支持库

易语言IDE界面美化支持库 下载下来可以看到&#xff0c;是一个压缩包。 那么&#xff0c;怎么安装到易语言中呢&#xff1f; 解压之后&#xff0c;得到这两个文件。 直接将clr和lib丢到易语言安装目录中&#xff0c;这样子就安装完成了。 打开易语言&#xff0c;在支持库配置…

C#-快速剖析文件和流,并使用(持续更新)

目录 一、概述 二、文件系统 1、检查驱动器信息 2、Path 3、文件和文件夹 三、流 1、FileStream 2、StreamWriter与StreamReader 3、BinaryWriter与BinaryReader 一、概述 文件&#xff0c;具有永久存储及特定顺序的字节组成的一个有序、具有名称的集合&#xff1b; …
最新文章