【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)
代码复杂度 简单直观 需要理解指针交互
相关推荐
青石路15 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
先吃饱再说16 小时前
判断回文字符串,从一行代码到双指针优化
算法
像我这样帅的人丶你还18 小时前
Java 后端详解(五):Redis 缓存
java·后端·全栈
黄敬峰19 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
plainGeekDev20 小时前
GreenDAO → Room
android·java·kotlin
得物技术20 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
亦暖筑序1 天前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
Asize1 天前
初识DFS 与 BFS:递归、队列与图遍历
算法