(LeetCode-Hot100)283. 移动零

❌|✅|💡|📌 283. 移动零

🔗 问题简介

LeetCode 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

💡 解题思路

方法一:双指针(最优解)

核心思想:使用两个指针,一个用于遍历(快指针),一个用于记录非零元素应放置的位置(慢指针)。

步骤详解:

  1. 初始化慢指针 slow = 0
  2. 快指针 fast 从 0 遍历到数组末尾:
    • 如果 nums[fast] != 0,则将其赋值给 nums[slow],并 slow++
  3. 遍历结束后,将 slow 到数组末尾的所有位置置为 0

💡 这种方法保证了非零元素的相对顺序不变,且只遍历一次数组主体。


方法二:交换法(双指针变体)

核心思想:每当遇到非零元素,就与当前"零区"开头的元素交换。

步骤详解:

  1. 初始化指针 j = 0
  2. 遍历数组,当 nums[i] != 0 时:
    • 交换 nums[i]nums[j]
    • j++
  3. 遍历结束即完成移动。

💡 此方法无需二次填充 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] ✅

✅ 答案有效性证明

正确性依据:

  1. 非零元素顺序不变:两种方法均按原顺序将非零元素依次放置或交换,未改变其相对位置。
  2. 所有 0 移至末尾
    • 方法一:显式填充末尾为 0。
    • 方法二:每次非零元素都与最左侧的 0 交换,最终所有 0 被"挤"到右边。
  3. 原地操作:仅使用 O(1) 额外空间,满足题目要求。

边界情况验证:

  • 全为 0 → 输出全 0 ✅
  • 无 0 → 原数组不变 ✅
  • 单元素 → 正确处理 ✅

📊 复杂度分析

方法 时间复杂度 空间复杂度 写操作次数
双指针+填0 O(n) O(1) ≤ n(赋值+填0)
交换法 O(n) O(1) ≤ 2n(每次交换2次写)
暴力法 O(n²) O(1) 极高

推荐使用方法一:写操作更少,缓存友好。


📌 问题总结

  • 关键洞察:利用双指针分离"已处理非零区"和"待处理区"。
  • 技巧:慢指针天然记录了非零元素个数,也是后续填 0 的起点。
  • 工程启示:在需要"分区"或"过滤"数组时,双指针是高效原地操作的利器。
  • 延伸思考:此题可推广为"移动特定值到末尾",解法通用。

💡 一句话口诀快指针找非零,慢指针放位置,最后补零就搞定!

相关推荐
We་ct2 小时前
LeetCode 129. 求根节点到叶节点数字之和:两种解法详解(栈+递归)
前端·算法·leetcode·typescript
郝学胜-神的一滴2 小时前
Python中的“==“与“is“:深入解析与Vibe Coding时代的优化实践
开发语言·数据结构·c++·python·算法
香芋Yu2 小时前
【大模型面试突击】06_预训练与微调
面试·职场和发展
流云鹤2 小时前
动态规划01
算法·动态规划
abyyyyy1232 小时前
oj题目练习
java·前端·数据库
有一个好名字2 小时前
JAVA虚拟机-JVM
java·开发语言·jvm
SmartBrain2 小时前
技术总结:VLLM部署Qwen3模型的详解
开发语言·人工智能·算法·vllm
weixin_477271692 小时前
第四正:关键(马王堆帛书《老子》20)
人工智能·算法·图搜索算法
玄〤2 小时前
枚举问题的两大利器:深度优先搜索(DFS)与下一个排列(Next Permutation)算法详解(Java版本)(漫画解析)
java·算法·深度优先·dfs