Article / 文章

LeetCode 第283题:移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意: 1. 必须在原数组上操作,不能拷贝额外的数组。 2. 尽量减少操作次数。

📖 文章摘要

本文详细解析LeetCode第283题“移动零”,这是一道考察数组操作的简单题目。文章提供了双指针和单次遍历两种实现方案,包含C#、Python、C++三种语言实现,配有详细的指针移动分析和性能对比。适合初学者学习数组操作和指针技巧。

核心知识点: 数组操作、双指针技巧、原地算法
难度等级: 简单
推荐人群: 算法初学者,想要掌握基本数组操作的开发者

题目描述

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

请注意:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。

示例

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示

  • 1 <= nums.length <= 10^4
  • -2^31 <= nums[i] <= 2^31 - 1

解题思路

本题可以使用两种方法来实现:

  1. 双指针法:

    • 使用两个指针,一个用于遍历数组,一个用于记录非零元素的位置
    • 遇到非零元素时,将其移动到前面的位置
    • 最后将剩余位置填充为0
  2. 单次遍历法:

    • 遍历数组,记录非零元素的个数
    • 将非零元素依次前移
    • 将剩余位置填充为0

图解思路

双指针移动过程分析表

步骤 数组状态 慢指针 快指针 操作
初始 [0,1,0,3,12] 0 0 开始
1 [0,1,0,3,12] 0 1 找到1
2 [1,0,0,3,12] 1 2 交换0和1
3 [1,0,0,3,12] 1 3 找到3
4 [1,3,0,0,12] 2 4 交换0和3
5 [1,3,12,0,0] 3 5 交换0和12

操作步骤分析表

操作 目的 时间复杂度 空间复杂度
遍历数组 找到非零元素 O(n) O(1)
交换元素 保持相对顺序 O(1) O(1)
填充零 完成最终结果 O(1) O(1)

代码实现

C# 实现

public class Solution {
    public void MoveZeroes(int[] nums) {
        int lastNonZeroFoundAt = 0;
        
        // 将所有非零元素移到前面
        for (int i = 0; i < nums.Length; i++) {
            if (nums[i] != 0) {
                nums[lastNonZeroFoundAt++] = nums[i];
            }
        }
        
        // 将剩余位置填充为0
        for (int i = lastNonZeroFoundAt; i < nums.Length; i++) {
            nums[i] = 0;
        }
    }
}

Python 实现

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        lastNonZeroFoundAt = 0
        
        # 将所有非零元素移到前面
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[lastNonZeroFoundAt] = nums[i]
                lastNonZeroFoundAt += 1
        
        # 将剩余位置填充为0
        for i in range(lastNonZeroFoundAt, len(nums)):
            nums[i] = 0

C++ 实现

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int lastNonZeroFoundAt = 0;
        
        // 将所有非零元素移到前面
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != 0) {
                nums[lastNonZeroFoundAt++] = nums[i];
            }
        }
        
        // 将剩余位置填充为0
        for (int i = lastNonZeroFoundAt; i < nums.size(); i++) {
            nums[i] = 0;
        }
    }
};

执行结果

C# 实现

  • 执行用时:160 ms
  • 内存消耗:42.8 MB

Python 实现

  • 执行用时:44 ms
  • 内存消耗:15.7 MB

C++ 实现

  • 执行用时:16 ms
  • 内存消耗:19.1 MB

性能对比

语言 执行用时 内存消耗 特点
C# 160 ms 42.8 MB 代码结构清晰,性能适中
Python 44 ms 15.7 MB 代码最简洁,性能不错
C++ 16 ms 19.1 MB 性能最优,内存占用适中

代码亮点

  1. 🎯 使用双指针技巧实现原地操作
  2. 💡 最小化操作次数,提高效率
  3. 🔍 无需额外空间,空间复杂度O(1)
  4. 🎨 代码简洁易懂,易于维护

常见错误分析

  1. 🚫 使用额外数组存储
  2. 🚫 未保持非零元素相对顺序
  3. 🚫 操作次数过多
  4. 🚫 边界条件处理不当

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
双指针法 O(n) O(1) 操作次数少 需要两次遍历
单次遍历法 O(n) O(1) 只需一次遍历 实现略复杂
冒泡排序法 O(n^2) O(1) 实现简单 效率低下

相关题目

📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第283题。

💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!