【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)
代码复杂度 简单直观 需要理解指针交互
相关推荐
xier_ran1 分钟前
【infra之路】02_RadixAttention与KV_Cache管理
java·spring boot·spring
黑马师兄14 分钟前
RAG混合检索深度解析:让AI真正找到你要的内容
java·人工智能·ai·agent·rag·ai-native
码客日记19 分钟前
Spring Boot 配置文件敏感信息加密(Jasypt 企业级完整方案)
java·spring boot·git
无限码力22 分钟前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试
lqqjuly30 分钟前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法
吴可可1231 小时前
SolidWorks草图转三维DWG技巧
算法
凡人叶枫1 小时前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
极客先躯1 小时前
高级java每日一道面试题-2026年02月01日-实战篇[Docker]-Docker Volume 的生命周期管理是怎样的?
java·运维·docker·容器·持久化·架构图·容器卷
tyung1 小时前
Go 手写 Wait-Free SPSC 无界队列:无 CAS、无锁、泛型节点池
数据结构·后端·go
NE_STOP1 小时前
Raft算法处理细节
java