【Hot100|4-LeetCode 283. 移动零 】
预埋link:hot100博客系列写完 ✍️后 会更新灵神题单 的双指针分类题单 。
这段代码是解决 LeetCode 283. 移动零 问题的经典双指针原地解法,核心目标是将数组中的所有 0 移动到末尾,同时保持非零元素的相对顺序,且时间复杂度为 O (n)、空间复杂度为 O (1)(原地操作,不额外开辟数组)。下面从核心思路、代码逐行解析、实例演示、关键细节四个维度详细讲解:
一、问题与核心思路
问题要求
给定一个整数数组 nums,将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。必须在原数组上操作,不能拷贝额外的数组。
核心思路:双指针分区
用两个指针(left 和 i)将数组划分为两个区域,实现原地排序:
left指针:指向下一个非零元素应该放置的位置(即左半区为「已处理的非零元素」)。i指针:作为遍历指针,负责扫描整个数组寻找非零元素(即右半区为「待处理的元素」)。- 遍历过程中,一旦找到非零元素,就与
left指针位置的元素交换,然后left指针后移,确保非零元素都聚集在数组左侧,0 自然被挤到右侧。
二、代码逐行解析
java
运行
class Solution {public void moveZeroes(int[] nums) {// 1. 定义left指针,指向非零元素的"边界",初始为0(非零区域为空)int left = 0;// 2. i指针遍历整个数组,寻找非零元素for(int i = 0;i< nums.length;i++){// 3. 当当前元素不为0时,与left位置元素交换,非零区域扩大if(nums[i]!=0){// 交换nums[i]和nums[left]int temp = nums[i];nums[i] = nums[left];nums[left] = temp;// 非零区域右移,left指针后移left++;}}}
}
逐句拆解关键代码
int left = 0left初始值为 0,代表数组最左侧。此时「非零元素区域」为空,所有元素都处于「待处理区域」。
for(int i = 0;i< nums.length;i++)i从数组开头遍历到末尾,逐个检查每个元素是否为非零。
if(nums[i]!=0)- 核心判断:如果当前遍历到的元素是非零,说明它需要被放到「非零元素区域」的末尾(即
left指向的位置)。
- 核心判断:如果当前遍历到的元素是非零,说明它需要被放到「非零元素区域」的末尾(即
元素交换逻辑
- 用临时变量
temp交换nums[i]和nums[left]。交换后,nums[left]成为非零元素,「非零区域」成功扩大。
- 用临时变量
left++- 交换后,
left指针后移一位,指向下一个非零元素的待放置位置。
- 交换后,
三、实例演示(直观理解过程)
用测试用例 nums = [0, 1, 0, 3, 12] 演示每一步执行过程,清晰看到指针和数组的变化:
| 遍历步骤 | i | nums[i] | 交换前数组 | 交换后数组 | left 值变化 | 说明 |
|---|---|---|---|---|---|---|
| 初始状态 | - | - | [0, 1, 0, 3, 12] | - | 0 | left 指向初始位置,非零区域为空 |
| 第 1 次循环 | 0 | 0 | [0, 1, 0, 3, 12] | 不交换 | 保持 0 | 元素为 0,跳过 |
| 第 2 次循环 | 1 | 1 | [0, 1, 0, 3, 12] | [1, 0, 0, 3, 12] | 从 0→1 | 交换 nums [1] 和 nums [0],非零区域为 [1] |
| 第 3 次循环 | 2 | 0 | [1, 0, 0, 3, 12] | 不交换 | 保持 1 | 元素为 0,跳过 |
| 第 4 次循环 | 3 | 3 | [1, 0, 0, 3, 12] | [1, 3, 0, 0, 12] | 从 1→2 | 交换 nums [3] 和 nums [1],非零区域为 [1,3] |
| 第 5 次循环 | 4 | 12 | [1, 3, 0, 0, 12] | [1, 3, 12, 0, 0] | 从 2→3 | 交换 nums [4] 和 nums [2],非零区域为 [1,3,12] |
最终结果:[1, 3, 12, 0, 0],完全符合「非零元素相对顺序不变,0 移到末尾」的要求。
四、关键细节与优化
1. 为什么能保持非零元素的相对顺序?
因为 i 是从左到右顺序遍历的,非零元素会按遍历顺序依次交换到 left 位置,不会改变它们之间的先后关系。比如示例中 1、3、12 的顺序和原始数组一致。
2. 优化点:避免不必要的自交换
当 i == left 时(比如数组开头就是非零元素),交换操作相当于 “自己和自己交换”,没有意义。可以添加一个判断跳过这种情况:
java
运行
if(nums[i]!=0){if(i != left){ // 只有i和left不同时才交换int temp = nums[i];nums[i] = nums[left];nums[left] = temp;}left++;
}
优化后不影响结果,但减少了无意义的赋值操作,效率更优。
3. 复杂度分析
- 时间复杂度:O (n)。仅遍历数组一次,每个元素最多被交换一次,操作次数与数组长度成正比。
- 空间复杂度:O (1)。仅用了临时变量
temp,无额外开辟数组等空间,实现原地操作。
五、总结
该解法的核心是双指针分区思想,通过 left 划分非零区域,i 寻找非零元素,用交换实现原地移动。这种思路不仅适用于 “移动零”,还可迁移到类似的「数组分区问题」(如将数组分为奇数和偶数、正数和负数等),是面试中高频考察的基础算法思路。
