问题描述
给定一个数组 nums
,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。重要的是要在不复制数组的情况下原地对数组进行操作。 leetcode链接
示例说明:
-
示例 1:
- 输入:
nums = [0,1,0,3,12]
- 输出:
[1,3,12,0,0]
- 输入:
-
示例 2:
- 输入:
nums = [0]
- 输出:
[0]
- 输入:
约束条件:
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1
解题思路
一般像数组,有序,并且将其进行数字移动的操作都可以考虑双指针法,因为通常来说都可以降低时间复杂度,如果不用双指针法,可能就需要两层循环,但是用双指针法,一次循环就可以达到两层循环的效果,非常好用!
算法方法
为了解决这个问题,我们采用双指针技巧,通过两个指针 slow
和 fast
来有效地区分非零元素和零。方法可以分为两个主要步骤:
-
第一遍扫描 - 将非零元素移动到左边:
- 使用
fast
遍历数组时,我们用slow
来跟踪下一个非零元素应该放置的位置。每次在fast
位置遇到一个非零元素,我们就将其值复制到slow
指向的位置,然后增加slow
。 - 这个步骤确保所有非零元素都移动到数组的开头,并保持它们原有的顺序。
- 使用
-
第二遍扫描 - 将剩余位置填充为零:
- 从
slow
指针的位置开始(现在指向最后一个非零元素的下一个位置),我们将数组的其余部分填充为零。
- 从
Rust 代码实现
rust
impl Solution {
pub fn move_zeroes(nums: &mut Vec<i32>) {
let mut slow = 0_usize;
let mut fast = 0_usize;
while fast < nums.len() {
if nums[fast] != 0 {
nums[slow] = nums[fast];
slow += 1;
}
fast += 1;
}
while slow < nums.len() {
nums[slow] = 0;
slow += 1;
}
}
}
复杂度分析
- 时间复杂度: 由于算法对数组进行了两次遍历,因此时间复杂度为线性时间复杂度, O(n),其中n是数组中的元素个数。
- 空间复杂度: 由于操作是就地进行的,并且只使用了恒定数量的额外空间,所以空间复杂度为 O(1)。
为什么双指针方法有效?
双指针方法之所以在"移动零"问题中特别有效,是因为它允许我们在单次或极少的遍历中完成对数组的修改,同时保持非零元素的相对顺序。这种方法使用两个指针(在本问题中是slow
和fast
)分别跟踪特定条件下的元素位置,从而达成如下目的:
-
slow
指针:标记了当前已处理序列的末尾,即下一个非零元素应该放置的位置。 -
fast
指针:用于遍历数组,寻找非零元素。 -
这种区分使得算法能够在
fast
指针寻找到非零元素时,立即将其移动到slow
指针的位置,从而将非零元素集中到数组的前端,而不需要额外的数组来存放非零元素或多次遍历数组。 -
减少赋值操作 :通过只在
fast
指针找到非零元素时才进行赋值操作,减少了不必要的赋值次数。如果fast
和slow
指向相同的元素,实际上没有必要做赋值操作,因为它们都是非零的,这避免了自赋值。 -
避免多余的遍历 :一旦所有非零元素都被移动到数组的前面,剩下的位置必定都是零。因此,第二次遍历从
slow
指针开始,直接将剩余的部分填充为零,无需再次检查这些元素是否为零。Pomelo_刘金。转载请注明原文链接。感谢!