算法每日练 -- 双指针篇(持续更新中)

news2024/11/26 2:51:47

介绍:

        常见的双指针有两种形式,一种是对撞指针(左右指针),一种是快慢指针(前后指针)。需要注意这里的双指针不是 int* 之类的类型指针,而是使用数组下标模拟地址来进行遍历的方式。

对撞指针:

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

 快慢指针:

        快慢指针又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动。这种方法对于处理环形链表或数组非常有用。其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,都可以考虑使用快慢指针的思想。

练习

1、移动零

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

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

提示:

  • 1 <= nums.length <= 10^4
  • -2^31 <= nums[i] <= 2^31 - 1

 算法原理:

        本道题采用的是快排思想:数组分块的方法,数组分块是一种常见题型,主要是根据一种划分方式,将数组的内容分成左右两部分。这种类型的题目一般使用双指针来解决。

思路:

        在本题中,我们可以用一个 cur 来扫描整个数组,另一个 dest 来记录非零元素序列的最后一个位置。根据 cur 在扫描过程中,遇到的不同情况,分类处理,实现数组划分。在 cur 遍历期间,使[ 0, dest ]的元素全部都是非零元素,[ dest+1, cur-1 ]的元素全为0。

  1. 初始化 cur = 0(用来遍历数组),dest = -1(指向非零元素的最后一个位置)。因为刚开始我们不知道最后一个非零元素在什么位置,因此初始化为-1。
  2. cur 依次往后遍历元素,遍历到的元素会分为两种情况:
  • 遇到的元素值为0,此时 cur 直接 ++。因为我们的目标是让[ dest+1, cur-1 ]内的元素全部为0,因此当 cur 遇到 0 的时候,直接 ++,就可以让 0 在 cur-1 的位置上,从而保持在[ dest+1, cur-1 ]内。
  • 遇到的元素值不为0,此时 dest++,并且交换 cur 位置和 dest 位置的元素,之后让cur++,扫描下一个元素。

        因为 dest 指向的位置是非零元素区间的最后一个位置,如果扫描到一个新的非零元素,那么它的位置应该在 dest+1 的位置上,因此 dest 先++;

        dest++ 之后,指向的元素就是 0 元素(因为非零元素区间末尾最后一个元素就是0),因此可以交换到cur所处的位置上,实现[ 0, dest ]的元素全部都是非零元素,[ dest+1, cur-1 ]的元素都是0。

代码演示:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int cur = -1;
        int dest = 0;
        for(size_t dest = 0; dest<nums.size();dest++)
        {
            if(nums[dest] != 0)
            {
                cur++;
                std::swap(nums[cur], nums[dest]);
            }
        }
    }
};

2、复写零

        给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

提示:

  • 1 <= arr.length <= 10^4
  • 0 <= arr[i] <= 9

解题思路:(原地复写-双指针)

        如果从前向后进行原地复写操作的话,由于0的出现会复写两次,导致没有复写的数会被覆盖掉。因此我们选择从后往前的复写策略。

        而从后往前复写需要我们找到最后一个复写的元素,因此我们的思路可分为两步:

  1.  先找到最后一个复写的元素。
  2.  然后从后向前进行复写操作。

算法流程:

  • 初始化两个指针cur = 0, dest = 0;
  • 找到最后一个复写的元素:当 cur < n 时循环,判断cur位置的元素,如果为0,dest往后移动两位,否则移动一位。判断dest是否已经到结束位置,如果结束就break终止循环;如果没有结束,cur++,继续判断。
  • 判断dest是否越界:如果越界到n位置,执行(n-1位置元素值修改为0,cur向前移动一步,dest位置向前移动两步)。
  • 从cur位置开始往前遍历原数组,因此还原出复写后的结果数组:判断cur位置的值(如果是0,dest以及dest-1位置元素值修改为0,dest-=2;如果是非0,dest位置修改成非0值,dest-=1),cur--,复写下一个位置。

代码演示:

class Solution
{
public:
	void duplicateZeros(vector<int>& arr)
	{
		// 1. 先找到最后⼀个数
		int cur = 0, dest = -1, n = arr.size();
		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--;
			}
		}
	}
};

3、快乐数

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

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

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

提示:

  • 1 <= n <= 2^31 - 1

