算法1,移动零

283. 移动零 - 解题思路整理

题目分析

问题描述

给定一个整数数组 nums,需要将数组中所有的 0 移动到数组的末尾,同时保持非零元素的相对顺序不变。

核心要求

  1. 操作必须原地进行,不能拷贝额外的数组

  2. 尽量减少操作次数

  3. 保持非零元素的原始相对顺序

关键约束

  • 时间复杂度 O(n)

  • 空间复杂度 O(1)

示例说明

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

输入: [0,0,1]
输出: [1,0,0]

算法分析

1. 双指针划分法(分区维护)

核心思想

通过两个指针在数组内部划分三个逻辑区间,模拟快速排序的单趟划分过程,将零作为"基准值"进行处理。

指针定义

  • dest:已处理区间内最后一个非零元素的位置

    • 初始值:-1(表示还没有非零元素)
  • cur:当前遍历位置的指针,用于扫描整个数组

    • 初始值:0

区间划分

cur指针扫描过程中,数组被逻辑上划分为三个部分:

复制代码
[0, dest]        [dest+1, cur-1]    [cur, n-1]
┌─────────────┐  ┌───────────────┐  ┌─────────┐
│ 已处理的    │  │ 已处理的      │  │ 待处理  │
│ 非零元素    │  │ 零元素        │  │ 的元素  │
│ (顺序正确)  │  │ (顺序正确)    │  │         │
└─────────────┘  └───────────────┘  └─────────┘

算法流程

复制代码
初始化 dest = -1
for cur 从 0 到 n-1 遍历:
    if nums[cur] != 0:          # 遇到非零元素
        dest = dest + 1         # 扩展非零区
        swap(nums[dest], nums[cur])  # 将非零元素交换到非零区末尾
    # 遇到0时,cur++即可(0已经在零区的起始位置)

