双指针算法实战:移动零、复写零与快乐数
核心概念
双指针算法在数组和链表操作中非常常见,主要分为两种形式:对撞指针和快慢指针。
对撞指针通常用于顺序结构,也叫左右指针。两个指针分别从两端向中间移动,一个从最左端开始,另一个从最右端开始,逐渐逼近。终止条件通常是两指针相遇(left == right)或错开(left > right)。
快慢指针又称龟兔赛跑算法,基本思想是使用两个移动速度不同的指针在序列上移动。如果问题涉及循环往复的情况,快慢指针是很好的选择。最常用的实现是在一次循环中,慢指针每次向后移动一位,快指针每次向后移动两位。
注意:这里的指针并非 C 语言中的地址指针(int*),而是用变量记录数组下标,变量++或--相当于指针的移动。
283. 移动零
题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。必须在原数组上操作,不能拷贝额外的数组。
思路解析
对撞指针在这里行不通,因为需要保持非零元素的相对顺序。我们采用两个指针都从数组左端出发:一个用来找非零元素(cur),另一个用来确定非零元素放置的位置(dest)。
- 初始化 cur 指向第一个元素,dest 初始化为 -1。
- 当 cur 指向的元素为 0 时,cur 继续向后移动。
- 当 cur 指向的元素不为 0 时,dest 向前移动一位,然后交换 cur 和 dest 指向的元素,cur 继续向后移动。
- 当 cur 走到数组尾部时结束。
这种方案保证了非零元素按原顺序前移,剩余的 0 自然留在后面。
代码实现
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int size = nums.size();
int dest = -1;
int cur = 0;
while (cur < size) {
if (nums[cur] != 0) {
dest++;
swap(nums[dest], nums[cur]);
}
cur++;
}
}
};
1089. 复写零
题目描述
在一个固定的数组中,将每个出现的 0 重复复制一份,其他元素后移。数组长度固定,超出部分丢弃。
思路解析
直接遍历会导致覆盖后续元素,因此考虑从后往前遍历。
步骤一:寻找最后一个有效元素位置 利用双指针法,cur 从头开始,dest 初始化为 -1。遇到非零元素 dest 后移一位,遇到 0 元素 dest 后移两位。当 dest 到达数组末尾时停止。这里要注意细节:如果 dest 移动两次后刚好到末尾,cur 就不能再前进了,否则空间不够。
步骤二:从后往前复写 cur 指向最后一个有效数据,dest 指向数组最后。从 cur 开始向前遍历:非零元素写一次,0 元素写两次。如果 cur 指向的是 0 且 dest 只剩一个位置,则只写一次 0。