题目分析:

        为了方便叙述,我将 “对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和”这一操作记为 x 操作。题目告诉我们,当我们不断重复 x 操作时,计算会出现两种死循环方式:

  • 一种是一直在1中死循环。
  • 另一种是在历史计算的数据中死循环,但始终不会变为1。

        由于上述的两种情况只会出现一种,因此,我们只要能确定循环是在第一种情况还是第二种情况中进行,就可以得到结果。

证明:

  •   我们知道输入的元素最大值为2 ^ 31 - 1 = 2147483647,我们取更大的9999999999,则经过一次变化后的最大值为 9 ^ 2 * 10 = 810,也就是说变化的区间在[ 1, 810 ]之间。
  • 根据鸽巢原理,一个数变化811次之后,必然会形成一个循环;
  • 因此变化的过程最终会走到一个圈里,因此本题可以使用快慢指针解决。

算法思路:

        重复执行 x 操作后,数据会陷入到一个循环中。根据快慢指针的特性,在一个圈中,快指针总是会追上慢指针,也就是说,两个指针总是会在一个位置上相遇,如果相遇位置是1,那么这个数一定是快乐数;如果相遇位置不为1,那么就不是快乐数。

补充知识:如何求一个数n每个位置上的数字的平方和

  • 把数n的每一位提取出来:循环迭代(int t = n % 10;  n/=10取掉原来的个位),直到n变为0。
  • 提取每一位的时候,用一个变量tmp来记录这一位的平方与之前提取位数的平方和:tmp = tmp + t * t;

代码演示:

class Solution
{
public:
	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;
	}
};

 

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

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

相关文章

RHCE的学习(11)

第八章 防火墙 什么是防火墙 防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定义好的规则来控制数据包的进出。 分类&#xff1a; 硬件防火墙&#xff1a;由厂商设计好的主机硬件&#xff0c;其操作系统主要以提供数据包数据的过滤机制为主&#xff…

Java开发配置文件的详情教程配置文件类型

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

ffmpeg:视频字幕嵌入(GPU加速)

实现方案 参考指令 ffmpeg -i input_video.mp4 -vf "subtitlessubtitles.srt" output_video.mp4 解决因文件名称复杂导致的指令执行失败问题&#xff08;引号给文件框起来&#xff09; ffmpeg -i "A.mp4" -vf "subtitlesB.srt" "c.mp4&qu…

改进系列(3):基于ResNet网络与CBAM模块融合实现的生活垃圾分类

目录 1. ResNet介绍 2. CBAM 模块 3. resnet cbam 3.1 添加在每个layer层后 3.2 关于训练的建议 4. 垃圾分类实战 4.1 数据集 4.2 训练 4.3 最好的权重 4.4 推理 5. 其它 1. ResNet介绍 ResNet&#xff08;残差网络&#xff09;是一种深度卷积神经网络模型&#xf…

【HarmonyOS Next】数据本地存储:@ohos.data.preferences

【HarmonyOS Next】数据本地存储&#xff1a;ohos.data.preferences 在开发现代应用程序时&#xff0c;数据存储是一个至关重要的过程。应用程序为了保持某些用户设置、应用状态以及其他小量数据信息通常需要一个可靠的本地存储解决方案。在 HarmonyOS Next 环境下&#xff0c…

【从零开始的LeetCode-算法】3255. 长度为 K 的子数组的能量值 II

给你一个长度为 n 的整数数组 nums 和一个正整数 k 。 一个数组的 能量值 定义为&#xff1a; 如果 所有 元素都是依次 连续 且 上升 的&#xff0c;那么能量值为 最大 的元素。否则为 -1 。 你需要求出 nums 中所有长度为 k 的 子数组 的能量值。 请你返回一个长度为 n - …

C++ 参数传递 笔记

目录 1、输入参数的传递方式-选择传值还是传引用&#xff1f; 处理用户信息 处理坐标 处理配置 处理ID 2、对于需要修改的参数,使用非const引用传递 有趣的例外&#xff1a;警惕表象的迷惑 需要警惕的陷阱 “糟糕”的update方法&#xff1a; “完美”的set_name与set…

爬虫策略规避:Python爬虫的浏览器自动化

