【算法专题--双指针算法】leetcode--283. 移动零、leetcode--1089. 复写零

news/2024/5/19 21:28:03 标签: 算法, leetcode, c++, 数据结构, 推荐算法

🍁你好,我是 RO-BERRY
📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

请添加图片描述


目录

  • 前言
  • 1. 移动零(easy)
  • 2. 解法(快排的思想:数组划分区间 - 数组分两块)
  • 3. 复写零(easy)
  • 4.解法(原地复写 - 双指针)


前言

双指针
常见的双指针有两种形式,一种是对撞指针,⼀种是左右指针。
对撞指针:一般用于顺序结构中,也称左右指针。

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

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

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

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

1. 移动零(easy)

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

题目链接: 283. 移动零

题目描述:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示:

1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?


2. 解法(快排的思想:数组划分区间 - 数组分两块)

算法思路:

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

算法流程:

  1. 初始化 cur = 0 (用来遍历数组), dest = -1 (指向非零元素序列的最后⼀个位置。
    因为刚开始我们不知道最后⼀个非零元素在什么位置,因此初始化为 -1 )
  2. cur 依次往后遍历每个元素,遍历到的元素会有下面两种情况:

i. 遇到的元素是 0 , cur 直接 ++ 。因为我们的⽬标是让 [dest + 1, cur - 1] 内的元素全都是零,因此当 cur 遇到 0 的时候,直接 ++ ,就可以让 0 在 cur - 1的位置上,从而在 [dest + 1, cur - 1] 内;

ii. 遇到的元素不是 0 , dest++ ,并且交换 cur 位置和 dest 位置的元素,之后让cur++ ,扫描下⼀个元素。

  • 因为 dest 指向的位置是非零元素区间的最后⼀个位置,如果扫描到⼀个新的非零元素,那么它的位置应该在 dest + 1 的位置上,因此 dest 先自增 1 ;
  • dest++ 之后,指向的元素就是 0 元素(因为非零元素区间末尾的后⼀个元素就是0 ),因此可以交换到 cur 所处的位置上,实现 [0, dest] 的元素全部都是非零元素, [dest + 1, cur - 1] 的元素全是零。

在这里插入图片描述

C++ 算法代码:

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

C++ 代码结果:
在这里插入图片描述

Java 算法代码:

class Solution
{
	public void moveZeroes(int[] nums)
	{
		for (int cur = 0, dest = -1; cur < nums.length; cur++)
			if (nums[cur] != 0) // 仅需处理⾮零元素
			{
				dest++; // dest 先向后移动⼀位
				// 交换
				int tmp = nums[cur];
				nums[cur] = nums[dest];
				nums[dest] = tmp;
			}
	}
}

3. 复写零(easy)

题目链接: 1089. 复写零
题目描述:

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

示例 1:

输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]

提示:

1 <= arr.length <= 104
0 <= arr[i] <= 9

4.解法(原地复写 - 双指针)

确定目标位置:

  1. 首先,使用两个指针 dest 和 cur 来确定每个位置复制0后应该放置的位置。cur 指针用于遍历原始数组,而 dest 指针则用于确定在复制0后应该放置新元素的位置。cur 最终的位置就是复写后数组的最后一个数的位置。

如果 arr[cur] 不为0,那么 dest 只增加1,因为当前元素不需要复制。
如果 arr[cur] 为0,那么 dest 增加2,因为需要复制一个0放在当前0的后面。
处理边界情况:在遍历过程中,如果 dest 超过了数组的最大索引 n-1,则说明没有足够的空间来放置所有的0。在这种情况下,将数组的最后一个元素设置为0,并将 dest 和 cur 指针相应地向后移动。

  1. 从后向前复写:在确定了所有元素的目标位置后,从数组的末尾开始,根据原始数组中的元素值,将元素复制到它们的目标位置。

如果 arr[cur] 不为0,则将其复制到 arr[dest],并将两个指针都向前移动1位。
如果 arr[cur] 为0,则先复制一个0到 arr[dest],再复制另一个0到 arr[dest-1],然后将 cur 向前移动1位,dest 向前移动2位。

在这里插入图片描述

解题方法

算法使用双指针技术,通过一次遍历来确定每个元素的目标位置,然后从后向前进行复写。这种方法避免了额外的空间开销,并且只进行了一次遍历,因此效率较高。

