当前位置:   article > 正文

Leetcode算法入门与数组丨1. 数据结构与算法简介

Leetcode算法入门与数组丨1. 数据结构与算法简介

前言

Datawhale组队学习丨9月Leetcode算法入门与数组丨打卡笔记

这篇博客以及接下来几篇将会是一个 入门型 的文章,主要是自己学习的一个记录。

内容会参考这篇笔记(很详细):LeetCode 算法笔记(Leetcode-Notes)

1 数据结构与算法

程序 = 算法 + 数据结构

  • 算法:解决问题的方式、方法或者过程。
  • 数据结构:数据的计算机表示和相应的一组操作。
  • 程序:由算法和数据结构组成,并进行具体实现。

程序设计的所要追求的是:

更加合适的数据结构 + 花费时间更少、占用空间更小的算法

LeetCode 算法笔记(Leetcode-Notes) 笔记中写道:如果把程序比喻成做菜,那么各种食材和调味料就是数据结构,而不同的做菜方法或者说不同的烹饪方式就是算法。那么色香味俱全就是做菜的追求,程序也是这样,通过合适的算法以及合适的数据结构,产生一个高质量的代码去解决问题。

1.1 数据结构

数据结构(Data Structure):带有结构特性的数据元素的集合。简言之,就是 数据的组织结构,用来组织、存储数据。用来提高计算机硬件的利用效率。

通过学习数据结构,我们可以了解和掌握计算中中的数据是以什么样的方式进行组织和存储的。

数据结构的分类方式

1. 逻辑结构

  • 集合结构:数据元素同属于一个集合,彼此互相独立,除此之外无其他关系。

在这里插入图片描述

  • 线性结构:数据元素之间是一对一关系。也就是线性结构中的数据元素(除了首尾元素)左右两侧都只有一个数据于其相邻。 数组、链表,栈、队列、哈希表,都属于线性结构类型。

在这里插入图片描述

  • 树形结构:数据元素之间是一对多的关系。

在这里插入图片描述

  • 图形结构:数据元素之间是多对多的关系。

在这里插入图片描述

2. 物理结构

  • 顺序存储结构:数据元素之间的逻辑关系通过数据元素的存储地址来反映。逻辑上相邻的数据元素在物理地址上相邻。
    • 优点:简单、易理解,且实际占用最少的存储空间。
    • 缺点: 需要占用一片地址连续的存储单元;并且存储分配要事先进行;另外对于一些操作的时间效率较低(移动、删除元素等操作)。

在这里插入图片描述

  • 链式存储结构:将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。
    链结点:每个元素所占用的若干单元组合。存放当前元素数据信息和下一元素所在链接点的指针。

    • 优点: 存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比顺序存储结构高(插入、移动、删除元素)。
    • 缺点:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链式存储结构比顺序存储结构的空间开销大。

在这里插入图片描述

1.2 算法

算法:解决问题的方法或者流程。

它不依赖于任何一种语言,可以用 自然语言、编程语言(Python、C、C++、Java 等) 描述,也可以用 伪代码、流程图 来表示。

1. 算法的基本特性

  • 输入:没有、一个或者多个输入。
  • 输出:一个或多个参数作为算法的输出。
  • 有穷性:算法必须在有限的步骤内结束。
  • 确定性:每条指令必须有明确的含义。
  • 可行性:每一步都能通过执行有限次数完成。

2. 算法追求的目标

  • 所需运行时间更少(时间复杂度更低);
  • 占用内存空间更小(空间复杂度更低)。

2 算法复杂度

2.1 算法复杂度简介

算法复杂度(Algorithm complexity):在问题的输入规模为 n n n 条件下,程序的时间使用情况空间使用情况

比较算法优劣的两种方法

  • 事后统计:计算机执行后,对运行时间和空间使用情况的记录,并挑选出好的算法。
  • 预先估算(常用):计算机执行前,对算法中步骤的时间复杂度和空间复杂度的估算,并挑选出好的算法。

问题的输入规模 n n n :算法问题输入的数据量大小。

  • 排序算法中: n n n 表示需要排序的元素数量。
  • 查找算法中: n n n 表示查找范围内的元素总数,比如数组大小、二维数组矩阵大小、字符串长度、二叉树节点数、图的节点数、图的边界点等。
  • 二进制计算相关算法中: n n n 表示二进制的展开宽度。

2.2 时间复杂度

