在学习了基础语法之后,是时候通过实战来巩固一下双指针技巧了。今天我们来搞定两道经典的数组操作题:移动零和复写零。
一、移动零
题目描述
给定一个数组 nums,要求将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。注意:必须在原数组上操作,不能创建新数组。
思路解析
这道题的核心在于如何原地交换。很多人第一反应是新建一个数组存非零元素,但这违反了空间复杂度的要求。
我们可以使用两个指针:
cur(current):负责遍历整个数组,寻找非零元素。dest(destination):指向当前应该放置非零元素的位置。
当 cur 遇到非零元素时,将其与 dest 位置的元素交换,然后 dest 后移。如果 cur 遇到的是 0,则跳过,继续向后找。这样能保证所有非零元素按顺序前移,剩下的位置自然就是 0。
这里有个小细节:如果 cur 和 dest 指向同一个位置,其实不需要交换,直接跳过即可,能减少不必要的内存写入。
代码实现
void Swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void moveZeroes(int* nums, int numsSize) {
int cur = 0;
int dest = 0;
while (cur < numsSize) {
if (nums[cur] != 0) {
// 只有当两个指针不重合时才交换,优化性能
if (cur != dest) {
Swap(&nums[dest], &nums[cur]);
}
dest++;
}
cur++;
}
}
这段代码在力扣平台上运行效率不错,逻辑清晰且符合原地修改的要求。
二、复写零
题目描述
在一个固定长度的整数数组中,将每个出现的 0 重复一遍。数组后面的元素会被覆盖掉,不需要保留。
思路解析
这道题如果从前向后处理会非常麻烦。因为一旦你在中间插入了一个 0,后面的元素都要往后挪一位,这会导致已经处理过的数据被覆盖或者需要频繁移动内存,复杂度很高。
最佳策略是从后向前遍历。
- 统计范围:先遍历一遍数组,算出如果进行复写操作,最终的有效长度会延伸到哪里。我们需要找到最后一个会被写入的元素位置。


