LeetCode 283:移动零(双指针问题) —— 题解
👋 欢迎阅读
一.题目
283. 移动零 - 力扣(LeetCode)
🎯 欢迎来到「移动零」题解之旅!本文将带你从“将所有零移到数组末尾,同时保持非零元素顺序”这一数组操作问题出发,深入理解双指针(快慢指针)的经典应用,并掌握如何原地修改数组,实现高效的一次遍历。
在开始之前,建议你先:
了解题目背景:这是 LeetCode 283 题,给定一个数组
nums,要求将所有0移动到数组末尾,并保持非零元素的相对顺序不变,且必须原地操作(不能复制新数组)。这是数组操作中的基础题,也是双指针思想的入门经典。明确学习目标:掌握双指针解法——维护一个“慢指针”指向已处理好的非零序列的末尾,用“快指针”遍历数组,遇到非零元素则交换(或覆盖)到慢指针位置,最后将剩余位置填零。理解为什么这种“只关心非零元素,遇到零就跳过”的策略能保持相对顺序,并熟练处理边界(如全零数组或全非零数组)。
准备好环境:建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如
nums = [0,1,0,3,12]输出[1,3,12,0,0])。
本文将从问题转化、双指针策略设计(快慢指针详解)、代码模拟到复杂度分析,层层递进。即使你对双指针还不熟悉,我们也会从“用慢指针记录非零元素应该放的位置”这一直觉出发,让你轻松抓住核心思想——快指针负责探路,慢指针负责记录,所有非零元素依次往前靠,零自然被挤到后面。现在,让我们一起把零“搬运”到末尾,让数组焕然一新吧! 🔄📦
二.做题思路
一、问题分析(前置分析)
给定一个数组nums,要求将所有 0 移动到数组末尾,同时保持非零元素的相对顺序不变。必须原地修改,不能复制数组。
核心观察:等价于将所有非零元素按原顺序压缩到数组前端,剩余位置全部填充 0。
二、算法策略(双指针)
使用快指针
fast遍历数组,慢指针slow指向下一个非零元素应该存放的位置。初始化:
slow = 0。遍历过程:
若
nums[fast] != 0,则将nums[fast]赋值给nums[slow],然后slow++。无论当前元素是否为 0,
fast都向后移动。
遍历结束后,
slow之前的位置都已放置非零元素,将slow到末尾的所有位置置为 0。
三、正确性说明(简单版本)
快慢指针保证了所有非零元素按原顺序被依次“搬运”到数组前部,且不会丢失任何非零元素。因为slow始终指向下一个可放置非零元素的位置,而fast负责遍历所有元素,遇到非零就覆盖到slow处。最后将剩余位置置零,既保留了非零顺序,又确保了所有 0 都在末尾。该算法一次遍历即可完成,正确性由指针移动逻辑保证。
四、实现细节(边界防护)
若数组长度
n <= 1,直接返回(无需操作)。使用
int slow = 0。for (int fast = 0; fast < n; ++fast)遍历:若
nums[fast] != 0,则nums[slow++] = nums[fast]。
遍历结束后,从
slow到n-1循环赋值0。时间复杂度 O(n),空间复杂度 O(1),满足原地要求。
五、返回值(目标映射)
不需要返回值,原地修改数组,使所有 0 移动到末尾,非零元素相对顺序不变。
三.代码
class Solution { public: void moveZeroes(vector<int>& nums) { // 算法思路:双指针法 // left 指向当前可能存放非零元素的位置(也是等待被非零元素替换的位置) // right 从 left+1 开始,向后查找非零元素,一旦找到就与 left 交换, // 然后将 left 右移一位,继续处理。 // 这样就能保证所有非零元素按原顺序前移,所有零被移动到末尾。 int left = 0; int right = left + 1; int n = nums.size(); // 当右指针未越界时,持续扫描 while (right < n) { // 如果左指针指向0,说明此处需要被非零元素替换 if (nums[left] == 0) { // 如果右指针指向非零元素,则交换,将非零元素移到左指针位置 if (nums[right] != 0) { swap(nums[left], nums[right]); // 注意:交换后,left 位置变为非零,但 left 并未自增, // 下一次循环时会进入 else 分支将 left 和 right 都右移, // 相当于 left 指向了下一位,right 指向下下位,正确。 } else { // 如果右指针也指向0,则右指针继续右移,寻找非零元素 right++; } } else { // 如果左指针指向非零,说明当前位置已经正确,将两个指针同时右移 left++; right++; } } } };四、流程图
🎯 闭幕
🎉 恭喜你完成了「移动零」问题的学习!
为了巩固知识并进一步拓展,建议你:
🚀动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。
💡深入思考
本题要求原地移动零,且保持非零元素的相对顺序。常用的解法是双指针(快慢指针),
slow指向已处理区域的末尾,fast用于遍历。请问slow和fast各自的具体职责是什么?如果采用覆盖法(先移非零,再补零)与交换法(遇非零即与
slow交换),两种方式的操作次数有何差异?哪种在时间复杂度相同的情况下更高效?若数组包含负数,题目只要求移动零,负数应视为非零元素保持顺序,算法逻辑是否需要改动?
如果要求将所有零移动到数组开头(而非末尾),你只需修改判断条件中的哪一处?请动手试一试。
本题强制不复制数组,若允许复制,你会用怎样的额外空间方案实现?此时的时间复杂度是否变化?
📚延伸挑战
若问题改为将指定值(不限于 0)全部移到末尾,且保持其他元素顺序,你的代码应做哪些通用化改造?
如果你觉得本文对你有所帮助,欢迎:
👍点赞 / 收藏
👤关注作者,获取更多题解
💬留言交流你的疑问或优化思路
✅深入思考答案
双指针职责:
slow表示已排好的非零区域的下一个位置(即慢指针),fast用于遍历数组寻找非零元素。覆盖法 vs 交换法:两者时间复杂度均为 O(n),但覆盖法只需赋值(非零前移 + 末尾补零),交换法需三次赋值(交换),因此覆盖法常数更小,通常更快。
负数处理:算法只判断
!=0,负数被视为非零,无需改动。移动零到开头:将条件
nums[fast] != 0改为nums[fast] == 0,并将非零值(如 1)补在末尾。允许复制:新建数组,先拷贝非零,再补零,最后复制回原数组,时间 O(n),空间 O(n)。
祝你在算法之路上越走越稳,早日攻克每一道难题!下次见 🚀✨