概念
双指针题型
题目
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
解题思路🙋🏻 ♀️
这一题也是快慢指针, 核心思路
- 检查
fastPointer位置的元素是否为零。如果不是零,我们就将fastPointer位置的元素和slowPointer位置的元素交换,然后将slowPointer向前移动一位。 - 这样就可以保证在
slowPointer前面的所有元素都不是零,而在slowPointer后面的元素都是零。
初始状态:
nums: 0 -> 1 -> 0 -> 3 -> 12
^
slowPointer, fastPointer
第一次迭代,fastPointer 指向的元素是0,所以不做任何操作,只将 fastPointer 向后移动一位:
nums: 0 -> 1 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
第二次迭代,fastPointer 指向的元素是1,不是0,所以我们交换 slowPointer 和 fastPointer 的元素,然后将 slowPointer 和 fastPointer 都向后移动一位:
nums: 1 -> 0 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
接着继续这个过程,直到 fastPointer 到达数组的末尾。最终的结果如下:
nums: 1 -> 3 -> 12 -> 0 -> 0
^ ^
slowPointer fastPointer
在这个过程中,所有的 0 都被移动到了数组的末尾,同时非零元素的相对顺序保持不变。
边界思考🤔
-
如果数组为空(即
nums.count == 0),则函数应直接返回,因为没有任何元素需要移动。 -
如果数组只有一个元素, 函数可以直接返回.
代码
第一个版本.
import Foundation
class Solution {
// 函数开始,参数是一个整数类型的数组,inout 表示这个数组可以在函数内部被修改
func moveZeroes(_ nums: inout [Int]) {
// 如果数组的元素个数小于等于1,那么直接返回,因为没有必要进行移动
if nums.count <= 1 {
return
}
// 初始化两个指针,fast 指针用来遍历数组,slow 指针用来指向最后一个非零元素
var fast = 0
var slow = 0
// 当 fast 指针没有遍历完数组时,执行循环
while fast <= nums.count - 1 {
// 如果 fast 指针指向的元素不是0,那么将其值赋给 slow 指针指向的元素,然后 slow 指针向前移动一位
if nums[fast] != 0 {
nums[slow] = nums[fast]
slow += 1
}
// fast 指针每次循环都向前移动一位
fast += 1
}
// 当 slow 指针没有遍历完数组时,执行循环,将 slow 指针后面的元素都设为 0
while slow <= nums.count - 1 {
nums[slow] = 0
slow += 1
}
// 函数结束,无返回值
return
}
}
这个版本遍历了两次... 然后多了一个步骤, 就是这里
// 当 slow 指针没有遍历完数组时,执行循环,将 slow 指针后面的元素都设为 0
while slow <= nums.count - 1 {
nums[slow] = 0
slow += 1
}
因为交换完之后, slow 指针后面的, 并不是 0 , 还得手动修改 所以换了一种解决方案
class Solution {
// 函数开始,参数是一个整数类型的数组,inout 表示这个数组可以在函数内部被修改
func moveZeroes(_ nums: inout [Int]) {
// 如果数组的元素个数小于等于1,那么直接返回,因为没有必要进行移动
if nums.count <= 1 {
return
}
// 初始化两个指针,fast 指针用来遍历数组,slow 指针用来指向最后一个非零元素
var fast = 0
var slow = 0
// 当 fast 指针没有遍历完数组时,执行循环
while fast <= nums.count - 1 {
// 如果 fast 指针指向的元素不是0,那么将其值赋给 slow 指针指向的元素,然后 slow 指针向前移动一位
if nums[fast] != 0 {
// 交换 fast 指针指向的非零元素和 slow 指针指向的元素
nums.swapAt(slow, fast)
slow += 1
}
// fast 指针每次循环都向前移动一位
fast += 1
}
// 函数结束,无返回值
}
}
重点分析
if nums[fast] != 0 {
// 交换 fast 指针指向的非零元素和 slow 指针指向的元素
nums.swapAt(slow, fast)
slow += 1
}
初始状态,slow 和 fast 都指向第一个元素:
nums: 1 -> 0 -> 3 -> 1 -> 0 -> 3 -> 12
^
slowPointer, fastPointer
第一次迭代,fastPointer 指向的元素是 1,非零,所以我们交换 slowPointer 和 fastPointer 的元素(实际上它们此时指向同一个元素,所以数组不变),然后 slowPointer 和 fastPointer 都向前移动一位:
nums: 1 -> 0 -> 3 -> 1 -> 0 -> 3 -> 12
^^
slowPointer fastPointer
第二次迭代,fastPointer 指向的元素是 0,所以不做任何操作,只将 fastPointer 向后移动一位:
nums: 1 -> 0 -> 3 -> 1 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
第三次迭代,fastPointer 指向的元素是 3,非零,所以我们交换 slowPointer 和 fastPointer 的元素,然后 slowPointer 和 fastPointer 都向前移动一位:
nums: 1 -> 3 -> 0 -> 1 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
第四次迭代,fastPointer 指向的元素是 1,非零,所以我们交换 slowPointer 和 fastPointer 的元素,然后 slowPointer 和 fastPointer 都向前移动一位:
nums: 1 -> 3 -> 1 -> 0 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
注意 这里我们可以看到 slowPointer 已经开始持续指向 0 了.
第五次迭代,fastPointer 指向的元素是 0,所以不做任何操作,只将 fastPointer 向后移动一位:
nums: 1 -> 3 -> 1 -> 0 -> 0 -> 3 -> 12
^ ^
slowPointer fastPointer
第六次迭代,fastPointer 指向的元素是 3,非零,所以我们交换 slowPointer 和 fastPointer 的元素,然后 slowPointer 和 fastPointer 都向前移动一位:
nums: 1 -> 3 -> 1 -> 3 -> 0 -> 0 -> 12
^ ^
slowPointer fastPointer
最后一次迭代,fastPointer 指向的元素是 12,非零,所以我们交换 slowPointer 和 fastPointer 的元素,然后 slowPointer 和 fastPointer 都向前移动一位:
nums: 1 -> 3 -> 1 -> 3 -> 12 -> 0 -> 0
^ ^
slowPointer, fastPointer
fast 跳出循环了
时空复杂度分析
O (n)