206 . 反转链表 √
24 . 两两交换链表中的节点 √
141 . 环形链表 √
142 . 环形链表 II
25 . K个一组翻转链表
注意:
堆是另一种数据结构,
不会让手写,每个语言都自己有实现。
栈:先入先出FILO,压栈出栈
队列:先入先出FIFO
常见数据结构操作的时间复杂度:
(栈和队列的插入删除查找的时间复杂度是相同的)
常见算法的时间复杂度如下:
20 . 有效的括号 √
21 . 用栈实现队列 √
225 . 用队列实现栈 √
实现机制:
小顶堆,越小的越排在前面,最小的在最顶部。
大顶堆 同理,根节点最大。
维基百科 :heap wiki 堆
下图可知,最简单的二叉堆性能不好。严格斐波那契堆 性能较好。
703 . 数据流中的第K大元素 √
239 . 滑动窗口最大值 √ 难理解啊
使用 python 直接调用现成的heapq 堆排列实现,最小堆实现
滑动窗口问题,双端队列。
705 . 粉碎糖果 kth-largest-element-in-a-stream
706 . 滑动窗口最大值
哈希表:映射表一样的东西,方便快速查找。
哈希函数:将所有的转(取模)到一个表中找到,表的索引通过 哈希函数计算出来。
哈希碰撞: 数据的数量大于表的长度时,就一定会冲突,公用同一个模具。
hashmap 和 hashset :插入删除查找O(1),但存储数据无序.
treemap 和 treeset :插入删除查找O(logN),但存储数据有序.
python 字典默认的就是 hashmap 实现的。
242 . 有效的字母异位词 √
1 . 两数之和 √
15 . 三数之和. √ 难点
18 . 四个数之和
大概念:
树子
二叉树(面试最常见)
二叉搜索树
小概念:
父亲节
孩子节点
左右孩子
根节点
二叉树
完全二叉树
链是特殊化的树
树是特殊化的图
图 地图走路径最短时会使用到。(面试很少考到)
二叉搜索树,又称为 有效二叉树、排序二叉树,空树也是二叉搜索树。具备如下性质:
(
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
)
注意:
不是左右孩子直接比较,还需要根节点的参与。
中序遍历后是升序的。
特点:
5. 平均的搜索插入删除的时间复杂度是 O(logN)
6. 最差时,二叉树是单边的,为O(N),称为 退化。
针对最差情况改良后的树有,
平衡二叉搜索树(如红黑树,AVL树 splay树)
他们在最坏情况下的都是O(logN)的,平均也是O(logN)
python java 中标准库实现的二叉树都是用红黑树实现的。
6 . 验证二叉搜索树 √
236 . 二叉树的最近公共祖先 √
235 . 二叉搜索树的最近公共祖先 √
前、中、后序遍历(实际使用的少,用的多的是深度广度优先搜索)
二叉搜索数的中序遍历 结果是 升序的。
递归是深度优先搜索的基础
递归:函数自己调用自己。
递归出口
n! 阶乘的递归实现:
def Factorial(n):
if n == 1: return 1
return n * Factorial(n-1)
递归模板:
# level 标记所在的层级,是否在递归中,第几层
def recursion(level, param1, param2, ....):
# 递归出口 一般放在开始
if level > MAX_LEVEL: #递归出口
print_resut
return
# 当前层需要处理的事情 程序逻辑 业务逻辑
process_data(level, data ...) # 当前任务处理
# 进入下一层, p1 表示给下一层的参数可能改变了
self.recursion(level+1,p1,p2,...) # 递归
# 出来之后,可能需要做的一些事,不一定需要有。
reverse_state(level) # 可能有的后续事情
斐波那契数列:递归实现会有大量的重复,子模块。
分治算法: divide & conquer
分治可以并行计算,如果有中间结果就不能用分治,可以用动态规划。
分治算法模板:(分治算法是通过递归实现的,知识需要将问题分解成小问题)
def divide_conquer(problem, param1, param2, ..):
# problem 当前需要处理的问题。
# 递归出口
if problem is None:
print_reseut
return
# 准备当前问题需要的数据
data = prepare_data(problem)
# 将大问题分成小问题
subproblems = split_problem(problem, data)
# 分别解决小问题
subresults1 = self.divide_conquer(subproblems[0],p1, ..)
subresults2 = self.divide_conquer(subproblems[1],p1, ..)
subresults3 = self.divide_conquer(subproblems[2],p1, ..)
# 利用子问题的结果来解决 当前的大问题。
result = process_result(subresult1, subresult2, result3,..)
50 . Pow(x, n) √
169 . 多数元素 √
贪心:首选最大,最多的。解决问题的数量有限。
考的较少。
因为:当前最佳,通常不是全局最佳。
适用场景:
与动态规划的不同:
贪心对每个子问题都做出选择,不能回退。
动态规划会 保存之间运算的结果,并根据需要选择,可以回退。
122 . 买卖股票的最佳时机 II √
在树、图、状态集中寻找特定的节点。
广度优先搜索:BFS
滴波浪的感觉,一层一层的处理。
每一次都遍历儿子。
图的广度优先搜索代码:
BFS树的时候不用 viisited,因为不会可能重复访问的情况。
广度有限搜索是非递归的,需要手动实现。
def BFS(graph, start, end):
quece = [] # 存放BFS后的顺序
quece.append([start])
visited.add(start) # 存放已经访问过的节点
while graph: # 有内容就继续访问
node = graph.pop() # 拿出来
viisited.add(node) # 添加进去,表示已经访问过了
process(node) # 进行操作
# 找没有被访问过的,node 的后继节点
nodes = generate_related_nodes(node)
queue.push(nodes)
.....
没有理解这个??????代码
广度优先,人更容易理解,代码难写。
深度优先搜索,代码好些,有回溯的思想(不太好理解)。回溯根,没有可以前进的方向时,就停止。
深度有限是递归实现,代码好写。
visited =set()
def dfs(node, visited):
visited.add(node)
# 这里添加处理当前节点的程序
......
for next_node in node.children():
if not next_node in visited:
dfs(next_node, visited)
102 . 二叉树的层次遍历 √
104 . 二叉树的最大深度 √
111 . 二叉树的最小深度 √
22 . 括号生成 √
搜索中有的分支是没有必要的,就不用去判断了。
51 . N皇后
52 . N皇后 II
36 . 有效的数独
37 . 解数独
对数列的要求:
数组适合,链表非常不适合。
假设数列是 递增升序的,需要从其中找到 target。
时间复杂度是 O(logN)
下面的代码背下来:
left, right = 0, len(array)-1
while left <= right:
mid = (left + right) / 2
if array[mid] == target:
return result
elif array[mid] < target:
left = mid + 1
else:
right = mid + 1
69 . x 的平方根 √
查询效率比哈希表高。
用边来存放字母,叶子节点存放单词。
空间换时间。
基本性质:
1 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3 每个节点的所有子节点包含的字符都不相同。
208. 实现 Trie (前缀树)
212. 单词搜索 II
191. 位1的个数
231 . 2的幂
338 . 比特位计数
52 . N皇后 II
斐波那契数列,实现的过程中要记住已经计算出的结果。
def fib(n):
return n if n<= 1 else fib(n-1)+fib(n-2)
DP vs 回溯(递归) vs 贪心
回溯(递归) - 有重复计算
贪心 - 永远局部最优
DP - 记录局部最优子结构、多种记录值
63 . 不同路径 II
70 . 爬楼梯
120 . 三角形最小路径和
152 . 乘积最大子序列
121 . 买卖股票的最佳时机
122 . 买卖股票的最佳时机 II
123 . 买卖股票的最佳时机 III
188 . 买卖股票的最佳时机 IV
714 . 买卖股票的最佳时机含手续费
309 . 最佳买卖股票时机含冷冻期
300 . 最长上升子序列
522 . 最长特殊序列 II
72 . 编辑距离
200 . 岛屿数量
547 . 朋友圈
146 . LRU缓存机制