LeetCode-Go 题解:283. Move Zeroes——双指针原地移动零元素的三种变体
2026/9/10 4:24:07 网站建设 项目流程

LeetCode-Go 题解:283. Move Zeroes——双指针原地移动零元素的三种变体

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

本文围绕 LeetCode 第 283 题Move Zeroes(移动零)展开,以 LeetCode-Go 仓库中 leetcode/0283.Move-Zeroes/README.md 为主体,结合仓库内对应的 Go 实现 与 测试用例,讲解如何在不借助额外数组的前提下,用一趟扫描完成原地置换,保持非零元素的相对顺序。读完本文,你将掌握双指针快慢交换这一高频技巧,并能顺带打通第 26、27、80 题的同源解法。

题目描述

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

给定一个整数数组nums,编写一个函数,将所有的0移动到数组的末尾,同时保持所有非零元素的相对顺序不变。

示例:

Input: [0,1,0,3,12] Output: [1,3,12,0,0]

题目附带的两个约束,是这道题真正的考点:

  • 必须原地(in-place)操作,不允许复制一份数组;
  • 尽量最小化总的操作次数(Minimize the total number of operations)。

题目大意(中文解读)

结合 leetcode/0283.Move-Zeroes/README.md 中的中文说明,本题核心要求可概括为两点:

  1. 不能采用额外的辅助空间(即空间复杂度必须控制在 O(1) 级别);
  2. 将数组中的所有0元素移动到末尾,并且维持所有非0元素的相对位置不变。

也就是说,[0,1,0,3,12]处理后的结果必须是[1,3,12,0,0]1,3,12三者的先后次序不能被改变——这一点直接否定了"先把非零挑出来、再整体排序"之类的思路,也决定了必须采用保持稳定性的原地算法。

解题思路:一趟扫描的 i、j 双指针交换

README 给出的解题思路非常精炼:

这一题可以只扫描数组一遍,不断的用 i,j 标记 0 和非 0 的元素,然后相互交换,最终到达题目的目的。

这里i是快指针,负责遍历数组寻找非零元素;j是慢指针,标记"下一个应该放置非零元素的位置"。每一轮遇到非零元素时,把nums[i]nums[j]交换,然后j前进一位。由于i恒不小于j,交换操作只会把非零值向前搬运,把0向后推移,因此:

  • 非零元素的相对顺序得到保持(稳定);
  • 所有0最终被"挤"到数组末尾;
  • 全程只扫描一遍数组,时间复杂度 O(n),空间复杂度 O(1)。

仓库源码级实现解析

仓库中的核心实现位于 leetcode/0283.Move-Zeroes/283. Move Zeroes.go,完整代码如下:

package leetcode func moveZeroes(nums []int) { if len(nums) == 0 { return } j := 0 for i := 0; i < len(nums); i++ { if nums[i] != 0 { if i != j { nums[i], nums[j] = nums[j], nums[i] } j++ } } }

关键实现细节拆解

  1. 空数组早退if len(nums) == 0 { return }保证对空切片安全返回,这与 gotest.sh 覆盖全仓库测试的运行方式配合,避免空输入引发越界。

  2. i != j的优化:当ij指向同一位置(说明从ji之间没有出现过0),此时元素本就在正确的位置上,交换自身毫无意义。显式跳过该分支,减少了不必要的写操作,正是题目"最小化操作次数"这一要求的直接体现。

  3. 交换的等价性:由于j <= i恒成立,nums[j]在被交换前要么是0(前面有零元素被跳过),要么是j == i时的自身,因此交换不会破坏任何非零元素的相对次序。

复杂度与正确性

  • 时间复杂度:O(n),其中 n 为数组长度,每个元素至多被访问一次;
  • 空间复杂度:O(1),只使用了一个额外的整型变量j
  • 稳定性:非零元素顺序保持,完全满足题意。

测试用例与验证

仓库为本题配备了 7 组表驱动测试,见 leetcode/0283.Move-Zeroes/283. Move Zeroes_test.go,覆盖了各种边界情况:

输入期望输出覆盖场景
[1, 0, 1][1, 1, 0]非零元素中间夹一个零
[0, 1, 0, 3, 0, 12][1, 3, 12, 0, 0, 0]连续多个零分散在数组中
[0, 1, 0, 3, 0, 0, 0, 0, 1, 12][1, 3, 1, 12, 0, 0, 0, 0, 0]长零序列 + 多个非零元素
[0, 0, 0, 0, 0, 0, 0, 0, 12, 1][12, 1, 0, 0, 0, 0, 0, 0, 0, 0]全零前缀
[0, 0, 0, 0, 0][0, 0, 0, 0, 0]全零数组
[1][1]单元素数组
[][]空数组

其中全零数组与空数组两个用例,恰好验证了实现中的空输入早退与"无零可移"的幂等行为。测试采用 Go 标准库testing编写,运行方式与仓库整体一致:

go test ./leetcode/0283.Move-Zeroes/ -v

仓库根目录还提供了 gotest.sh,用于对./leetcode/...下所有题目包一次性生成合法的覆盖率文件,方便核对本题的覆盖情况。

延伸:与第 26、27、80 题的同源解法

README 明确指出"与这一题相近的题目有第 26 题,第 27 题,第 80 题"。从源码结构看,这四题共享同一套"双指针原地重排"的心法,区别只在于判定条件与返回值:

  • 第 26 题 Remove Duplicates from Sorted Array:用lastfinder两个指针去重,把每个不重复的元素搬到数组前部,返回新长度;
  • 第 27 题 Remove ElementremoveElement(nums, val)把不等于val的元素全部前移,j即新长度,其"跳过目标值、快慢指针交换"的骨架与moveZeroes几乎逐行对应——moveZeroes可以看作val = 0且把剔除的元素"收拢到末尾"的特例;
  • 第 80 题 Remove Duplicates from Sorted Array II:允许每个元素最多保留两个副本,用slowfast指针配合nums[slow-2] != v判定,思路同源但阈值不同。

对照阅读这四份实现,可以归纳出这一类"数组原地重排"题的通用套路:一个快指针负责探测,一个慢指针负责记录写入位置,以某种规则决定哪些元素需要被跳过。掌握了 283 题的双指针交换,其余三题基本可以举一反三。

小结

第 283 题是双指针技巧中最典型的入门题之一。LeetCode-Go 仓库给出的实现用最少的代码量满足了"原地 + 一趟扫描 + 保持相对顺序 + 最小化操作次数"的全部约束,并通过 测试用例 覆盖了从空数组到全零数组的各类边界。建议读者在理解ij交换逻辑后,结合第 26、27、80 题的源码做对比练习,把"快慢指针原地重排"固化成可迁移的解题肌肉记忆。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询