力扣解题-移动零

力扣解题-移动零

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

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

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

输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]

输出: [0]

提示:

1 <= nums.length <= 104

-231 <= nums[i] <= 231 - 1

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

Related Topics

数组

双指针


第一次解答

解题思路

核心方法:分类存储非零/零元素 + 回填原数组,通过两个列表分别存放非零元素和零元素,再按顺序将元素回填到原数组,逻辑简单直观,但存在额外内存开销和冗余操作,性能较差。

具体步骤:

  1. 初始化两个列表:numList用于存储数组中的所有非零元素,zeroList用于存储所有零元素,通过列表分类实现"分离非零和零元素"的核心目标。
  2. 遍历原数组nums,将每个元素按"是否为0"分类:非零元素加入numList,零元素加入zeroList,完成元素分类。
  3. 先遍历numList,将其中的非零元素按原有顺序依次回填到原数组nums的前半部分(下标从0开始),保证非零元素的相对顺序不变。
  4. 再遍历zeroList,将所有零元素回填到原数组numsnumList之后的位置,完成"零元素移到末尾"的要求。
  5. 最终原数组nums即为处理后的结果。

性能劣势说明

该解法虽然实现了核心功能,但不符合题目"尽量减少操作次数"的进阶要求,且内存和耗时表现差:

  1. 额外内存开销:创建两个列表存储元素,增加了O(n)的内存消耗,导致内存击败率仅5.09%;
  2. 冗余操作次数:需要三次遍历(遍历原数组分类+遍历非零列表回填+遍历零列表回填),且涉及列表的增删/访问操作,整体操作次数多,耗时击败率仅9.66%;
  3. 未充分利用"原地操作"的优化空间,本质是通过额外空间换逻辑简单,而非高效的原地调整。

执行耗时:6 ms,击败了9.66% 的Java用户

内存消耗:47.6 MB,击败了5.09% 的Java用户

java 复制代码
public void moveZeroes(int[] nums) {
        List<Integer> numList=new ArrayList<>();
        List<Integer> zeroList=new ArrayList<>();
        for (int num : nums) {
            if (num==0){
                zeroList.add(num);
            }else {
                numList.add(num);
            }
        }
        for(int i=0;i<numList.size();i++){
            nums[i]=numList.get(i);
        }
        for(int i=0;i<zeroList.size();i++){
            nums[numList.size()+i]=zeroList.get(i);
        }
    }

第二次解答

解题思路

核心方法:单指针标记非零元素位置 + 原地归位非零元素 + 末尾统一补零,完全基于原数组原地操作,消除额外内存开销,大幅减少操作次数,是更贴合题目进阶要求的高效解法。

具体步骤:

  1. 初始化两个关键变量:
    • zeroIndex:作为非零元素的目标占位指针,初始值为0,标记下一个非零元素应存放的位置;
    • zeroNum:统计数组中零元素的个数,用于后续补零操作。
  2. 遍历原数组nums的每个元素,核心逻辑是"归位非零元素":
    • 若当前元素nums[i] != 0:将该非零元素赋值到zeroIndex指向的位置(相当于把非零元素按顺序"挤"到数组前面),然后zeroIndex向后移动一位,为下一个非零元素预留位置;
    • 若当前元素nums[i] == 0:仅将zeroNum加1,不做其他操作(零元素暂时不处理,留到最后统一补零)。
  3. 遍历完成后,所有非零元素已按原有顺序排列在数组前nums.length - zeroNum个位置;此时从下标nums.length - zeroNum开始,到数组末尾的位置,统一赋值为0,完成"零元素移到末尾"的要求。
  4. 全程无额外集合创建,仅在原数组上完成操作,且仅需两次短遍历(一次归位非零元素,一次补零)。

核心优化点说明

  1. 原地操作,消除内存开销:不再创建额外列表,所有操作基于原数组,内存消耗大幅降低;
  2. 减少操作次数:仅一次遍历完成所有非零元素的归位,补零阶段仅遍历零元素个数的位置,相比第一次解答的三次遍历,操作次数减少约1/3;
  3. 无冗余数据拷贝:通过指针直接赋值非零元素,避免列表增删、访问的额外开销,是耗时从6ms降至1ms的核心原因;
  4. 完全满足题目"保持非零元素相对顺序"和"原地操作"的核心要求,同时贴合"尽量减少操作次数"的进阶目标。

执行耗时:1 ms,击败了99.98% 的Java用户

内存消耗:47.3 MB,击败了5.09% 的Java用户

java 复制代码
public void moveZeroes(int[] nums) {
        //假定位置0所在的位置
        int zeroIndex=0;
        int zeroNum=0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0){
                nums[zeroIndex]=nums[i];
                zeroIndex++;
            }else {
                zeroNum++;
            }
       }
        for(int i=nums.length-zeroNum;i<nums.length;i++){
            nums[i]=0;
        }
    }

总结

  1. 第一次解答通过"列表分类+回填"实现功能,逻辑简单但存在额外内存开销和冗余遍历,性能较差;
  2. 第二次解答采用"单指针归位非零元素+末尾补零"的原地操作思路,消除额外空间、减少操作次数,是高效且贴合题目要求的最优解;
  3. 本题的核心优化方向是:放弃"额外空间换简单逻辑",转而通过指针技巧实现原地操作,减少无效遍历和数据拷贝
相关推荐
涡能增压发动积1 天前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
Wenweno0o1 天前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
小O的算法实验室1 天前
2026年ASOC,基于深度强化学习的无人机三维复杂环境分层自适应导航规划方法,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
swg3213211 天前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung1 天前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald1 天前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川1 天前
深入拆解 Java 内存模型:从原子性、可见性到有序性,彻底搞懂 happen-before 规则
java·后端
元宝骑士1 天前
FIND_IN_SET使用指南:场景、优缺点与MySQL优化策略
后端·mysql
用户31952370347711 天前
记一次 PostgreSQL WAL 日志撑爆磁盘的排查
后端
nghxni1 天前
LightESB PlatformHttp v3.0.0:JSONPath 订单转换 HTTP 路由实战
后端