本系列是算法通关手册LeeCode的学习笔记
算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)
本系列为自用笔记,如有版权问题,请私聊我删除。
目录
1. 数组简介
1.1 数组定义
数组(Array),一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。数组是实现线性表的顺序结构的存储基础。
以整数数组为例:
如图所示,假设元素个数为 n,则数组中的每一个数据元素都有自己的下标索引。
下标索引从 0 开始,到 n - 1 结束。
数组在计算机中的表示,就是一片连续的内存单元,每个数组中的每个元素都占有一定的存储单元,每个存储单元都有自己的内存地址,并且元素之间是紧密排列的。
还可以从两个方面来了解什么是数组:
1,线性表:线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前后两个方向;
2,连续的内存空间:线性表有【顺序存储结构】和【链式存储结构】两种存储结构,其中顺序存储结构是指占用的内存空间是连续的,逻辑上相邻的数据元素之间,物理内存上的存储位置也相邻。
综合以上,数组可以看作是,【线性表】使用了【顺序存储结构】的一种实现方式。
1.2 访问数据元素
数组的一个最大特点是,可以进行随机访问。即数组可以根据下标,直接定位到某一个元素存放的位置。
计算机给数组分配了一组连续的存储空间,其中第一个元素开始的地址被称为【首地址】。每个元素都有基于首地址的下标索引和内存地址,这个内存地址通过寻址公式得到。
寻址公式:
下标 i 对应的数据元素地址 = 数据首地址 + i * 单个数据元素所占内存大小
1.3 多维数组
上面提到的是一维数组,数据元素只是单下标变量。但在实际问题中,很多信息是二维或者多维的。
可以将二维数组看作矩阵,并处理矩阵相关的问题。
在具体的编程语言中,数组这个数据结构的实现方式具有一定差别,Python中使用列表作为数组使用,下面的基本操作使用Python中的语法实现,列表存储的数据类型可以不一致,数组长度也可以不一致。
2. 数组的基本操作
数据结构的操作一般涉及到增删改查四种情况,我们主要关注实现他们时的时间复杂度,以便于后续计算。
2.1 访问元素
访问数组中下标为 i 的元素:
检查 i 的范围是否合法;
位置合法时,由给定下标得到元素的值。
# 2.1 访问元素
def value(nums, i):
if 0 <= i <= len(nums) - 1:
return nums[i]
else:
return None
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(value(arr, 5))
print(value(arr, 10))
# 6
# None
访问操作不依赖于数组中元素个数,因此时间复杂度为O(1)
2.2 查找元素
查找数组中元素值为 val 的位置:
建立一个基于下标的循环;
找到元素时,返回元素下标;
遍历完,找不到元素,返回-1。
# 2.2 查找元素
def find(nums, val):
for i in range(len(nums) - 1):
if nums[i] == val:
return i
return -1
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(find(arr, 5))
print(find(arr, 10))
# 3
# -1
从代码中可以知道,查找函数只返回 val 元素第一次出现的位置。
查找操作中,如果数组无序,则只能通过逐一对比的方式查找,称为线性查找,依赖于数组中的元素个数,因此时间复杂度为 O(n)
2.3 插入元素
1,在数组尾部插入值为 val 的元素:
如果数组尾部容量不满,则直接把 val 放在数组尾部的空闲位置,并更新数组的元素计数值;
如果数组容量已满,则插入失败;
Python中的 list 列表可以自动开辟新的空间进行插入,直接调用 append 方法即可。
# 2.3 插入元素
# 尾部插入
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(arr)
arr.append(10)
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 3, 5, 1, 6, 4, 9, 10]
在尾部插入元素不依赖于数组中元素的个数,因为时间复杂度为 O(1)
2,在数组第 i 个位置上插入值为 val 的元素:
检查下标 i 是否合法;
确定合法,把 第 len(nums) - 1 到第 i 位置上的元素位置依次向后移动(后向前);
然后在第 i 个位置赋值为 val ,并更新数组的元素计数值。
Python中使用 insert 方法。
先移动位置 6 处的元素到位置 7 ,不这样的话,前一个元素会覆盖后一个元素。
# 中间插入
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(arr)
arr.insert(2, 10)
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 10, 3, 5, 1, 6, 4, 9]
中间位置插入元素时,操作次数与数组的元素个数相关,时间复杂度为 O(n)
2.4 改变元素
将数组中第 i 个元素值改为 val:
检查位置 i 是否合法;
将位置 i 的元素值赋为 val 。
# 2.4 改变元素
def change(nums, i, val):
if 0 <= i <= len(nums) - 1:
nums[i] = val
else:
return False
arr = [0, 2, 3, 5, 1, 6, 4, 9]
change(arr, 10, 10)
print(arr)
change(arr, 4, 10)
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 3, 5, 10, 6, 4, 9]
改变元素与访问操作类似,不依赖于数组元素个数,时间复杂度为 O(1)
2.5 删除元素
1,删除尾部元素:
将元素计数值减一。
Python使用 pop 方法。
# 2.5 删除元素
# 删除尾部元素
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(arr)
arr.pop()
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 3, 5, 1, 6, 4]
不依赖于数组中的元素个数,时间复杂度为 O(1)
2,删除数组第 i 个位置上的元素:
检查下标 i 是否合法;
将第 i + 1 位置到 len(nums) - 1 位置的元素依次向左移动;
删除后,修改数组的元素计数值。
Python 中使用以下标为参数的 pop 方法。
# 删除中间位置元素
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(arr)
arr.pop(2)
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 5, 1, 6, 4, 9]
删除中间位置元素涉及移动元素,与数组中元素个数有关,时间复杂度为 O(n)
3,基于条件删除元素:
这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。
# 基于条件删除元素
arr = [0, 2, 3, 5, 1, 6, 4, 9]
print(arr)
arr.remove(1)
print(arr)
# [0, 2, 3, 5, 1, 6, 4, 9]
# [0, 2, 3, 5, 6, 4, 9]
操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,时间复杂度都为 O(n)
3. 数组基础总结
数组是最基础,最简单的数据结构。数组是实现线性表的顺序结构存储的基础。它使用一组连续的内存空间,来存储一组具有相同类型的数据。
数组的最大特点支持随机访问。访问数组元素,改变数组元素的时间复杂度为 O(1),在数组尾部插入,删除元素的时间复杂度也是 O(1),普通情况下插入,删除元素的时间复杂度为 O(n)。
4. 数组基础例题
首先处理 k ,若 k 的长度超过了 nums 的长度,则表示数组轮转超过了一圈,与第一圈没有区别;
而轮转的意思就是,将最后位置的元素取出,插入到第一个位置
解法一:
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 若轮转超过一圈,则回到一圈内处理
k = k % len(nums)
nums.insert(0, nums.pop())
解法二:
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 若轮转超过一圈,则回到一圈内处理
k = k % len(nums)
# 取出后k个元素
# 将后k个元素与其余元素调转位置拼接
nums[:] = nums[-k:] + nums[:-k]
238. 除自身以外数组的乘积 - 力扣(LeetCode)
本题的难点是在时间复杂度为 O(n) 的条件下完成,且不能使用除法
通过给出的条件可知,nums 中的元素范围在 -30 到 30 之间,因此可以对出现的数字进行计数,然后遍历计数器,对出现的数进行乘方运算即可。这样将时间复杂度降为了 O(60) 即 O(1)。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
# 元素 a 的范围为 -30 到 30
# 若将 a + 30,则元素的范围为 0 到 60
# 若 nums 中 0 的个数大于等于 2 个,则 answer 全为 0
# 若 0 的个数为 1 个, 只有那个位置的元素不为0 ,要求记录 0 所在的位置
# 最后,减去当前元素位置的个数,用次方求乘积
count = [0] * 61 # 用于计数, 下标中是 a + 30 ,元素中是a出现的次数
pos = -1 # 用于记录 0 的位置
for i in range(len(nums)):
if nums[i] == 0:
pos = i
count[nums[i] + 30] += 1 # 将nums中的元素加上三十计数
answer = []
if count[30] > 1: # 如果nums中0的个数大于1
return [0] * len(nums)
elif count[30] == 1:
for i in range(len(nums)) :
if i != pos :
answer.append(0)
else :
ans = 1
for j in range(61):
if j == 30:
continue
else:
ans *= ((j - 30) ** count[j])
answer.append(ans)
else:
for i in range(len(nums)):
ans = 1
count[nums[i] +30] -= 1
for j in range(61):
ans *= ((j - 30) ** count[j])
answer.append(ans)
count[nums[i] +30] += 1
return answer
实际上,上述算法的速度依然很慢,另有题解如下:
238. 除自身以外数组的乘积 - 力扣(LeetCode)
算法通关手册(LeetCode) | 算法通关手册(LeetCode)
原文内容在这里,如有侵权,请联系我删除。