在学习了基础语法后,是时候通过实战题目来巩固双指针技巧了。今天重点讲解两道经典的数组原地修改问题:移动零与复写零。
一、移动零
题目描述
给定一个数组 nums,要求将所有 0 移动到数组末尾,同时保持非零元素的相对顺序。注意:必须在原数组上操作,且尽量减少操作次数。
思路解析
最直观的想法是创建一个新数组,但题目限制了空间复杂度。我们可以利用双指针来解决:
- 读指针 (
read):遍历整个数组,寻找非零元素。 - 写指针 (
write):指向当前应该放置非零元素的位置。
当 read 遇到非零数时,将其与 write 位置的元素交换(如果两者不同),然后 write 前移。这样能保证所有非零元素按顺序被'挤'到前面,剩余的尾部自然填充为 0。
代码实现
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) {
// 只有当两个指针不重合时才交换,减少不必要的操作
if (read != write) {
Swap(&nums[write], &nums[read]);
}
write++;
}
read++;
}
}
二、复写零
题目描述
在一个固定长度的整数数组中,将每个出现的 0 复写一遍,导致后续元素向右移动,超出原数组长度的元素将被丢弃。同样需要原地修改。
思路解析
如果从前向后遍历并插入 0,会覆盖掉尚未处理的非零元素,因此不可行。正确的策略是从后向前处理:
- 统计有效长度:先遍历一次数组,模拟复写过程,计算出在复写后的逻辑数组中,最后一个会被保留的元素位置(记为
dest)。 - 边界判断:如果某个
0被复写后超出了数组边界,说明这个0只能写入一次(即数组的最后一个位置)。 - 反向填充:从计算出的位置开始,倒序遍历原数组,将元素写入目标位置。遇到
0则写入两次(需检查边界)。