时间复杂度: O(n),其中n是数组的长度。算法只进行了一次遍历,因此时间复杂度是线性的。
空间复杂度: O(1),算法只使用了常数个额外变量,没有使用与数组大小相关的额外空间。

总结

这个算法是一种高效的原地修改算法,它利用了双指针技术来避免额外的空间开销,并且只进行了一次遍历。这种算法的时间复杂度和空间复杂度都是最优的。

C++ 算法代码:

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        //找最后一个数 
        int dest=-1,cur=0,n=arr.size();
        while(cur<n)
        {
            if(arr[cur])
                dest++;
            else
                dest+=2;
            if(dest>=n-1)
                break;
            cur++;
        }
        //边界情况
        if(dest>=n)
        {
            arr[n-1]=0;
            dest-=2;
            cur--;
        }
        while(cur>=0)
        {
            if(arr[cur])
                arr[dest--]=arr[cur--];
            else
            {
                arr[dest--]=arr[cur];
                arr[dest--]=arr[cur--];
            }

        }

    }
};

在这里插入图片描述
Java 算法代码:

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

http://www.niftyadmin.cn/n/5430818.html

相关文章

【设计模式】Java 设计模式之单例模式(Singleton Pattern)

一、单例模式概述 单例模式是一种创建型设计模式&#xff0c;它确保一个类仅有一个实例&#xff0c;并提供一个全局访问点来访问这个唯一实例。在软件设计中&#xff0c;单例模式常用于管理那些只需要一个实例的类&#xff0c;如配置信息类、数据库连接池等。 二、单例模式结…

k8s集群部署elk

一、前言 本次部署elk所有的服务都部署在k8s集群中&#xff0c;服务包含filebeat、logstash、elasticsearch、kibana&#xff0c;其中elasticsearch使用集群的方式部署&#xff0c;所有服务都是用7.17.10版本 二、部署 部署elasticsearch集群 部署elasticsearch集群需要先优化…

为什么手机和电视ip地址不一样

在数字化时代&#xff0c;我们每天都会与各种电子设备打交道&#xff0c;其中最常见的就是手机和电视。当我们连接到互联网时&#xff0c;这些设备都会被分配一个独特的IP地址&#xff0c;用于在网络上进行标识和通信。然而&#xff0c;您可能已经注意到&#xff0c;即使手机和…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ListItemGroup)

该组件用来展示列表item分组&#xff0c;宽度默认充满List组件&#xff0c;必须配合List组件来使用。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件的父组件只能是List。 使用说明 当List…

hive行转列函数stack(int n, v_1, v_2, ..., v_k)

用stack()函数时&#xff0c;参数中的键值对应按照一对列名和列值进行排使用列 stack(int n, v_1, v_2, ..., v_k) 功能&#xff1a;把k列数据转换成n行&#xff0c;k/n列&#xff0c;其中n必须是正整数&#xff0c;后面的v_1到v_k必须是元素&#xff0c;不能是列名。&#x…

【Algorithm】动态规划和递归问题:动态规划和递归有什么区别?如何比较递归解决方案和它的迭代版本?

【Algorithm】动态规划和递归问题:动态规划和递归有什么区别?如何比较递归解决方案和它的迭代版本? 1. 动态规划(Dynamic Programming,DP)和递归定义及优缺点1.1 递归 (Recursion)定义及优缺点1.2 动态规划 (Dynamic Programming)定义及优缺点2. 动态规划(DP)和递归的特…

Flashcat与出行科技企业一起实践多云可观测

当前架构 某出行科技企业从单个公有云往多云转型&#xff0c;依托于国内领先的公有云提供商&#xff0c;采用多云架构&#xff0c;在可用性、弹性、成本、供应商依赖方面&#xff0c;拥有了显著的优势。 相应的&#xff0c;多云架构也给技术团队带来了一定的复杂度和技术挑战…

提升用户体验,Xinstall智能判定拉起技术来袭

在移动互联网时代&#xff0c;App推广已经成为各大企业的必争之地。然而&#xff0c;随着市场竞争的加剧&#xff0c;如何提升App的转化效率和用户体验成为了推广者们亟待解决的问题。这时&#xff0c;Xinstall的智能判定拉起技术应运而生&#xff0c;为推广者们带来了新的解决…