1.时间复杂度简介

时间复杂度(Time Complexity):在问题的输入规模为 n n n 的条件下,算法运行所需要花费的时间,可以记作为 T ( n ) T(n) T(n)

度量标准:基本操作次数

什么是基本操作?

算法执行中的每一条语句。每一次基本操作都可以在常数时间内完成。

2.渐进符号

渐进符号(Asymptotic Symbol):专门用来刻画函数的增长速度的。简单来说,渐进符号只保留了 最高阶幂,忽略了一个函数中增长较慢的部分,比如 低阶幂、系数、常量。因为当问题规模变的很大时,这几部分并不能左右增长趋势,所以可以忽略掉。

  • Θ \Theta Θ 渐进紧确界符号
  • O O O 渐进上界符号
  • o o o 非渐近紧确上界符号
  • Ω \Omega Ω 渐进下界符号
  • ω \omega ω非渐近紧确下界符号

在这里插入图片描述

渐近记号 Θ \Theta Θ O O O o o o Ω \Omega Ω ω \omega ω关系

记号含义通俗理解
Θ \Theta Θ紧确界相当于"="
O O O(大欧)上界相当于"<="
o o o(小欧)非紧的上界相当于"<"
Ω \Omega Ω(大欧米伽)下界相当于">="
ω \omega ω(小欧米伽)非紧的下界相当于">"

在这里插入图片描述

3.时间复杂度计算

  • 计算算法的时间复杂度的步骤
  1. 确定基本操作:首先,需要明确算法中的基本操作是什么,这些基本操作通常是算法中执行一次所需要的时间最长的操作。

  2. 计算每个基本操作的执行次数:对于算法中的每个基本操作,需要计算它们分别会执行多少次。这通常涉及到对算法中的循环、递归等结构进行分析。

  3. 求和并简化:将每个基本操作的执行次数加总,并进行简化。通常可以根据常见的时间复杂度规则(如忽略常数项、保留最高次项等)来简化计算。

  4. 得出时间复杂度表达式:根据求和并简化后的结果,得出算法的时间复杂度表达式。常见的时间复杂度包括 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n ! ) O(n!) O(n!) O ( log ⁡ n ) O(\log n) O(logn) O ( n log ⁡ n ) O(n \log n) O(nlogn)等。

需要注意的是,时间复杂度只是对算法的时间性能进行粗略估计,它描述的是算法的增长率,而非具体的执行时间。在实际应用中,还需要考虑输入规模、硬件环境等因素来综合评估算法的效率。

  • 计算时间复杂度时原则
  1. 忽略常数项:在计算时间复杂度时,通常会忽略常数项。因为常数项对于大规模数据来说影响较小,而且不同的机器、不同的编译器等因素都会影响常数项。

  2. 保留最高次项:在计算时间复杂度时,通常只保留最高次项,因为最高次项对于大规模数据来说影响最大。

  3. 乘法法则:如果一个算法中有多个嵌套的循环,那么它们的时间复杂度应该用乘法法则计算。

  4. 加法法则:如果一个算法中有多个不同的操作,那么它们的时间复杂度应该用加法法则计算。

  5. 最坏情况分析:在计算时间复杂度时,通常会假设算法在最坏情况下的时间复杂度,因为最坏情况下的时间复杂度可以保证算法在任何情况下都不会超时。

  6. 平均情况分析:有些算法的时间复杂度与输入数据的分布情况有关,这时需要进行平均情况分析。

