【LeetCode 热题 100】移动零


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中~



【LeetCode 热题 100】移动零

📝题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序 。请注意 ,必须在不复制数组的情况下原地对数组进行操作

示例 1:

bash 复制代码
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

bash 复制代码
输入: nums = [0]
输出: [0]

进阶:你能尽量减少完成的操作次数吗?


💡提示信息

1 <= nums.length <= 10 4 10^4 104;
− 2 31 -2^{31} −231 <= numsi <= 2 31 2^{31} 231 - 1

在处理数组类算法题时,"原地修改"往往是面试中的高频考点。

虽然题目看似简单,但如果深究起来,它其实考察了我们对数组操作效率的理解。今天,我将带大家深入剖析这道题的两种主流解法:两次遍历法和双指针交换法。


解法一:两次遍历法

这是最容易想到且符合题目要求的解法。、核心思想是:既然我们要把 0 扔到最后,那不如先把非 0 的数字按顺序排好,剩下的位置自然就是给 0 的。

算法思路

  • 可以定义一个指针 j,用来记录当前"非零元素"应该存放的位置;
  • 第一遍遍历(整理非零元素):遍历数组,每当发现一个非零元素 numsi,就把它赋值给 numsj,然后 j 向后移动一位。这样遍历完后,数组的前 j 个位置就紧凑地排满了所有的非零元素;
  • 第二遍遍历(补零):此时,从索引 j 开始直到数组末尾的所有位置,原本要么是 0,要么是被我们移走的非零元素的旧位置。我们只需要把这些位置全部赋值为 0 即可;

Java代码实现如下

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {

        if(nums==null|| nums.length == 0){
            return;
        }

        int j = 0;  // 记录非零元素应该存放的位置
        
		// 第一次遍历:将所有非零元素按顺序移到数组前部
        for(int i = 0; i<nums.length; i++ ){
            if(nums[i]!=0){
                nums[j] = nums[i];
                j++;
            }
        }
		
		// 第二次遍历:将 j 之后的所有位置填充为 0
        while(j < nums.length){
        nums[j] = 0;
        j++;
    }
    }

    
}

提交代码,运行结果如下

复杂度分析

  • 时间复杂度O(n) 。 虽然有两个循环,但它们不是嵌套的,而是顺序执行的。第一个循环遍历 n 次,第二个循环最多遍历 n 次,总体是线性的。
  • 空间复杂度O(1) 。 我们只需要常数级别的额外空间来存储指针 j,完全符合原地操作的要求。

解法二:双指针法

题目中的进阶要求提到:"尽量减少完成的操作次数"。上面的解法虽然好,但在处理全是非零元素的数组时,依然会进行大量的重复赋值操作。

双指针交换法利用"交换"的技巧,能在一次遍历中完成任务,且减少了不必要的写操作

算法思路

我们定义两个指针:

  • 左指针 left: 指向下一个非零数应该去的地方,或者说是第一个 0 的位置;
  • 右指针 right: 指向当前处理到的元素,负责不断向右探索;
  • 如果 numsright 是 0,说明这个数不需要动,right 继续向右走;
  • 如果 numsright 不是 0,说明我们找到了一个需要移动的数。此时,我们将 numsright 和 numsleft 进行交换,交换后,left 向右移动一位,准备接收下一个非零数。

Java代码实现如下

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {

        if(nums==null|| nums.length == 0){
            return;
        }

        int left = 0;  // 左指针,指向非零元素应该放置的位置

        // 右指针 right 负责遍历
        for(int right = 0; right<nums.length; right++ ){
            // 如果当前元素不为 0,则与 left 指针处的元素交换
            if(nums[right]!=0){
                swap(nums,right,left);
                left++;
            }
        }
        return;
    }

    // 辅助函数:交换数组中两个位置的元素
    private void swap(int[] nums, int right, int left){
    	if(right != left){  // 优化:如果是同一个位置,不需要交换
        	int temp = nums[right];
        	nums[right]=nums[left];
        	nums[left]=temp;
    	}
    }
}

最终可以实现如下效果:

如果数组全是非零数,left 和 right 会一起走,可以不交换,无副作用;如果遇到了 0,left 会停在 0 的位置,等待 right 找到后面的非零数来和它交换。这样既把非零数提到了前面,又把 0 换到了后面。
提交代码,运行结果如下

复杂度分析

  • 时间复杂度O(n) 。只进行了一次遍历;
  • 空间复杂度O(1) 。仅使用了常数空间;

双指针交换法只有当 left 和 right 指向不同元素均为非零时才发生交换操作(交换涉及 3 次赋值)。如果数组中没有 0,该算法几乎没有额外的写操作。因此,双指针法在操作次数上通常更优。


总结对比

特性 两次遍历 (覆盖+补零) 双指针交换 (一次遍历)
核心逻辑 先覆盖非零数,再把剩余位置填 0 遇到非零数就和前面的 0 交换
遍历次数 2 次 1 次
时间复杂度 O(n) O(n)
空间复杂度 O(1) O(1)
代码复杂度 简单直观 需要理解指针交互
相关推荐
CRMEB系统商城29 分钟前
开源自建还是SaaS订阅?算一笔3年经济账
java·大数据·开发语言·开源
六点_dn1 小时前
RabbitMQ学习笔记-定义与作用
笔记·学习·rabbitmq
keyipatience2 小时前
线程栈与TLS和线程互斥
java·linux·服务器·开发语言·ubuntu
战族狼魂2 小时前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
weixin_408099672 小时前
2026 图片去水印 API 接口完全指南:一键去除图片水印(附 Python/Java/PHP/C# 示例)
java·python·php·图片处理·api调用·图片去水印·石榴智能
李剑一2 小时前
你用过网易的将军令嘛?它底层实现账号保护的原理相当简单粗暴
算法
Scott9999HH2 小时前
告别流量波动玄学!从法拉第电磁定律到底层 C/C++ 流量累积算法,深度解密工业级电磁流量计选型与开发
c语言·c++·算法
钱六两2 小时前
#3、SpringAI 接入deepSeek大模型(喂饭版)
java·spring boot·ai编程
ChaoZiLL3 小时前
我的数据结构4-栈和队列
数据结构
香辣牛肉饭3 小时前
【算法】动态规划 最长公共子序列(LCS)
经验分享·笔记·算法·动态规划