操作细节

  1. 遇到0的情况

    • 零元素已经在它应该在的位置(零区的开头)

    • 只需 cur++继续扫描,不需要额外操作

  2. 遇到非零的情况

    • 将这个非零元素交换到非零区的末尾(dest+1位置)

    • 扩展非零区的边界(dest++

    • 继续扫描(cur++

算法终止条件

  • cur == n时,所有元素都已处理完成

  • 此时 [0, dest]区间包含所有非零元素(保持原序)

  • [dest+1, n-1]区间包含所有零元素

2. 算法正确性证明

保持顺序不变

  • 每个非零元素在遇到时,都会被交换到当前非零区的末尾

  • 交换操作不会改变已处理非零元素之间的相对顺序

  • 因此所有非零元素的相对顺序得以保持

零元素位置正确

  • 零元素不需要主动移动

  • 通过与非零元素的交换,零元素自然会被"挤"到数组右侧

  • 当算法结束时,所有零元素都在数组末尾

3. 复杂度分析

时间复杂度:O(n)

  • 只需一次线性扫描,每个元素最多被访问一次

  • 每个元素最多被交换一次

空间复杂度:O(1)

  • 只使用了常数级别的额外空间(destcur两个指针)

  • 满足原地操作的要求

4. 与快速排序的关联

该方法本质上是快速排序中 partition 操作的简化版

  • 将数组元素分为两类:非零元素("小于基准")和零元素("大于等于基准")

  • dest指针维护分区的边界

  • 不需要递归调用,一次遍历即可完成

5. 关键点总结

  1. 分区思想:维护"已处理非零区"和"已处理零区"的边界

  2. 交换策略:只交换非零元素到正确位置,零元素自然到位

  3. 顺序保持:通过顺序交换保证非零元素的相对顺序

  4. 效率优化:一次遍历完成,无冗余操作

这种方法不仅解决了当前问题,其"双指针分区"的思想还可以应用于:

    1. 移除元素
    1. 颜色分类(荷兰国旗问题)
    1. 按奇偶排序数组

      等一系列数组原地修改问题

      复制代码
      public  void moveZeroes2(int[] nums) {
                  for(int cur=0,dest=-1;cur<nums.length;cur++){
                      if(nums[cur]!=0){
                          dest++;
                          int temp=nums[cur];
                          nums[cur]=nums[dest];
                          nums[dest]=temp;
                      }
                  }
              }

      错误写法

      你的代码有几个关键问题,我给你逐一分析:

      存在的问题:

      1. 循环条件错误

      复制代码
      for (int i = 0; i < nums.length-1; i++)  // 错误
    2. 你使用 i作为循环变量,但在循环体内却操作 cur指针

    3. 循环条件是 i < nums.length-1,这会漏掉最后一个元素

    4. 正确的写法是 cur < nums.lengthi < nums.length

    2. 逻辑混乱,变量使用不当

    复制代码
       if (nums[i] == 0) {  // 用 i 来判断
           cur++;           // 但更新的是 cur
       }
    1. 判断用的是 nums[i],但更新的是 cur指针

    2. 这会导致逻辑错乱,curi可能指向不同位置

    3. swap函数错误(Java值传递)

    复制代码
       private void swap(int a, int b) {
           int temp = a;
           a = b;
           b = temp;
       }
    1. 这是最严重的问题 :Java 是值传递,这个 swap函数完全无效

    2. 交换的只是局部变量的值,不会影响原始数组

    3. 必须通过数组索引直接交换元素

    复制代码
    #### 4. **dest指针越界风险**
    
        swap(nums[dest+1], nums[cur]);  // 当 dest=-1 时,dest+1=0
    1. 第一次遇到非零元素时,dest=-1dest+1=0

    2. 如果此时 cur=0,就是自己和自己交换

    3. 虽然不会出错,但逻辑不够清晰

    4. 统一指针变量 :只使用 cur作为遍历指针,或者用 i但保持一致

    5. 修正循环条件cur < nums.length而不是 length-1

    6. 直接交换数组元素:不能通过普通参数交换,必须在方法内直接操作数组

    7. 简化逻辑 :你的版本中遇到0时 cur++,遇到非零时也 cur++,这可以统一

    复制代码
    ### 你的代码思路与正确思路对比:
    
    你的思路(有问题):
    
        if (nums[i] == 0) {
            cur++;  // 遇到0,cur前进
        } else {
            swap(nums[dest+1], nums[cur]);  // 交换
            dest++;  // 扩展非零区
            cur++;   // 前进
        }
    
    正确思路:
    
        if (nums[cur] != 0) {  // 遇到非零元素
            // 交换到非零区末尾
            int temp = nums[++dest];  // 先dest+1,再使用
            nums[dest] = nums[cur];
            nums[cur] = temp;
        }
        // 遇到0什么都不用做,循环会自动cur++
    
    **核心差异**:正确版本中遇到0不需要任何操作,遇到非零时才进行交换操作。
    
    ### 修正后的正确代码:
    
        public void moveZeroes(int[] nums) {
            int dest = -1;  // 已处理区间最后一个非零元素的位置
            
            for (int cur = 0; cur < nums.length; cur++) {
                if (nums[cur] != 0) {  // 遇到非零元素
                    // 将非零元素交换到非零区末尾
                    dest++;  // 先扩展非零区
                    
                    // 直接交换数组元素,不能通过值传递的swap函数
                    int temp = nums[dest];
                    nums[dest] = nums[cur];
                    nums[cur] = temp;
                }
                // 遇到0时,cur++即可(循环会自动cur++)
            }
        }
    
    ### 更简洁的写法(一步完成交换):
    
        public void moveZeroes(int[] nums) {
            int dest = -1;
            
            for (int cur = 0; cur < nums.length; cur++) {
                if (nums[cur] != 0) {
                    // 交换 nums[++dest] 和 nums[cur]
                    int temp = nums[++dest];
                    nums[dest] = nums[cur];
                    nums[cur] = temp;
                }
            }
        }
    
    ### 关键修正点总结:
    
         public void moveZeroes(int[] nums) {
                    int cur=0;
                    int dest=-1;
                    for (int i = 0; i < nums.length-1; i++) {
                        if (nums[i] == 0) {
                            cur++;
                        }else{
                            swap(nums[dest+1],nums[cur]);
                            dest++;
                            cur++;
                        }
                    }
                }
                private void swap(int a,int b){
                    int temp=a;
                    a=b;
                    b=temp;
                }
相关推荐
shehuiyuelaiyuehao1 小时前
算法2,复写零
数据结构·算法
像污秽一样1 小时前
算法设计与分析-算法效率分析基础-习题1.1
c语言·数据结构·c++·算法
abant22 小时前
leetcode 739 单调栈模板题
算法·leetcode·职场和发展
宝贝儿好7 小时前
【强化学习实战】第十一章:Gymnasium库的介绍和使用(1)、出租车游戏代码详解(Sarsa & Q learning)
人工智能·python·深度学习·算法·游戏·机器学习
weixin_4588726110 小时前
东华复试OJ二刷复盘2
算法
Charlie_lll10 小时前
力扣解题-637. 二叉树的层平均值
算法·leetcode
爱淋雨的男人11 小时前
自动驾驶感知相关算法
人工智能·算法·自动驾驶
wen__xvn11 小时前
模拟题刷题3
java·数据结构·算法
滴滴答滴答答11 小时前
机考刷题之 6 LeetCode 169 多数元素
算法·leetcode·职场和发展