本文是根据 Datawhale 开源教程LeetCode 算法笔记(Leetcode-Notes)做的笔记 https://github.com/datawhalechina/leetcode-notes,主要记录学习过程中的一些重要的是知识点。
数组基础知识
1. 定义
| 数组(Array):一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。
特点:
- 占用连续内存空间
- 存储的数据类型相同
- 每个元素占用的内存大小相同
- 是用来实现线性表的顺序存储结构的基础
- 也可以看成是使用了顺序存储结构的线性表的一种实现方式
为什么数组可以根据下标随机访问元素?
答:这是因为数组占用的是连续的存储空间,每个数据元素都有对应的下标索引和内存地址,根据数组的首地址,就可以推导出其他任何下标索引的地址
寻址公式如下:下标 i i i 对应的数据元素地址 = 数据首地址 + i i i × 单个数据元素所占内存大小。
2. 多维数组
也被称为数组中的数组,因为相当于一维数组里的元素是另外一个一维数组。二维数组的第一维度表示行,第二维度表示列。
3.不同编程语言中数组的实现
3.1 C / C++
C / C++ 中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如:
int arr[3][4] = {
{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
3.2 JAVA中
Java 中的数组跟数据结构定义中的数组不太一样。 Java 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如:
int[][] arr = new int[3][]{ {1,2,3}, {4,5}, {6,7,8,9}};
3.3 python中
原生 Python 中其实没有数组的概念,而是使用了类似 Java 中的 ArrayList 容器类数据结构,叫做列表
。通常我们把列表来作为 Python 中的数组使用。Python 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如:
arr = ['python', 'java', ['asp', 'php'], 'c']
4. 数组的基本操作
4.1 访问元素
访问数组中第 i 个元素:由于不依赖数组中的元素个数,时间复杂度为O(1)
示例代码如下:
# 从数组 nums 中读取下标为 i 的数据元素值
def value(nums, i):
if 0 <= i <= len(nums) - 1:
print(nums[i])
arr = [0, 5, 2, 3, 7, 1, 6]
value(arr, 3)
4.2 修改元素(改)
将数组中第 i 个元素值改为 val:就是将第 i 个元素值赋值为 val, 由于不依赖数组中的元素个数,时间复杂度为O(1)
示例代码如下:
def change(nums, i, val):
if 0 <= i <= len(nums) - 1:
nums[i] = val
arr = [0, 5, 2, 3, 7, 1, 6]
i, val = 2, 4
change(arr, i, val)
print(arr)
4.3 查找元素(查)
查找数组中元素值为 val 的位置:在数组无序的情况下,只能通过将 val 与数组中的数据元素逐一对比的方式进行检索,也称为线性查找。建立一个基于下标的循环,每次将val 与当前数据元素 nums[i] 进行比较。在找到元素的时候返回元素下标,找不到时可以返回一个特殊值(例如 -1)。线性查找操作依赖于数组中元素个数,因此时间复杂度为 O(n)
# 从数组 nums 中查找元素值为 val 的数据元素第一次出现的位置
def find(nums, val):
for i in range(len(nums)):
if nums[i] == val:
return i
return -1
arr = [0, 5, 2, 3, 7, 1, 6]
print(find(arr, 5))
4.4 插入元素(增)
末尾添加: append() O(1)
任意位置添加: add(i,k) 位置i添加元素k O(n)
注意:如果数组尾部容量不满,则直接把 val 放在数组尾部的空闲位置,并更新数组的元素计数值。如果数组容量满了,则插入失败。不过,Python 中的 list 做了其他处理,当数组容量满了,则会开辟新的空间进行插入
arr = [0, 5, 2, 3, 7, 1, 6]
val = 4
arr.append(val)
print(arr)
arr = [0, 5, 2, 3, 7, 1, 6]
i, val = 2, 4
arr.insert(i, val)
print(arr)
4.5 删除元素(删)
末尾删除: pop() O(1)
任意位置删除: pop(i) 删除下标位置为 i 的元素 O(n)
arr = [0, 5, 2, 3, 7, 1, 6]
arr.pop()
print(arr)
arr = [0, 5, 2, 3, 7, 1, 6]
i = 3
arr.pop(i)
print(arr)
详细学习内容请查看参考内容,本篇主要作为跟人简要的总结及笔记
参考内容:
https://algo.itcharge.cn/01.Array/01.Array-Basic/01.Array-Basic/
01.02.02 练习题目(第 03 天)
1. 0066. 加一
1.1 题目大意
描述:给定一个非负整数数组,数组每一位对应整数的一位数字。
要求:计算整数加 1 1 1 后的结果。
说明:
- 1 ≤ d i g i t s . l e n g t h ≤ 100 1 \le digits.length \le 100 1≤digits.length≤100。
- 0 ≤ d i g i t s [ i ] ≤ 9 0 \le digits[i] \le 9 0≤digits[i]≤9。
示例:
- 示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123,加 1 之后为 124。
- 示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
| 考察要点:整数、字符串、列表之间的转换
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
digits = [str(i) for i in digits] #列表里的元素,整数转换成字符
s = ''.join(digits) #列表转换成字符串
num = str(int(s)+1) #转换成字符串,方便遍历每个元素
# num = int(s) #转换成整数方便运算
# num+=1
# num=str(num)
result = []
for i in num:
result.append(int(i))
return result
2. 0724. 寻找数组的中心下标
2.1 题目大意
描述:给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
要求:计算数组的中心下标 。
说明:
- 1 <= nums.length <= 104
- -1000 <= nums[i] <= 1000
示例:
- 示例 1:
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
- 示例 2:
输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。
- 示例 3:
输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。
解法一
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
l = len(nums)
for i in range(len(nums)):
if i == 0: #边界条件,首元素满足的情况
if sum(nums[1:])==0:
return 0
else:
continue
elif i == (l-1): #边界条件,最后一个满足的情况
if sum(nums[0:l-1])==0:
return l-1
else:
continue
else: #普通情况
if sum(nums[0:i])==sum(nums[i+1:]): #注意这里的右边界
return i
else:
continue
return -1
最简单的方法,不考虑效率和内存
解法二
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
pre_sum = 0
total = sum(nums)
for i in range(len(nums)):
if total - nums[i] == pre_sum*2:
return i
pre_sum+=nums[i]
return -1
从左向右遍历数组,当遍历到数组的 i 位置时,pre_sum表示 i 位置左边的元素之和
3. 0189. 轮转数组
3.1 题目大意
描述:给定一个数组 n u m s nums nums,再给定一个数字 k k k。
要求:将数组中的元素向右移动 k k k 个位置。
说明:
- 1 ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le nums.length \le 10^5 1≤nums.length≤105。
- − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 −231≤nums[i]≤231−1。
- 0 ≤ k ≤ 1 0 5 0 \le k \le 10^5 0≤k≤105。
示例:
输入:nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]
解释
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
进阶:
- 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
- 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
解法一:从后向前遍历k个元素,每次插入到列表头部
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
for _ in range(len(nums)):
nums.insert(0,nums.pop())
解法二:截取最后k个字符,拼接到头部
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# nums[:] = XX 的赋值,nums 的地址不变;nums = XX的赋值,nums 是新地址。
l = len(nums)
k = k % l
nums[:] = nums[l-k:] + nums[0:l-k]
| 注意:使用nums[:]是引用的原地址,num是新地址
解法三:翻转三次数组
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n=len(nums)
k=k%n
def swap(l,r):
while(l<r):
nums[l],nums[r]=nums[r],nums[l]
l=l+1
r=r-1
swap(0,n-k-1)
swap(n-k,n-1)
swap(0,n-1)
1. 0048. 旋转图像
1.1 题目大意
描述:给定一个 n × n n \times n n×n 大小的二维矩阵(代表图像) m a t r i x matrix matrix。
要求:将二维矩阵 m a t r i x matrix matrix 顺时针旋转 90°。
说明:
- 不能使用额外的数组空间。
- n = = m a t r i x . l e n g t h = = m a t r i x [ i ] . l e n g t h n == matrix.length == matrix[i].length n==matrix.length==matrix[i].length。
- 1 ≤ n ≤ 20 1 \le n \le 20 1≤n≤20。
- − 1000 ≤ m a t r i x [ i ] [ j ] ≤ 1000 -1000 \le matrix[i][j] \le 1000 −1000≤matrix[i][j]≤1000。
示例:
- 示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
- 示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
思路
- 第一步,沿对角交换(也就是行列转置)
- 第二步,对于每一行,进行反转
代码实现
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 沿对角线交换
l = len(matrix)
for i in range(l):
for j in range(0,i): # 这个范围确保了我们只处理矩阵的上三角部分或下三角部分,并且避免了重复的互换操作
matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j]
# 每行反转
for line in matrix:
line.reverse()
扩展
顺时针旋转90°是这样操作,那么逆时针呢?
道理都一样,只是以副对角线为主交换两侧元素!
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
# 既然副对角线为主那么i的范围就是从n-1到0啦 因为python的range是左闭右开所以是n-1和-1
for i in range(n-1,-1):
# 注意这里j的范围 如果j的范围也是0到n-1那么会出现交换后又交换回来 等于没有交换
for j in range(i):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
for line in matrix:
line.reverse()
2. 0054. 螺旋矩阵
2.1 题目大意
描述:给定一个 m × n m \times n m×n 大小的二维矩阵 m a t r i x matrix matrix。
要求:按照顺时针旋转的顺序,返回矩阵中的所有元素。
说明:
- m = = m a t r i x . l e n g t h m == matrix.length m==matrix.length。
- n = = m a t r i x [ i ] . l e n g t h n == matrix[i].length n==matrix[i].length。
- 1 ≤ m , n ≤ 10 1 \le m, n \le 10 1≤m,n≤10。
- − 100 ≤ m a t r i x [ i ] [ j ] ≤ 100 -100 \le matrix[i][j] \le 100 −100≤matrix[i][j]≤100。
示例:
- 示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
- 示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路
-
初始化四个边界变量 top, bottom, left, right 分别表示当前螺旋遍历的上边界、下边界、左边界和右边界。
-
初始化一个结果数组 result 用于存储遍历得到的元素。
-
使用一个循环来模拟螺旋遍历的过程,循环条件是 top <= bottom 和 left <= right,即上边界不超过下边界且左边界不超过右边界。
-
在每一轮循环中,首先从左到右遍历上边界,将遍历得到的元素添加到 result 数组中,然后将上边界向下移动一行,即增加 top。
-
接着从上到下遍历右边界,将遍历得到的元素添加到 result 数组中,然后将右边界向左缩小一列,即减小 right。
-
如果此时上边界仍然不超过下边界,继续从右到左遍历下边界,将遍历得到的元素添加到 result 数组中,然后将下边界向上移动一行,即减小 bottom。
-
最后,如果此时左边界仍然不超过右边界,继续从下到上遍历左边界,将遍历得到的元素添加到 result 数组中,然后将左边界向右扩大一列,即增加 left。
重复上述步骤,直到所有元素都被遍历完。
代码实现
解法一:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix:
return []
m, n = len(matrix), len(matrix[0])
result = []
top, bottom, left, right = 0, m - 1, 0, n - 1
while top <= bottom and left <= right:
# 从左到右遍历上边界
for j in range(left, right + 1):
result.append(matrix[top][j])
top += 1
# 从上到下遍历右边界
for i in range(top, bottom + 1):
result.append(matrix[i][right])
right -= 1
# 检查是否需要继续遍历
if top <= bottom:
# 从右到左遍历下边界
for j in range(right, left - 1, -1):
result.append(matrix[bottom][j])
bottom -= 1
if left <= right:
# 从下到上遍历左边界
for i in range(bottom, top - 1, -1):
result.append(matrix[i][left])
left += 1
return result
解法二
class Solution(object):
def spiralOrder(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
if not matrix or not matrix[0]: return []
M, N = len(matrix), len(matrix[0])
left, right, up, down = 0, N - 1, 0, M - 1
res = []
x, y = 0, 0
dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]
cur_d = 0
while len(res) != M * N:
res.append(matrix[x][y])
if cur_d == 0 and y == right:
cur_d += 1
up += 1
elif cur_d == 1 and x == down:
cur_d += 1
right -= 1
elif cur_d == 2 and y == left:
cur_d += 1
down -= 1
elif cur_d == 3 and x == up:
cur_d += 1
left += 1
cur_d %= 4
x += dirs[cur_d][0]
y += dirs[cur_d][1]
return res
3. 0498. 对角线遍历
3.1 题目大意
描述:给定一个大小为 m × n m \times n m×n 的矩阵 m a t mat mat 。
要求:以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
说明:
- m = = m a t . l e n g t h m == mat.length m==mat.length。
- n = = m a t [ i ] . l e n g t h n == mat[i].length n==mat[i].length。
- 1 ≤ m , n ≤ 1 0 4 1 \le m, n \le 10^4 1≤m,n≤104。
- 1 ≤ m × n ≤ 1 0 4 1 \le m \times n \le 10^4 1≤m×n≤104。
- − 1 0 5 ≤ m a t [ i ] [ j ] ≤ 1 0 5 -10^5 \le mat[i][j] \le 10^5 −105≤mat[i][j]≤105。
示例:
- 示例 1:
输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]
- 示例 2:
输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]
思路
-
初始化一个空的结果数组 result 用于存储遍历得到的元素。
-
遍历矩阵的对角线元素。对角线有两种方向:从左上到右下和从右上到左下。我们可以使用一个标志变量 up 来表示当前对角线遍历的方向。初始时,up 为 True,表示从左上到右下。
-
在对角线遍历过程中,根据 up 的值来确定下一个元素的坐标。如果 up 为 True,则下一个元素的坐标为 (i-1, j+1),如果 up 为 False,则下一个元素的坐标为 (i+1, j-1),其中 (i, j) 是当前元素的坐标。
-
在遍历的过程中,检查坐标是否越界,如果越界则需要调整坐标。当 up 为 True 时,如果 i 减到小于 0,说明需要向下移动一行,并将 up 设为 False。当 up 为 False 时,如果 j 减到小于 0,说明需要向右移动一列,并将 up 设为 True。
-
在每一步中,将遍历到的元素添加到 result 数组中。
-
继续遍历直到所有元素都被遍历完。
代码实现
class Solution:
def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:
m, n, ans = len(mat), len(mat[0]), []
for k in range(m + n - 1):
if not k % 2:
ans += [mat[x][k-x] for x in range(min(m - 1, k), max(-1, k - n),-1)]
else:
ans += [mat[x][k-x] for x in range(max(0, k - n + 1), min(k + 1, m))]
return ans
解法二:
if not mat:
return []
m, n = len(mat), len(mat[0])
result = []
up = True # 初始方向为从左上到右下
i, j = 0, 0 # 初始位置为左上角
while len(result) < m * n:
result.append(mat[i][j])
if up:
# 向右上移动
if i > 0 and j < n - 1:
i -= 1
j += 1
else:
up = False
if j < n - 1:
j += 1
else:
i += 1
else:
# 向左下移动
if i < m - 1 and j > 0:
i += 1
j -= 1
else:
up = True
if i < m - 1:
i += 1
else:
j += 1
return result