数组基础(第 03 ~ 04 天)

本文是根据 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 1digits.length100
  • 0 ≤ d i g i t s [ i ] ≤ 9 0 \le digits[i] \le 9 0digits[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 1nums.length105
  • − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 231nums[i]2311
  • 0 ≤ k ≤ 1 0 5 0 \le k \le 10^5 0k105

示例

输入: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 1n20
  • − 1000 ≤ m a t r i x [ i ] [ j ] ≤ 1000 -1000 \le matrix[i][j] \le 1000 1000matrix[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 1m,n10
  • − 100 ≤ m a t r i x [ i ] [ j ] ≤ 100 -100 \le matrix[i][j] \le 100 100matrix[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]

思路

  1. 初始化四个边界变量 top, bottom, left, right 分别表示当前螺旋遍历的上边界、下边界、左边界和右边界。

  2. 初始化一个结果数组 result 用于存储遍历得到的元素。

  3. 使用一个循环来模拟螺旋遍历的过程,循环条件是 top <= bottom 和 left <= right,即上边界不超过下边界且左边界不超过右边界。

  4. 在每一轮循环中,首先从左到右遍历上边界,将遍历得到的元素添加到 result 数组中,然后将上边界向下移动一行,即增加 top。

  5. 接着从上到下遍历右边界,将遍历得到的元素添加到 result 数组中,然后将右边界向左缩小一列,即减小 right。

  6. 如果此时上边界仍然不超过下边界,继续从右到左遍历下边界,将遍历得到的元素添加到 result 数组中,然后将下边界向上移动一行,即减小 bottom。

  7. 最后,如果此时左边界仍然不超过右边界,继续从下到上遍历左边界,将遍历得到的元素添加到 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 1m,n104
  • 1 ≤ m × n ≤ 1 0 4 1 \le m \times n \le 10^4 1m×n104
  • − 1 0 5 ≤ m a t [ i ] [ j ] ≤ 1 0 5 -10^5 \le mat[i][j] \le 10^5 105mat[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]

思路

  1. 初始化一个空的结果数组 result 用于存储遍历得到的元素。

  2. 遍历矩阵的对角线元素。对角线有两种方向:从左上到右下和从右上到左下。我们可以使用一个标志变量 up 来表示当前对角线遍历的方向。初始时,up 为 True,表示从左上到右下。

  3. 在对角线遍历过程中,根据 up 的值来确定下一个元素的坐标。如果 up 为 True,则下一个元素的坐标为 (i-1, j+1),如果 up 为 False,则下一个元素的坐标为 (i+1, j-1),其中 (i, j) 是当前元素的坐标。

  4. 在遍历的过程中,检查坐标是否越界,如果越界则需要调整坐标。当 up 为 True 时,如果 i 减到小于 0,说明需要向下移动一行,并将 up 设为 False。当 up 为 False 时,如果 j 减到小于 0,说明需要向右移动一列,并将 up 设为 True。

  5. 在每一步中,将遍历到的元素添加到 result 数组中。

  6. 继续遍历直到所有元素都被遍历完。

代码实现

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

请添加图片描述

猜你喜欢

转载自blog.csdn.net/qq_42859625/article/details/132887937