复写零
- 一、题目链接
- 二、题目
- 三、算法原理
- 1、先找到最后一个要复写的数——双指针算法
- 1.5、处理一下边界情况
- 2、“从后向前”完成复写操作
- 四、编写代码
- 五、时间复杂度和空间复杂度
一、题目链接
复写零
二、题目
三、算法原理
解法:双指针算法
先根据“异地”操作,然后优化成双指针下的“就地”操作。
异地操作完成复写零:定义两个指针,一个指针cur指向原来的数组,一个指针dest指向新开辟的数组。两个指针从左往右操作。当cur指向非零,抄一遍;当cur指向零,抄两遍。当dest越界复写结束。
就地操作模拟异地操作:cur、dest都会指向原始数组。
我们发现会将非0元素覆盖为0,所以从前往后操作在本题行不通。
但是“删除数组中值为val的元素”这一题必须从前往后,同样先根据异地操作总结模拟规律,后就地根据双指针完成,因为是删除,所以dest肯定在cur前面,因此肯定不能覆盖那些未操作的数。
那么在本题“复写零”中从前往后不行,那么试试从后往前呢?两个指针初始位置怎么确定?抄写元素肯定要抄写到最后一位,所以dest在最后一个位置,那么cur指向最后一个要复写的数。
还是用示例1分析,dest指向数组最后一个位置,cur指向最后一个要复写的元素4,让复写操作从后往前进行。dest往前走的过程中,dest要修改的数已经复写过了就可以大胆地覆盖:
1、先找到最后一个要复写的数——双指针算法
我们发现从后往前走确实可行,但是要先找到最后一个要复写的数——双指针算法:
dest:始终指向完成复写的位置,所以初始位置在下标-1处。过程中判断dest是否到数组最后一个位置。
cur:从前向后遍历数组,决定dest向后移动1步或2步。所以初始位置在下标0处。
步骤:
- 判断cur指向的值
- cur的值决定dest是向后移动1步(cur为非0)或2步(cur为0)
- 判断dest是否到数组中的最后一个位置(若到最后,终止操作)
- 若dest没到最后,++cur
到最后,cur指向最后一个要复写的数;dest指向数组中的最后一个位置,也就是将“最后一个复写的数”写到的位置。这个位置就是cur、dest从后向前操作的起始位置。
1.5、处理一下边界情况
但是这样“找”在提交代码时还是会有问题的,特殊示例:
只会在最后一个要复写的数为0时dest才会越界,dest完成“从后向前”会对数组越界访问,在LeetCode上越界访问会报错。
若dest越界了:
- 数组下标n - 1处的值改为0
- cur- -、dest - = 2
2、“从后向前”完成复写操作
前面有复写操作的图片,这里就拿过来啦
四、编写代码
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
// 1.先找到最后一个要复写的数--双指针算法
int cur = 0, dest = -1, n = arr.size();
while (dest < n)
{
// 判断cur指向的值,决定dest走1步或2步
if (arr[cur]) dest++;
else dest += 2;
// dest到最后了,cur所指就是最后复写的数
if (dest >= n - 1) break;
// dest没到最后
++cur;
}
// 处理边界情况
if (dest == n)
{
arr[n - 1] = 0;
--cur; dest -= 2;
}
// 2."从后向前"完成复写操作
while (cur >= 0 && dest >= 0)
{
if (arr[cur]) arr[dest--] = arr[cur--];
else
{
arr[dest--] = 0;
arr[dest--] = 0;
--cur;
}
}
}
};
五、时间复杂度和空间复杂度
虽然是两个while循环,但是常数级别的时间是可以忽略的,因此时间复杂度是O(n),整体的时间复杂度就相当于遍历一遍数组就可以完成“复写”的操作。
空间复杂度只有限的使用到了3个变量,所以空间复杂度是O(1)。