在学习了基础语法之后,是时候通过实战来巩固双指针技巧了。今天我们来攻克两道经典的数组操作题:移动零和复写零。
一、移动零
题目描述
给定一个数组 nums,要求将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。注意:必须在原数组上操作,不能创建新数组。
思路解析
这道题的核心在于如何原地重排。最直观的想法是创建一个新数组,但题目限制了空间复杂度。我们可以利用两个指针来解决:
- 读指针 (
read):负责遍历整个数组,寻找非零元素。 - 写指针 (
write):负责记录下一个非零元素应该放置的位置。
当 read 遇到非零元素时,将其交换到 write 指向的位置,然后 write 向前移动一步。无论是否交换,read 都会继续向后扫描。这样就能保证所有非零元素按顺序被'挤'到前面,剩下的位置自然就是 0。
代码实现
以下是 C 语言的具体实现:
void Swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void moveZeroes(int *nums, int numsSize) {
int read = 0;
int write = 0;
while (read < numsSize) {
if (nums[read] != 0) {
Swap(&nums[read], &nums[write]);
write++;
}
read++;
}
}
这段逻辑非常简洁,时间复杂度为 O(n),空间复杂度为 O(1)。实际运行中,如果数组本身没有 0,或者 0 已经在末尾,交换操作依然能正确工作,不会破坏原有顺序。
二、复写零
题目描述
在一个固定长度的整数数组中,将每个出现的 0 复制一份,并将后续元素向右移动。数组长度超出部分将被截断。
思路解析
如果从前往后处理,每遇到一个 0 就插入,后面的元素会被覆盖,导致数据丢失。因此,从后往前操作是更稳妥的策略。
为了确定从哪里开始写入,我们需要先计算一下'扩展后的长度'。具体步骤如下:
- 统计长度:遍历数组,模拟扩展过程。如果遇到 0,计数加 2;否则加 1。直到计数达到或超过数组长度。
- 处理边界:如果最后一个被处理的元素是 0,且它会导致数组越界(即刚好填满),那么原数组的最后一个元素不需要再复制一次,直接保留即可。
- 反向填充:从原数组末尾开始,根据当前元素是否为 0,决定写入一个还是两个 0。


