题目链接
题目理解
本题不难理解,其实就是将数组中的0复写一遍,数组空间不能变长,因此,我们每复写一个0,数组末尾的数字就会被覆盖掉。
输入: arr = [1,0,2,3,0,4,5,8]
输出: [1,0,0,2,3,0,0,4]
解释:将数组中的0复写,复写了两个0,因此数组的空间被占了两个,数字5和数字8被覆盖。
本题的难点在于,题目要求只能在原本的数组上修改,且数组不能够超过原来的长度。那我们想insert将0插入,然后删除超过长度的部分的思路就不能使用了。
解题思路
核心思路:双指针 + 从后向前填充
这道题的关键在于:我们不能从前向后直接复制,因为这样会覆盖还未处理的元素。例如,如果我们在位置 i 遇到 0,直接将它复制到位置 i+1,那么原位置 i+1 的元素就会被覆盖,而这个元素可能本身也需要被处理。
思路分析
问题转化:我们需要在数组中找到每个 0 后都插入一个 0,同时保持数组长度不变。这会导致一些元素被"挤出"数组。
关键问题:原数组中的哪些元素最终会留在新数组中?
双指针方法:
我们使用两个指针:
slow(慢指针)遍历原数组,fast(快指针)模拟在新数组中的位置
slow每遍历一个元素,fast就前进 1 步(如果元素是 0,则前进 2 步)当
fast到达或超过数组末尾时,slow指向的就是原数组中最后一个会被保留在新数组中的元素
老规矩,咱们直接上图例,主播手把手带大家过一遍。
第一步:找到 slow 的位置(确定哪些元素会被保留)
先设置两个指针,slow和fast,初始值全部为0,指向首元素,循环结束条件呢,是fast>=arr.size(),因为此时代表复写0后的长度已经达到/超出原长度,即可结束循环(大家可以接着往下看,超出的情况我会讲到):

arr[slow]为0,fast前进两步:

本次循环结束,slow++:

下一次循环,arr[slow]非0,fast前进一步,循环结束,slow++:

这次循环arr[slow]依旧非0,操作同上:

这次arr[slow]是0,fast+=2,然后slow++:

非0,都加一(注意此时fast值为7,因为数组从0开始,因此并未结束循环):

arr[slow]为0,fast+=2,此时fast为9>arr.size(),为超出情况。为什么会超出呢?因为最后加等了2,也就是最后一个数字为0

此时:
slow = 5(指向原数组中的第一个 0)
fast = 9分析 :
fast = 9表示如果完全复制所有 0,新数组需要 9 个位置,但我们的数组只有 8 个位置。因此最后一个 0 只能被复制一次(而不是两次)。
第二步:处理边界情况
因为
fast = 9 > n = 8(n为数组长度),我们需要特殊处理:
将数组最后一个位置(arr[7])设为 0
slow回退到 4(相当于是从倒数第二次的位置开始)
fast设为 n-2(n为数组长度,从倒数第二个位置开始填充)此时:
slow = 4(指向元素 1)
fast = 6数组变为:
[0, 5, 2, 0, 1, 0, 2, 0](最后一个位置已经处理)解释:说人话就是,我们知道这种情况最后是一个0(第二个复写的0会越界),因此直接在最末尾放上一个0,这一位数组就处理好了,我们将指针回溯到前一步的位置。
如果最后一个数字不为0,直接让fast=n-1即可(n为数组长度)
第三步:从后向前填充数组
该例子为特殊情况(fast>n),我们按第二步操作后,指针指向情况如下:

现在将slow的值给fast(Nums[slow]不为0的情况都这样处理),因为fast的位置,对应的就是slow元素复写后的位置(不理解的同学可以自己写出来这个例子复写0后的结果对照着想一下):

指针前移:

此时Nums[slow]==0,那么我们将fast以及fast前面一位的位置写为0(复写了两次,因此fast--两次,循环结束时,slow--一次):

不为0,直接将slow的值给fast,然后指针前移:

同上操作:

Nums[slow]==0,fast以及fast前面一位的位置写为0(复写了两次,因此fast--两次,循环结束时,slow--一次):

完成!下面我们看一下源码,主播给大家详细注释过了:
详解源码
cpp
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int n = arr.size();
// 第一阶段:定位最后一个需要保留的元素
int slow = 0; // 遍历原数组
int fast = 0; // 在新数组中的位置
while (fast < n)
{
if (arr[slow] == 0)
{
fast += 2; // 遇到0,新数组中需要占两个位置
}
else
{
fast += 1; // 非0元素,只占一个位置
}
// 当新数组位置已满时,停止遍历
if (fast >= n)
{
break;
}
slow++; // 继续检查下一个元素
}
// 第二阶段:处理边界情况
// 如果最后一个元素是0会导致数组越界,特殊处理
if (fast > n)
{
arr[n - 1] = 0; // 最后一个位置设为0
slow--; // 回退到前一个元素
fast = n - 2; // 从倒数第二个位置开始
}
else
{
fast = n - 1; // 正常情况,从最后一个位置开始
}
// 第三阶段:从后向前完成复写操作
while (slow >= 0)
{
if (arr[slow] != 0)
{
// 非0元素,直接复制
arr[fast--] = arr[slow--];
}
else
{
// 0元素,需要复制两次
arr[fast--] = 0;
if (fast >= 0) // 确保不会数组越界
{
arr[fast--] = 0;
}
slow--;
}
}
}
};
学会了就给主播点个赞呗?(✪ω✪)
---------(如有问题,欢迎评论区提问)---------