总之,计算时间复杂度需要遵循科学严谨的原则,同时也需要结合具体的算法和应用场景来进行综合评估。

  • 常见的时间复杂度
  1. 常数时间复杂度( O ( 1 ) O(1) O(1):无论输入规模大小,算法的执行时间都是常数级别的,即执行时间固定。
def algorithm(n):
    a = 1
    b = 2
    res = a * b + n
    return res
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 对数时间复杂度( O ( log ⁡ n ) O(\log n) O(logn):算法的执行时间与输入规模的对数成正比,常见的例子是二分查找算法
def algorithm(n):
    cnt = 1
    while cnt < n:
        cnt *= 2
    return cnt
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 线性时间复杂度( O ( n ) O(n) O(n) :算法的执行时间与输入规模成线性关系,即随着输入规模的增加,执行时间也相应增加。例如,遍历一个数组的算法。
def algorithm(n):
    sum = 0
    for i in range(n):
        sum += 1
    return sum
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 线性对数时间复杂度( O ( n log ⁡ n ) O(n \log n) O(nlogn):算法的执行时间与输入规模的对数乘以输入规模成正比,常见的例子是快速排序和归并排序等排序算法
def algorithm(n):
    cnt = 1
    res = 0
    while cnt < n:
        cnt *= 2
        for i in range(n):
            res += 1
    return res
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 平方时间复杂度( O ( n 2 ) O(n^2) O(n2):算法的执行时间与输入规模的平方成正比,例如,嵌套循环的算法。
def algorithm(n):
    res = 0
    for i in range(n):
        for j in range(n):
            res += 1
    return res
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 最佳、最坏、平均时间复杂度
  1. 最佳时间复杂度(Best Case Time Complexity):指在最理想的情况下,算法执行所需的最少时间。最佳时间复杂度表示算法在最有利的输入情况下的性能表现。

  2. 最坏时间复杂度(Worst Case Time Complexity):指在最不利的情况下,算法执行所需的最长时间。最坏时间复杂度表示算法在最差的输入情况下的性能表现。

  3. 平均时间复杂度(Average Case Time Complexity):指算法执行所需时间的平均值,考虑了所有可能的输入情况下的性能表现。平均时间复杂度需要对输入的概率分布进行分析,通常需要使用概率论或统计学的方法来计算。

2.3 空间复杂度

空间复杂度是衡量算法在执行过程中所需的额外空间或内存资源的度量。它描述的是算法所需的额外空间随着输入规模增加而增长的趋势。

通常,空间复杂度可以分为以下几种情况:

  1. 常数空间复杂度( O ( 1 ) O(1) O(1)):算法所需的额外空间是固定的,与输入规模无关。例如,只使用有限个变量的算法。

  2. 线性空间复杂度( O ( n ) O(n) O(n)):算法所需的额外空间随着输入规模线性增长。例如,需要存储输入数据的数组或列表

  3. 平方空间复杂度( O ( n 2 ) O(n^2) O(n2)):算法所需的额外空间随着输入规模的平方增长。例如,需要存储二维矩阵的算法。

  4. 指数空间复杂度( O ( 2 n ) O(2^n) O(2n)):算法所需的额外空间随着输入规模的指数增长。例如,需要存储所有可能子集的算法。

需要注意的是,空间复杂度并不仅仅取决于算法中的变量和数据结构的使用情况,还受到函数调用、递归等因素的影响。在实际应用中,除了考虑时间复杂度外,也需要关注算法的空间复杂度,以避免因为内存不足导致程序异常或效率低下。

3 总结

在这里插入图片描述

task01

2235. 两整数相加

class Solution:
    def sum(self, num1: int, num2: int) -> int:

        return num1 + num2
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
1929. 数组串联

class Solution:
    def getConcatenation(self, nums: List[int]) -> List[int]:
        return nums + nums

  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

771. 宝石与石头

class Solution:
    def numJewelsInStones(self, jewels: str, stones: str) -> int:
        jewels_dic = dict()
        for i in jewels:
            jewels_dic[i] = 1

        count = 0

        for j in stones:
            if j in jewels_dic:
                count += 1
        
        return count

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

task02

1480. 一维数组的动态和

class Solution:
    def runningSum(self, nums: List[int]) -> List[int]:
        n = len(nums)
        runningSum = [0] * n
        runningSum[0] = nums[0]
        for i in range(1, n):
            runningSum[i] = runningSum[i-1] + nums[i]

        return runningSum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

709. 转换成小写字母

class Solution:
    def toLowerCase(self, s: str) -> str:
        ans = ''
        for i in s:
            if ord('A') <= ord(i) <= ord('Z'):
                ans += chr(ord(i)+32)
            else:
                ans += i

        return ans
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

1672. 最富有客户的资产总量

class Solution:
    def maximumWealth(self, accounts: List[List[int]]) -> int:
        n = len(accounts)
        m = 0
        for i in range (n):
            sum = 0
            for j in range (len(accounts[i])):
                 sum += accounts[i][j]  

            if sum > m:
                m = sum

        return m

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

参考文献

  • [1] https://datawhalechina.github.io/leetcode-notes/#/
  • [2] https://blog.csdn.net/so_geili/article/details/53353593

—— END ——


如果以上内容有任何错误或者不准确的地方,欢迎在下面

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/619148
推荐阅读
相关标签