数据结构与算法----复习Part 3(数组基础知识与基础例题)

本文详细介绍了数组的基础概念、操作方法(包括访问、查找、插入、改变和删除),以及在LeetCode中遇到的两个典型问题的解决方案。重点讨论了时间复杂度分析。
摘要由CSDN通过智能技术生成

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

本系列为自用笔记,如有版权问题,请私聊我删除。

目录

1. 数组简介

1.1 数组定义

1.2 访问数据元素

1.3 多维数组

2. 数组的基本操作

2.1 访问元素

2.2 查找元素

2.3 插入元素

2.4 改变元素

2.5 删除元素

3. 数组基础总结

4. 数组基础例题

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. 数组基础例题

189. 轮转数组 - 力扣(LeetCode)

        首先处理 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)

原文内容在这里,如有侵权,请联系我删除。

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值