❌|✅|💡|📌 283. 移动零
🔗 问题简介
题解github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions
📝 题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意,你必须在不复制数组的情况下原地对数组进行操作。
🧪 示例说明
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
约束条件:
1 <= nums.length <= 10⁴-2³¹ <= nums[i] <= 2³¹ - 1
💡 解题思路
方法一:双指针(最优解)
✅ 核心思想:使用两个指针,一个用于遍历(快指针),一个用于记录非零元素应放置的位置(慢指针)。
步骤详解:
- 初始化慢指针
slow = 0。 - 快指针
fast从 0 遍历到数组末尾:- 如果
nums[fast] != 0,则将其赋值给nums[slow],并slow++。
- 如果
- 遍历结束后,将
slow到数组末尾的所有位置置为0。
💡 这种方法保证了非零元素的相对顺序不变,且只遍历一次数组主体。
方法二:交换法(双指针变体)
✅ 核心思想:每当遇到非零元素,就与当前"零区"开头的元素交换。
步骤详解:
- 初始化指针
j = 0。 - 遍历数组,当
nums[i] != 0时:- 交换
nums[i]和nums[j] j++
- 交换
- 遍历结束即完成移动。
💡 此方法无需二次填充 0,但涉及更多写操作(交换 vs 赋值)。
方法三:暴力法(不推荐)
❌ 核心思想:每次遇到 0 就将其"冒泡"到末尾。
缺点:
- 时间复杂度高(O(n²))
- 多次移动相同元素
- 不符合高效要求
📌 结论:仅用于理解问题,实际应避免。
💻 代码实现
java
class Solution {
// 方法一:双指针 + 填充0
public void moveZeroes(int[] nums) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != 0) {
nums[slow++] = nums[fast];
}
}
// 填充剩余位置为0
while (slow < nums.length) {
nums[slow++] = 0;
}
}
// 方法二:交换法(更简洁)
public void moveZeroesSwap(int[] nums) {
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
j++;
}
}
}
}
go
func moveZeroes(nums []int) {
// 方法一:双指针 + 填充0
slow := 0
for fast := 0; fast < len(nums); fast++ {
if nums[fast] != 0 {
nums[slow] = nums[fast]
slow++
}
}
for slow < len(nums) {
nums[slow] = 0
slow++
}
}
// 方法二:交换法
func moveZeroesSwap(nums []int) {
j := 0
for i := 0; i < len(nums); i++ {
if nums[i] != 0 {
nums[i], nums[j] = nums[j], nums[i]
j++
}
}
}
🎥 示例演示(以 [0,1,0,3,12] 为例)
方法一执行过程:
| 步骤 | fast | nums[fast] | slow | nums(部分) |
|---|---|---|---|---|
| 初始 | - | - | 0 | [0,1,0,3,12] |
| 1 | 0 | 0 | 0 | [0,1,0,3,12] |
| 2 | 1 | 1 ≠ 0 | 1 | [1,1,0,3,12] |
| 3 | 2 | 0 | 1 | [1,1,0,3,12] |
| 4 | 3 | 3 ≠ 0 | 2 | [1,3,0,3,12] |
| 5 | 4 | 12 ≠ 0 | 3 | [1,3,12,3,12] |
| 填0 | - | - | 3→5 | [1,3,12,0,0] ✅ |
方法二执行过程:
| i | j | 操作 | nums |
|---|---|---|---|
| 0 | 0 | nums[0]=0 → skip | [0,1,0,3,12] |
| 1 | 0 | swap(1,0) → j=1 | [1,0,0,3,12] |
| 2 | 1 | nums[2]=0 → skip | [1,0,0,3,12] |
| 3 | 1 | swap(3,1) → j=2 | [1,3,0,0,12] |
| 4 | 2 | swap(12,2) → j=3 | [1,3,12,0,0] ✅ |
✅ 答案有效性证明
正确性依据:
- 非零元素顺序不变:两种方法均按原顺序将非零元素依次放置或交换,未改变其相对位置。
- 所有 0 移至末尾 :
- 方法一:显式填充末尾为 0。
- 方法二:每次非零元素都与最左侧的 0 交换,最终所有 0 被"挤"到右边。
- 原地操作:仅使用 O(1) 额外空间,满足题目要求。
边界情况验证:
- 全为 0 → 输出全 0 ✅
- 无 0 → 原数组不变 ✅
- 单元素 → 正确处理 ✅
📊 复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 写操作次数 |
|---|---|---|---|
| 双指针+填0 | O(n) | O(1) | ≤ n(赋值+填0) |
| 交换法 | O(n) | O(1) | ≤ 2n(每次交换2次写) |
| 暴力法 | O(n²) | O(1) | 极高 |
✅ 推荐使用方法一:写操作更少,缓存友好。
📌 问题总结
- 关键洞察:利用双指针分离"已处理非零区"和"待处理区"。
- 技巧:慢指针天然记录了非零元素个数,也是后续填 0 的起点。
- 工程启示:在需要"分区"或"过滤"数组时,双指针是高效原地操作的利器。
- 延伸思考:此题可推广为"移动特定值到末尾",解法通用。
💡 一句话口诀 :快指针找非零,慢指针放位置,最后补零就搞定!