Swift 数据结构与算法(二) 数组 + Leetcode26 (快慢指针)

97 阅读3分钟

概念

「数组 Array」是一种线性数据结构,其将相同类型元素存储在连续的内存空间中。我们将元素在数组中的位置称为元素的「索引 Index」

截屏2023-08-01 14.39.42.png (Krahets)

数组元素被存储在连续的内存空间中,因此计算数组元素的内存地址非常容易。给定数组首个元素的地址和某个元素的索引,我们可以使用以下公式计算得到该元素的内存地址,从而直接访问此元素(Krahets)。

数组元素的内存地址计算

优点

数组元素的高效性带来了诸多便利。例如,我们可以在 �(1) 时间内随机获取数组中的任意一个元素。

/* 随机返回一个数组元素 */
func randomAccess(nums: [Int]) -> Int {
   // 在区间 [0, nums.count) 中随机抽取一个数字
   let randomIndex = nums.indices.randomElement()!
   // 获取并返回随机元素
   let randomNum = nums[randomIndex]
   return randomNum
}
缺点

数组在初始化后长度不可变。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。

/* 扩展数组长度 */
func extend(nums: [Int], enlarge: Int) -> [Int] {
   // 初始化一个扩展长度后的数组
   var res = Array(repeating: 0, count: nums.count + enlarge)
   // 将原数组中的所有元素复制到新数组
   for i in nums.indices {
       res[i] = nums[i]
   }
   // 返回扩展后的新数组
   return res
}

数组中插入或删除元素效率低下。如果我们想要在数组中间插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引(Krahets)。

image.png

数组常用操作

数组遍历。以下介绍两种常用的遍历方法。

/* 遍历数组 */
func traverse(nums: [Int]) {
    var count = 0
    // 通过索引遍历数组
    for _ in nums.indices {
        count += 1
    }
    // 直接遍历数组
    for _ in nums {
        count += 1
    }
}

数组查找。通过遍历数组,查找数组内的指定元素,并输出对应索引。

/* 在数组中查找指定元素 */
func find(nums: [Int], target: Int) -> Int {
    for i in nums.indices {
        if nums[i] == target {
            return i
        }
    }
    return -1
}

数组与链表的区别

截屏2023-08-01 21.27.28.png

常见题型

一、快慢指针

二、左右指针

1、二分查找 2、两数之和 3、反转数组 4、回文串判断

题目

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

 

示例 1:

输入: nums = [1,1,2]
输出: 2, nums = [1,2,_]
解释: 函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

输入: nums = [0,0,1,1,1,2,2,3,3,4]
输出: 5, nums = [0,1,2,3,4]
解释: 函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

解题思路🙋🏻‍ ♀️

首先我们要返回的是数组的不重复数值的个数, 其次要按顺序排序.

这一题可以根据快慢指针进行操作

这是一个双指针技术的示意图:

nums = [0,0,1,1,1,2,2,3,3,4]
       i
       j

在这个示例中,ij 都开始在第一个元素上。i 是慢指针,而 j 是快指针。

下一步,我们移动快指针 j 到下一个元素:

nums = [0,0,1,1,1,2,2,3,3,4]
       i
         j

由于 nums[i]nums[j] 是相同的,我们只需要移动快指针 j,慢指针 i 保持不动。

这个过程一直持续到 nums[i]nums[j] 不再相等:

nums = [0,0,1,1,1,2,2,3,3,4]
       i
           j

此时,我们将 i 移动到下一个位置,并将 nums[j] 的值复制到 nums[i]

nums = [0,1,1,1,1,2,2,3,3,4]
         i
           j

这个过程会一直持续到 j 到达数组的末尾:

nums = [0,1,2,3,4,2,2,3,3,4]
                   i
                         j

最后,我们返回 i + 1,即数组中唯一元素的数量。在这个例子中,返回值是 5

边界思考🤔

  1. 数组为空

当数组为空时,既没有慢指针 (i),也没有快指针 (j)。在这种情况下,我们应直接返回 (0)。

nums = []
  1. 数组只有一个元素

当数组只有一个元素时,慢指针 (i) 和快指针 (j) 都会指向这个元素。因此,应返回 (1),因为数组中只有一个唯一元素。

nums = [1]
       i
       j
  1. 快指针到达数组末尾

当快指针 (j) 到达数组的末尾时,应停止遍历。在这个示例中,所有的唯一元素都已经复制到了数组的前部,因此应返回慢指针 (i) 的位置加 (1),这就是数组中唯一元素的数量。

nums = [0,1,2,3,4,2,2,3,3,4]
                   i
                         j

在这个例子中,唯一元素的数量是 (5)(慢指针的位置加 (1)),因此应返回 (5)。

以上就是使用快慢指针遍历数组时需要注意的边界条件的图示。希望这个解释对你有所帮助。

代码

import Foundation

class Solution {
    func removeDuplicates(_ nums: inout [Int]) -> Int {
        // 如果数组为空,直接返回0
        if nums.count <= 0 {
            return 0
        }
        // 初始化快慢指针
        var fast = 0
        var slow = 0

        // 当快指针没有遍历完数组时,继续循环
        while fast <= nums.count - 1 {
            // 如果快慢指针指向的值不相同
            if nums[fast] != nums [slow] {
                // 慢指针前进一步
                slow += 1
                // 将快指针指向的值复制到慢指针指向的位置
                nums[slow] = nums[fast]
            }
            // 快指针总是前进一步
            fast += 1
        }
        // 返回慢指针的位置加1,这就是数组中唯一元素的数量
        return slow + 1
    }
}

时空复杂度分析

O(n) 的时间复杂度和 O(1) 的空间复杂度。

引用

Krahets. “4.1.   数组 - Hello 算法.” Www.hello-Algo.com, 1 Aug. 2023, www.hello-algo.com/chapter_arr…. Accessed 1 Aug. 2023.