数据结构这部分题我觉得除了树那部分,其余部分主要是记住python的各种方法使用。因为没有学过python的基础,只学过java的,python是直接上手的,所以很多方法的使用都不清楚,都是用到现学。
KMP实现:
def calNext(s2):
j, k = 0, -1
next = [0] * len(s2)
next[0] = -1
while j < len(s2) - 1:
if k == -1 or s2[j] == s2[k]:
j += 1
k += 1
next[j] = k
else:
k = next[k] # 重新进入上面循环
return next
print(calNext('abcabx')) # [-1, 0, 0, 0, 1, 2]
def KMP(s1, s2, pos = 0): # pos为开始比较的位置,一般为0
next = calNext(s2)
i, j = pos, 0
while i < len(s1) and j < len(s2):
if j == -1 or s1[i] == s2[j]:
i += 1
j += 1
else:
j = next[j]
if j == len(s2):
return i - j
else:
return -1
s1 = "acabaabaabcacaabc"
s2 = "abaabcac"
print(KMP(s1, s2)) # 5
参考博客:
KMP详解
油管最火KMP算法讲解,阿三哥的源代码!
队列和栈的基本操作可以使用数组来完成。队列是先进先出,栈是先进后出。
参考博客:数据结构:单调栈
堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。
如果数组是多维数组,数组堆化时按照数组的第0个元素排序。hq = [(points[i][0] ** 2 + points[i][1] ** 2, i) for i in range(len(points))]
参考博客:栈与堆的区别
树一般有两种解题方式:
参考博客:二叉树Python遍历和迭代方法实现
二叉树有一种特殊的遍历方式:Morris遍历(空间复杂度为 O ( 1 ) O(1) O(1))。
参考博客:Morris遍历的图示理解以及代码实现
二叉搜索树BST中序遍历时,结果依此递增。
参考博客:字典树(前缀树)
图的题多用到并查集、拓扑排序、动态规划
参考博客:
数据结构和算法:图
图的表示法与常用的转化算法
参考博客:Python之哈希表
知识点:
a ⊕ 0 = a
。a ⊕ a = 0
。a ⊕ b ⊕ a = b ⊕ a ⊕ a = b ⊕ (a ⊕ a) = b ⊕ 0 = b
。常用方法:
bin()
获取数字的二进制值ord()
输入单个字符返回其ASCII值(0-255)或unicode值chr()
输入一个整数[0,255]返回其ASCII值int("s",x)
将指定进制x的字符s转换成十进制值参考博客:
位运算介绍与经典例题总结
位掩码(BitMask)
Unicode详解
当需要枚举数组中的两个元素时,如果发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O ( N 2 ) O(N^2) O(N2) 减少至 O ( N ) O(N) O(N)。
双指针包括:
知识点:
二维数组.sort(key=lambda x: x[0])
根据指定二维数组的第一个元素升序排序二维数组.sort(key=lambda x: (x[0], -x[1]))
根据指定二维数组的第一个元素升序排序,若第一个元素相等,按第二个元素降序排序参考文章:十大经典排序算法
时间复杂度 O ( l o g N ) O(logN) O(logN),前提是有序。注意细节:
mid=(left+right)//2
改成mid=left+(right-left)//2
或者mid=left+((right-left)>>1)
(位运算速度快)可以防止left+right
过大造成的整数溢出while left < right
还是while left <= right
参考文章:二分查找及其多个变种
递归需要注意:
DFS:不撞南墙不回头,一般以栈作为基本数据结构,题型多为二叉树搜索和图搜索。
回溯 = DFS + 剪枝
层层递进,一般以队列作为基本数据结构,题型多为二叉树搜索和图搜索。
一道题如果BFS和DFS都可以做,两种解法要都会。
课程表 I 和 II 的 BFS 和 DFS 代码掌握并盲码了一遍,实际上再遇到拓扑排序的题还是做不出来(吐血…)
参考博客:拓扑排序(Topological Sorting)
每次均取最优解。贪心算法可以做的题动态规划也可以做。
参考博客:五大常用算法入门(一)——贪心算法
分而治之,归并排序原理。
参考博客:【五大常用算法】一文搞懂分治算法
一般用于解决数组中的定长问题。
类似滑动窗口协议,滑动窗口算法是指在给定特定窗口大小的数组或字符串上执行要求的操作。该技术可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。其时间复杂度为 O ( n ) O(n) O(n)。
参考博客:滑动窗口算法基本原理与实践
常采用递归实现,两个玩家要么是极大化玩家,要么是极小化玩家。极大化玩家目标是找到能获得最大收益的走法,但是必须考虑极小化玩家的走法,即需要求得对手的回手:让极大化玩家的收益最小化的走法。这个过程一直进行(求最大值、求最小值),直到达到递归函数的基线条件。对于搜索空间太深而无法抵达终局的游戏,极小化极大函数minimax()会在达到一定深度后停止。
α-β剪枝算法(alpha-beta pruning)优化极小化极大算法:
在搜索时将不会生成更优结果的棋局排除,由此来增加搜索的深度。跟踪记录递归调用minimax()间的两个值 α \alpha α和 β \beta β, α \alpha α表示搜索树当前找到的最优极大化走法的评分, β \beta β表示当前找到的对手的最优极小化走法的评分,如果 β ≤ α \beta \leq\alpha β≤α,则不值得对该搜索分支进一步搜索。这种启发式算法能显著缩小搜索空间。
参考博客:极小化极大算法
模板性极强(以岛屿数量为例):
class UnionFind:
def __init__(self, grid):
m, n = len(grid), len(grid[0]) # 获取数组的行、列数
self.count = 0 # 记录连通分量的数量
self.parent = [-1] * (m * n) # 记录根节点
self.rank = [0] * (m * n) # 记录树高
for i in range(m):
for j in range(n):
if grid[i][j] == "1":
self.parent[i * n + j] = i * n + j
self.count += 1
# 寻找元素i的根节点
def find(self,i):
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i]) # 递归实现Quick Find(路径压缩)
return self.parent[i]
# 在x和y之间添加一条连接,合并两个元素为根节点
def union(self,x,y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
if self.rank[rootx] < self.rank[rooty]: # Quick Union 权重
rootx,rooty = rooty,rootx
self.parent[rooty] = rootx
if self.rank[rootx] == self.rank[rooty]:
self.rank[rootx] += 1
self.count -= 1
# 连通分量的数量
def getCount(self):
return self.count
# 判断两节点x,y是否相连
def is_connected(self,x,y):
return self.find(x) == self.find(y)
# 添加新节点x
def add(self,x):
if x not in self.parent:
self.parent[x] = None
时间复杂度: O ( M N × α ( M N ) ) O(MN \times \alpha(MN)) O(MN×α(MN)),M 和 N 分别为行数和列数。注意当使用路径压缩(见 find 函数)和按秩合并(见数组 rank)实现并查集时,单次操作的时间复杂度为 α ( M N ) \alpha(MN) α(MN),其中 α ( x ) \alpha(x) α(x) 为反阿克曼函数,当自变量 x 的值在人类可观测的范围内(宇宙中粒子的数量)时,函数 α ( x ) \alpha(x) α(x) 的值不会超过 5,因此也可以看成是常数时间复杂度。
空间复杂度: O ( M N ) O(MN) O(MN),这是并查集需要使用的空间。
参考博客:
【算法与数据结构——并查集】
并查集(Union-Find)
并查集算法详解、
反阿克曼函数
解题步骤:
参考博客:
六大算法之三——动态规划
告别动态规划,连刷40道动规算法题,我总结了动规的套路
动态规划问题——经典模型的状态转移方程
动态规划之状态压缩dp入门
总结——01背包问题 (动态规划算法)
本质上是DFS搜索的思路,但其对搜索过的状态进行记录,从而完成对未知状态的推导,实际上也是一种DP的思想。
参考博客:
算法导论学习——动态规划之记忆化搜索
DFS–记忆化DFS–DP之间的联系
sort()
排序的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN):使用的是归并排序nums
和nums[:]
区别:用nums只是将nums指向那个副本,而nums在内存中原来地址的值没有变化,用nums[:]就会将内存中地址对应的值改变。gelthin-解释为何要用 nums1[:]结束了四个月的实习,回归力扣