数组定义
数组(Array):一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。
我们可以从两个方面来解释数组的定义。
- 线性表:线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前、后两个方向。数组就是一种线性表结构,此外,栈、队列、链表都是线性表结构。
- 连续的内存空间:线性表有两种存储结构:「顺序存储结构」和「链式存储结构」。其中,「顺序存储结构」是指占用的内存空间是连续的,相邻数据元素之间,物理内存上的存储位置也相邻。数组也是采用了顺序存储结构,并且存储的数据都是相同类型的。
综合这两个角度,数组就可以看做是:使用了「顺序存储结构」的「线性表」的一种实现方式。
如何随机访问数据元素
数组的一个最大特点是:可以进行随机访问。即数组可以根据下标,直接定位到某一个元素存放的位置。
那么,计算机是如何实现根据下标随机访问数组元素的?
计算机给一个数组分配了一组连续的存储空间,其中第一个元素开始的地址被称为 「首地址」。每个数据元素都有对应的下标索引和内存地址,计算机通过地址来访问数据元素。当计算机需要访问数组的某个元素时,会通过 「寻址公式」 计算出对应元素的内存地址,然后访问地址对应的数据元素。
寻址公式如下:下标 i 对应的数据元素地址 = 数据首地址 + i × 单个数据元素所占内存大小。
不同编程语言中数组的实现
在具体的编程语言中,数组这个数据结构的实现方式具有一定差别。
C / C++ 语言中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如:
int arr[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
Java 中的数组跟数据结构定义中的数组不太一样。Java 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如:
int[][] arr = new int[3][]{ {1,2,3}, {4,5}, {6,7,8,9}};
原生 Python 中其实没有数组的概念,而是使用了类似 Java 中的 ArrayList 容器类数据结构,叫做列表。通常我们把列表来作为 Python 中的数组使用。Python 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如:
arr = ['python', 'java', ['asp', 'php'], 'c']
数组的增删改查
访问数组元素、改变数组元素的时间复杂度为 O(1),在数组尾部插入、删除元素的时间复杂度也是 O(1),普通情况下插入、删除元素的时间复杂度为 O(n)。
练习
T0066. 加一
def plusOne(self, digits: List[int]) -> List[int]:
digits = [0] + digits #在数组开头加0,处理加分过程中可能发生的进位
digits[len(digits) - 1] += 1
for i in range(len(digits)-1, 0, -1):
if digits[i] != 10:
break
else:
digits[i] = 0
digits[i - 1] += 1
if digits[0] == 0:
return digits[1:]
else:
return digits
T0724. 寻找数组的中心下标
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
sum = 0
for i in range(len(nums)): #第一次遍历先求出数组全部元素和
sum += nums[i]
curr_sum = 0
for i in range(len(nums)): #第二次遍历找到左侧元素和恰好为全部元素和一半的位置
if curr_sum * 2 + nums[i] == sum:
return i
curr_sum += nums[i]
return -1
T0189. 轮转数组
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k = k % n ##处理 k 值大于数组长度 n 的情况,以避免不必要的重复旋转
self.reverse(nums, 0, n-1)
self.reverse(nums, 0, k-1)
self.reverse(nums, k, n-1)
def reverse(self, nums: List[int], left: int, right: int) -> None:
while left < right :
tmp = nums[left]
nums[left] = nums[right]
nums[right] = tmp
left += 1
right -= 1
T0048. 旋转图像
思路1:原地旋转
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
for i in range(n // 2):
for j in range((n + 1) // 2):
# 四元组交换
matrix[i][j], matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1] = matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1], matrix[i][j]
思路2:原地翻转
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
#水平翻转:沿主对角线翻转矩阵的行,使得行的首尾元素交换位置。
#对角线转置:对每一行的前半部分进行转置,即将行元素与其对应的列元素交换。
for i in range(n // 2):
for j in range(n):
matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j]
for i in range(n):
for j in range(i):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
T0054. 螺旋矩阵
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
up, down, left, right = 0, len(matrix)-1, 0, len(matrix[0])-1
ans = []
while True:
for i in range(left, right + 1):
ans.append(matrix[up][i])
up += 1
if up > down:
break
for i in range(up, down + 1):
ans.append(matrix[i][right])
right -= 1
if right < left:
break
for i in range(right, left - 1, -1):
ans.append(matrix[down][i])
down -= 1
if down < up:
break
for i in range(down, up - 1, -1):
ans.append(matrix[i][left])
left += 1
if left > right:
break
return ans
T0498. 对角线遍历
class Solution:
def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:
rows = len(mat)
cols = len(mat[0])
count = rows * cols
x, y = 0, 0
ans = []
for i in range(count):
ans.append(mat[x][y])
if (x + y) % 2 == 0:
# 最后一列
if y == cols - 1:
x += 1
# 第一行
elif x == 0:
y += 1
# 右上方向
else:
x -= 1
y += 1
else:
# 最后一行
if x == rows - 1:
y += 1
# 第一列
elif y == 0:
x += 1
# 左下方向
else:
x += 1
y -= 1
return ans