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

avatar
作者
猴君
阅读量:2

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

请添加图片描述


目录


前言

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

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!