概念
「数组 Array」是一种线性数据结构,其将相同类型元素存储在连续的内存空间中。我们将元素在数组中的位置称为元素的「索引 Index」
(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)。
数组常用操作
数组遍历。以下介绍两种常用的遍历方法。
/* 遍历数组 */
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
}
数组与链表的区别
常见题型
一、快慢指针
二、左右指针
1、二分查找 2、两数之和 3、反转数组 4、回文串判断
题目
给你一个 升序排列 的数组 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
在这个示例中,i 和 j 都开始在第一个元素上。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。
边界思考🤔
- 数组为空
当数组为空时,既没有慢指针 (i),也没有快指针 (j)。在这种情况下,我们应直接返回 (0)。
nums = []
- 数组只有一个元素
当数组只有一个元素时,慢指针 (i) 和快指针 (j) 都会指向这个元素。因此,应返回 (1),因为数组中只有一个唯一元素。
nums = [1]
i
j
- 快指针到达数组末尾
当快指针 (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.