进入电子信息相关行业,掌握常用数据结构与算法是基础能力。本文是系列讲解视频的学习总结,以备后续回顾提升。

数据结构

算法的时间复杂度

1、什么是时间复杂度

指算法的执行效率,算法的执行时间与算法的输入值之间的关系。

def test(num):       # 输入num,决定循环次数,即问题规模N
    total = 0        # 执行时间a
    for i in range(num):
        total += i   # 执行时间b
    return total     # 执行时间c

O(a+N*b+c)=O(N)

2、大O表示法

常用时间复杂度案例分析

O(1)
def O1(num):
    i = num  # a
    j = num*2 # b
    return i + j

算法执行时间a+b,执行时间与num无关系,所以该算法时间复杂度为O(1)。

没有循环一般为O(1).

O(log n)
def OlogN(num):
    i = 1  # a
    while (i<num): # N
        i = i*2  # 该语句执行时间为b,i以2倍速度增长,循环次数n满足2^n =N,即$n=log_2(N)$
    return i

执行时间 b * logN,时间复杂度O(log N)。

O(n)
def ON(num):
    total = 0
    for i in range(num):  # num = N
        total += i # b
    return total

有循环结构,不为O(1),只看循环体复杂度就好了。执行时间Nb,O(Nb)=O(N)。

O(n log n)
def ONlogN(num1,num2):
    total = 0
    j = 0
    for i in range(num1): #M 
        while(j<num2): # N      (a+b)log N
            total += i + j #a
            j = j * 2   #b
    return total

O(M* (a+b)log N) = O(MlogN) -> O(NlogN)

O(n^2)
def ON2(num):
    total = 0
    for i in range(num): # N
        for j in range(num): # N
            total += i + j # a
    return total

O(aN^2)=O(n^2)

O(M+N)
def OMN(num1,num2):
    total = 0
    for i in range(num1): #M
        total += i  # a
    for j in range(num2): # N
        total += j  # b
    return total 

O(aM+bN)=O(M+N)

3、复杂度比较

Big-O Complexity Chart

O(1) < O(log N) < O(N) < O(N log N) < O(N^2) < O(2^N) < O(n!)

算法的空间复杂度

一般一个算法的时间复杂度和空间复杂度都要进行评估。

1、什么是空间复杂度

指算法的存储空间与输入值之间的关系。

def test(num):  
    total = 0   # total占存储空间设为a
    for i in range(num):
        total += i  #total只占int,存储空间不变
    return total

空间复杂度 O(1)

def test(nums):  
    array = []   # total占存储空间为list
    for num in nums:
        array.append(num)  #array占空间与nums元素个数成正比
    return array

空间复杂度 O(N)

2、常用空间复杂度

一般关注O(1) 、O(N),很少关注O(N^2)、O(logN)、O(N log N)。

实际分析查看变量,变量为常量,O(1),比如 total。如果是数组,多个数集合在一起,可能为O(N)、O(N^2)。

此外,需要关注递归,存在递归栈保存信息,一般为O(N)。

O(1) < O(N) < O(N^2)

工作面试时,可考虑两种算法都写出来,与面试官交流。实际工作中,一般考虑时间复杂度最优,因为目前存储设备成本一般较时间成本低。

数组

数组:在连续的内存空间中,存储一组相同类型的元素。

区分元素与索引,元素指数组的值,索引指该值的编号,从0开始。

区分数组的访问和搜索,a = [1,2,3],访问第二个元素:a[1]=2,访问指通过索引取值;搜索指查找元素对应的索引。

数据结构的四大基本操作,需要记住其时间复杂度。对比不同数据结构,就是考虑四种基本操作的时间复杂度。

访问 Access O(1)
搜索 Search O(N)
插入 Insert O(N)
删除 Delete O(N)
特点:适合读多写少
常用操作
“Python数组操作方法”
# 1、创建数组
# python需要注意是同类型数据
a = []

# 2、添加元素
# 时间复杂度 O(1)
a.append(1)
a.append(2)
a.append(3)
# [1,2,3]
print(a)

# 3、插入元素
# 时间复杂度 O(N)
a.insert(2,99)
# [1,2,99,3]
print(a)

# 4、访问元素
# 时间复杂度O(1)
temp = a[2]
# 99
print(temp)

# 5、更新元素
# 时间复杂度 O(1)
a[2] =88
# [1,2,88,3]
print(a)

# 6、删除元素
# remove 方法 ,时间复杂度O(N)
a.remove(88)
# [1,2,3]
print(a)
# pop方法,时间复杂度
a.pop(1)  # O(N)
# [1,3]
print(a)
a.pop()  # 删除最后一个元素,O(1)
# [1]
print(a)

