该笔记是以b站视频为基础,所做的笔记,详细内容请查看这个视频
数据结构
数组
链表
python 中的一些常用操作
# 创建链表
linkedlist = deque()
# 添加元素
# 时间复杂度为O(1)
linkedlist.append(1)
linkedlist.append(2)
linkedlist.append(3)
print(linkedlist) # [1,2,3]
# 插入元素
# 时间复杂度O(N):插入仍然是O(1),但是这里需要用到索引的值,找到其对应索引的位置进行插入,这个寻找索引位置的操作的时间复杂度为O(n)
# insert(索引位置,插入的值)
linkedlist.insert(2, 99)
print(linkedlist) # [1,2,99,3]
# 访问元素(access)
element = linkedlist[2]
print(element) # 99
# 搜索元素,输入一个值,返回这个值在链表中的索引位置
# 时间复杂度O(n)
index = linkedlist.index(99)
print(index) # 2
# 更新元素
# 时间复杂度O(n)
linkedlist[2] = 88
print(linkedlist) # [1,2,88,3]
# 删除元素
# 时间复杂度O(n)
# del linkedlist[2] # 删除索引为2的node
linkedlist.remove(88) # 删除值为88的node
print(linkedlist)
# 获取长度
# 时间复杂度O(1),deque中创建链表时会有一个内置参数来统计链表的长度。
length = len(likedlist)
print(length) # 3
哈希表–散列表
key: value 键值对
Java–HashMap
Python–字典
key->哈希函数->内存地址->key/value对应的内存地址
会存在内存碰撞
常用操作:
# 创建哈希表
# 创建哈希表通过array
hashTable = ['']*4
# 创建哈希表通过dictionary
mapping = {}
# add element
# time complexity O(1)
hashTable[1] = 'xiaoming'
hashTable[2] = 'lihua'
mapping[1] = 'xiaoming'
mapping[2] = 'lihua'
# update element
# time complexity
hashTable[1] = 'bishi'
mapping[1] = 'bishi'
# remove element
# O(1)
hashTable[1] = ''
mapping.pop(1)
del mapping[1]
# get element
# O(1)
hashTable[3]
mapping[3]
# check key
# O(1)
3 in mapping # --> True or False
# length
# O(1)
len(mapping)
# is Empty
len(mapping) == 0
集合set
无序,不重复
set:
- HashSet
- LinklistSet
- Tree Set
HashSet: 元素–> 哈希函数 -->哈希值 --> 哈希表:
- 如果有元素:对比:如果相等,更新。如果不相等,哈希冲突–>链表法
- 如果没有元素:直接存
搜索:无哈希冲突O(1),有哈希冲突O(k)
插入:无哈希冲突O(1),有哈希冲突O(k)
删除:无哈希冲突O(1),有哈希冲突O(k)
# create set
s = set()
# add element O(1)
s.add(10)
s.add(3)
s.add(2)
s.add(2)
print(s) #{2,3,10}
# search element O(1)
2 in s
# delete element O(1)
s.remove(2)
# length O(1)
len(s)
堆
大根堆,小根堆
堆化: 一组数–> 完全二叉树–>最大堆/最小堆
堆化:O(n) 把一组无序的数加到堆里面去
添加是O(logn)
常用操作
import heapq
# creat minheap
minheap = [8,9,4]
heapq.heapify(minheap)
# add element
heapq.heappush(minheap, 10)
# peak
minheap[0]
# delete top
heapq.heappop(minheap)
# size
len(minheap)
# iteration
while len(minheap) != 0:
print(heapq.heappop(minheap)
转化成最大堆时候将列表中的数值取反
minheap = [8,9,4]转化成new_minheap = [-8,-9,-4]
再对new_minheap进行最小堆化
图
节点之间相互连接,与其他节点的关系为朋友,不像树那样的父子关系。
每个边被称为一个度,节点与节点之间的关系为边,上图例子中,每个节点有两个边(度)
有向图
权重图:边之间会有权重值
Trie:字典树(前缀树)
Trie:
- root 节点 root = Trie()
- 孩子节点:HashMap<字符为key, Trie为value>
- 结束flag:boolean isEnd,判断是否为数组里面该有的单词
- 对应的值:string val
算法
双指针
两个指针解决一道题。
普通双指针:两个指针往同一个方向移动
对撞双指针:两个指针面对面移动:有序数列的时候可以考虑一下对撞双指针
快慢双指针:慢指针+快指针
快慢指针的应用:判断环形链表
s = s.next
f = f.next.next
二分查找法
有序数列,left, right, mid = (left+right)/2
滑动窗口
目的减少while循环
a = [1,4,2,3,4,5,6] 三个为一组取最大的和。
一般想法是指针指向a[0],然后开始加上本身和后面的两个数,一直遍历到结尾。
如图
在相加过程当中,其中4,2被加了两次,为了让计算次数减少,因此用滑动窗口来解决这个问题。
数组中定长的问题。
回溯法
应用查找全部子集
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。
回溯法应用
当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。
它有“通用解题法”之美誉。
例如:寻找[1, 2, 3]的全部子集
分为长度为0,1,2,3
0:[]
1:[1], [2], [3]
2:root
- 1: 2, 3
- 2: 3
- 3
深度优先搜索(DFS)
支路搜索
广度优先搜索(BFS)
层级搜索
并查集(union find)
用索引表示数字,用索引部分的值表示该数字的根
class UnionFind:
def __init__(self):
self.root = [-1]*(row*col)
for i in range(row*col):
self.root[i] = i
def find(self, x):
if x == self.root[x]
return self.root[x]
else:
return self.find(self.root[x])
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.root[rootX] = rootY
并查集优化
Quick Find: 直接把索引部分的值改成根节点,而不是上一节点
def find(self, x):
if x == self.root[x]
return self.root[x]
else:
self.root[x] = self.find(self.root[x])
return self.root[x]
Quick Union — 权重
思想:防止树太高
权重做法:首先比较哪个树高将高的树的根节点作为union之后的根节点
def __init__(self):
self.root = [-1] * n
self.rank = [0] * n
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
if self.rank[rootx] > self.rank[rooty]:
root[rooty] = rootx
elif self.rank[rootx] < self.rank[rooty]:
root[rootx] = rooty
else:
root[rooty] = rootx
self.rank[rootx] += 1
贪心算法
每一步做的都是当前看起来最好的选择,只是局部最优
记忆化搜索
目的减少重复计算
把计算好的结果存起来,等下一次用的时候直接调用,而不需要重新计算
例:斐波那契数列
把每个值存起来
数组的索引代表F(0), F(1)…修改数组中的值来保存
动态规划
[0][0] = 1 作为初始状态
三要素:
- 初始状态
- 方程式(状态转移方程式)
- 终止状态
斐波那契,求F5
初始:F0=0, F1=1
方程式:Fn = Fn-1 + Fn-2
终止状态:F5
应用:
- 计数:有多少种方式方法:机器人从左上到右下有多少路径
- 求最值:最大值/最小值:机器人从左上到右下的最大数字之和(每个格子都有权重值)
- 求存在性:是否存在某个可能性:是否存在机器人从左到右的路径(每个格子都有通行的方向,并不是任何方向都可以通行)
递归:
- 回溯
- 分治
- DFS: DFS,栈 | BFS,队列