Swift 数据结构与算法( 四) 数组 + Leetcode283 移动零 (双指针)

78 阅读3分钟

概念

双指针题型

题目

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

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

 

示例 1:

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

示例 2:

输入: nums = [0]
输出: [0]

解题思路🙋🏻‍ ♀️

这一题也是快慢指针, 核心思路

  1. 检查fastPointer位置的元素是否为零。如果不是零,我们就将fastPointer位置的元素和slowPointer位置的元素交换,然后将slowPointer向前移动一位。
  2. 这样就可以保证在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 都被移动到了数组的末尾,同时非零元素的相对顺序保持不变。

边界思考🤔

  1. 如果数组为空(即 nums.count == 0),则函数应直接返回,因为没有任何元素需要移动。

  2. 如果数组只有一个元素, 函数可以直接返回.

代码

第一个版本.

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
}

初始状态,slowfast 都指向第一个元素:

nums:  1 -> 0 -> 3 -> 1 -> 0 -> 3 -> 12
       ^
       slowPointer, fastPointer

第一次迭代,fastPointer 指向的元素是 1,非零,所以我们交换 slowPointerfastPointer 的元素(实际上它们此时指向同一个元素,所以数组不变),然后 slowPointerfastPointer 都向前移动一位:

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,非零,所以我们交换 slowPointerfastPointer 的元素,然后 slowPointerfastPointer 都向前移动一位:

nums:  1 -> 3 -> 0 -> 1 -> 0 -> 3 -> 12
                ^     ^
                slowPointer  fastPointer

第四次迭代,fastPointer 指向的元素是 1,非零,所以我们交换 slowPointerfastPointer 的元素,然后 slowPointerfastPointer 都向前移动一位:

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,非零,所以我们交换 slowPointerfastPointer 的元素,然后 slowPointerfastPointer 都向前移动一位:

nums:  1 -> 3 -> 1 -> 3 -> 0 -> 0 -> 12
                           ^         ^
                           slowPointer  fastPointer

最后一次迭代,fastPointer 指向的元素是 12,非零,所以我们交换 slowPointerfastPointer 的元素,然后 slowPointerfastPointer 都向前移动一位:

nums:  1 -> 3 -> 1 -> 3 -> 12 -> 0 -> 0
                                 ^    ^
                                 slowPointer, fastPointer

fast 跳出循环了

时空复杂度分析

O (n)