# 7、获取数组长度
a = [1,2,3]
size = len(a)
# 3
print(size)

# 8、遍历数组
# 时间复杂度 O(N)
for i in a:
    print(i)
for index,element in enumerate(a):
    print("Index at",index,"is :",element)
for i in range(0,len(a)):
    print("i:",i,"element:",a[i])

# 9、查找元素
# 时间复杂度 O(N)
index = a.index(2)  # 找到元素2的索引
# 1
print(index)

# 10、数组排序
# 时间复杂度 O(N log N)
# 从小到大
a = [3,1,2]
a.sort()
#[1,2,3]
print(a)
# 从大到小
a.sort(reverse=True)  # python3.8之后没有该参数,使用a.reverse()永久地修改原有的列表、元组等可迭代对象
# [3,2,1]
print(a)
练习题

485. 最大连续 1 的个数

给定一个二进制数组 nums , 计算其中最大连续 1 的个数。

示例 1:

  • 输入:nums = [1,1,0,1,1,1]
  • 输出:3

解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.

示例 2:

  • 输入:nums = [1,0,1,1,0,1]
  • 输出:2

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1.

分析

写一个特例分析,分析所需变量,设计算法(伪代码),翻译伪代码。


# 伪代码
func(nums) -> int:
    if nums is null or nums.length == 0:
        return 0
    count = 0
    result = 0
    for ( i = 0; i < nums.length; i ++):
        if nums[i] == 1:
            count = count +1
        else :
            result = max(result,count)
            count =0
    return max(result,count)


# python 
class Solution:
    # Time Complexity: O(N)
    # Space Complexity: O(1)
    def findMaxConsecutiveOnes(self,nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if nums is None or len(nums) == 0:
            return 0
    
        count = 0
        result = 0
        for num in nums:
            if num == 1:
                count += 1
            else:
                result = max(result,count)
                count = 0
    
        return max(result,count)

链表

单端链表

单端链表由一系列节点组成,节点包含元素和指向下一个节点的next指针。双端链表不同的是多了prev指针,可以从表头到表尾或表尾到表头两个方向遍历或迭代链表。

访问 Access O(N)
搜索 Search O(N)
插入 Insert O(1)
删除 Delete O(1)
特点:适合读少写多
常用操作
“Python链表操作方法”
from collections import deque
# 1、创建链表
linkedlist = deque()

# 2、添加元素
# 时间复杂度 O(1)
linkedlist.append(1)
linkedlist.append(2)
linkedlist.append(3)
# [1,2,3]
print(linkedlist)

# 3、插入元素
# 时间复杂度 O(N)
linkedlist.insert(2,99)
# [1,2,99,3]
print(linkedlist)

# 4、访问元素
# 时间复杂度 O(N)
element = linkedlist[2]
# 99
print(element)

# 5、搜索元素
# 时间复杂度 O(N)
index = linkedlist.index(99)
# 2
print(index)

# 6、更新元素
# 时间复杂度 O(N)
linkedlist[2] = 88
# [1,2,88,3]
print(linkedlist)

# 7、删除元素
# 时间复杂度 O(N)
# del linkedlist[2] # 删除索引对应元素
linkedlist.remove(88) #删除元素
# [1,2,3]
print(linkedlist)

# 8、获取链表长度
# 时间复杂度 O(1)
length = len(linkedlist)
# 3
print(length)

练习题

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

  • 输入:head = [1,2,6,3,4,5,6], val = 6
  • 输出:[1,2,3,4,5]

示例 2:

  • 输入:head = [], val = 1
  • 输出:[]

示例 3:

  • 输入:head = [7,7,7,7], val = 7
  • 输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50
# 伪代码
func(head,val) -> linkedlist:
    dummy = listnode(0)
    dummy.next = head
    prev = dummy
    while(head != null )
        if (head.val == val):
            prev.next = head.next
            head = head.next
        else:
            prev = head
            head = head.next
    return dummy.next

class Solution:
    # N为链表大小
    # 时间复杂度 O(N)
    # 空间复杂度 O(1)
    def removeElement(self,head : ListNode,val : int) -> ListNode:
        dummy = ListNode(0)
        dummy.next = head
        prev = dummy
        while head is not None:
            if head.val == val:
                prev.next = head.next
            else:
                prev = prev.next
            head = head.next
        return dummy.next

队列

哈希表

集合

算法

双指针

二分查找

滑动窗口

递归

分治法

回溯法

深度优先搜索

宽度优先搜索

并查集

贪心

记忆化搜索

动态规划

前缀树