网络爬虫作为一种自动化获取网页数据的技术&#xff0c;被广泛应用于数据挖掘、市场分析、竞争情报等领域。然而&#xff0c;随着反爬虫技术的不断进步&#xff0c;简单的爬虫程序往往难以突破网站的反爬虫策略。因此&#xff0c;采用更高级的爬虫策略&#xff0c;如浏览器自动…

信号-3-信号处理、可重入函数与`volatile`关键字总结

信号处理是操作系统中的一个重要机制&#xff0c;它允许进程在运行期间响应外部事件&#xff0c;并作出相应的处理。为了处理信号&#xff0c;程序员需要理解如何设置信号处理器&#xff0c;如何管理信号的屏蔽与阻塞&#xff0c;以及信号的递送机制。本文将结合操作系统中的信…

深度学习-图像评分实验(TensorFlow框架运用、读取处理图片、模型建构)

目录 0、实验准备 ①实验环境 ②需要下载的安装包 ③注意事项&#xff08;很关键&#xff0c;否则后面内容看不懂&#xff09; ④容易出现的问题 1、查看数据并读取数据。 2、PIL库里的Image包进行读取&#xff08;.resize更改图片尺寸&#xff0c;并将原始数据归一化处…

【Linux系统】—— 基本指令(二)

【Linux系统】—— 基本指令&#xff08;二&#xff09; 1 「alias」命令1.1 「ll」命令1.2 「alias」命令 2 「rmdir」指令与「rm」指令2.1 「rmdir」2.2 「rm」2.2.1 「rm」 删除普通文件2.2.2 「rm」 删除目录2.2.3 『 * 』 通配符 3 「man」 指令4 「cp」 指令4.1 拷贝普通…

面向对象技术简述(含设计模式)

6.9.2 面向对象技术 面向对象 对象 分类 继承 通过消息的通信 面向对象 对象 分类 继承 通过消息的通信 面向对象对象分类继承通过消息的通信其中包括&#xff1a; 对象 运行的实体&#xff1b;既包含属性/数据&#xff0c;又包含方法/行为/操作数据的函数&#xff1b;…

【信号处理】基于联合图像表示的深度学习卷积神经网络

Combined Signal Representations for Modulation Classification Using Deep Learning: Ambiguity Function, Constellation Diagram, and Eye Diagram 信号表示 Ambiguity Function(AF) 模糊函数描述了信号的两个维度(dimensions):延迟(delay)和多普勒(Doppler)。 …

【C++练习】两个实型数的加法运算

题目&#xff1a; 编写一个C程序&#xff0c;实现两个实型数的加法运算并输出结果。 要求&#xff1a; 定义一个函数 dadd&#xff0c;该函数接收两个 double 类型的参数&#xff0c;并返回它们的和。在 main 函数中&#xff0c;提示用户输入两个实型数&#xff08;double 类…

【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 061 &#xff0c;文末自助获取源码 \color{red}{T061&#xff0c;文末自助获取源码} T061&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

Sql面试题二:请查询出用户连续三天登录的所有数据记录

问题&#xff1a; 现有用户登录记录表&#xff0c;请查询出用户连续三天登录的所有数据记录 id dt 1 2024-04-25 1 2024-04-26 1 2024-04-27 1 2024-04-28 1 2024-04-30 1 2024-05-01 1 2024-05-02 1 2024-05-04 1 2024-05-05 2 2…

科研绘图系列:R语言分组堆积图(grouped stacked barplot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包准备画图主题数据链接导入数据画图数据准备图例图1图2合并图系统信息介绍 分组堆积图(Grouped Stacked Bar Chart)是一种数据可视化图表,它结合了分组柱状图和堆积柱状图…

世界技能竞赛大数据应用开发环境1:1还原

集群情况 模块A搭建环境&#xff0c;在容器中搭建大数据平台 Pc机&#xff0c;安装安装比赛需要软件 模块B中使用idea快速开发完成数据处理 模块E包含了接口数据&#xff0c;使用vs code快速搭建vue数据可视化

【网络协议大花园】应用层 http协议的使用小技巧,用好了都不用加班,效率翻两倍(下篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

SpringBoot源码解析(一)

SpringBoot自动装配原理 SpringBootApplication注解 我们在使用SpringBoot时&#xff0c;通常使用的是SpringBootApplication这个注解&#xff0c;比如&#xff1a; 而这个注解的定义为下图&#xff0c;可以发现这个注解上有另外三个注解&#xff1a;SpringBootConfiguration…