(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 numsfast 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 nums0=0 → skip 0,1,0,3,12
1 0 swap(1,0) → j=1 1,0,0,3,12
2 1 nums2=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 的起点。
  • 工程启示:在需要"分区"或"过滤"数组时,双指针是高效原地操作的利器。
  • 延伸思考:此题可推广为"移动特定值到末尾",解法通用。

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

相关推荐
To_OC7 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
咩咩啃树皮8 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
旖-旎8 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
鱟鲥鳚9 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
To_OC9 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn10 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹10 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
大模型码小白10 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
雪碧聊技术10 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划