学习总结
概述
主要贴合脑图进行课程总结,并将课程中提到的需要重点记忆的代码模板,和各个知识点的LeetCode题目进行汇总。便于后面的思考与刷题。
- 重在理解,避免死磕
- 重在练习,五遍刷题
- 机器思维,避免人肉
复杂度分析
时间复杂度
- 如何理解算法时间复杂度表示法
- 主定理
- O(1) 常数复杂度:Hash表,缓存
- O(log n) 对数复杂度:二分查找,二叉搜索树
- O(n) 线性复杂度:大多数遍历
- O(n^2): 双重for循环
- O(2^n):递归
空间复杂度
- 空间换时间,升维:在大多数情况下,会选择牺牲空间复杂度,来提高时间复杂度
- 一般在算法中,更多的是考虑是否为“原地算法”,即是否需要申请额外的空间。
数组与链表
非受限线性表:指的就是不受规则限制,区别于栈和队列。
- 数组和链表是所有数据结构的基础,基本上所有数据结构都可以基于数组和链表实现。
- 数组和链表之间重点掌握区别,数组的查找时间复杂度是O(1),而链表的增加和删除时间复杂度为O(1)。
- 数组:注意防止越界
- 链表:单链表、双向链表、循环链表、静态链表(借助数组,伴随向后继节点的指针)
LeetCode
Array
Linked
栈和队列
受限线性表:需要满足一定的规则,即先进后出或者先进先出等。
- 栈和队列的重点是掌握其在特殊场合下的应用。
- 栈:先进后出,FILO。应用:浏览器前进后退、括号匹配、表达式计算。
- 队列:先进先出,FIFO。
- 队列的拓展:双端队列(两边均可入队和出队),优先队列(根据优先级来出队)。
- Java PriorityQueue 源码
LeetCode
Stack
Queue
哈希表、映射、集合
- 哈希表:又称散列表。重点需要注意哈希冲突,设计好的哈希函数可以减少重复性,但不能完全避免哈希冲突。常用拉链表(散列表+链表)解决哈希冲突,如HashMap。
- 映射:Map。K/V键值对,key不重复
- 集合:Set。key不重复
LeetCode
二叉树、二叉搜索树
- 顺序和链式结构都可以实现。
- 遍历方式:广度优先遍历(BFS)、深度优先遍历(DFS);DFS分为前序遍历(根左右)、中序遍历(左根右)、后序遍历(左右根)。重点掌握所有的模板。
- 分类:完全二叉树(除了叶子节点外,其余节点的度均为2的二叉树),满二叉树(最后一层全是叶子节点的完全二叉树),二叉搜索树等。
- 二叉搜索树的中序遍历即为有序遍历。
- 自平衡二叉搜索树(AVL)的动图演示
LeetCode
递归
- 递归模板
- 核心:递归四部曲,终止条件、执行当前层逻辑、下坠、清理当前层的状态。
public void recur(int level, int param) {
// terminator
if (level > MAX_LEVEL) {
// process result
return;
}
// process current logic
process(level, param);
// drill down
recur( level: level + 1, newParam);
// restore current status
}
LeetCode
分治、回溯
分治代码模板
def divide_conquer(problem, param1, param2, ...):
# recursion terminator
if problem is None:
print_result
return
# prepare data
data = prepare_data(problem)
subproblems = split_problem(problem, data)
# conquer subproblems
subresult1 = self.divide_conquer(subproblems[0], p1, ...)
subresult2 = self.divide_conquer(subproblems[1], p1, ...)
subresult3 = self.divide_conquer(subproblems[2], p1, ...)
…
# process and generate the final result
result = process_result(subresult1, subresult2, subresult3, …)
# revert the current level states
LeetCode
BFS、DFS
- BSF模板
- 核心:维护一个queue队列,建立一个访问记录visited集合,有时候还需要在节点处记录当前的层数level。
def BFS(graph, start, end):
visited = set()
queue = []
queue.append([start])
while queue:
node = queue.pop()
visited.add(node)
process(node)
nodes = generate_related_nodes(node)
queue.push(nodes)
# other processing work
...
- DFS模板
- 核心:DFS在非递归写法时,是维护一个stack栈
//递归写法
visited = set()
def dfs(node, visited):
if node in visited: # terminator
# already visited
return
visited.add(node)
# process current node here.
...
for next_node in node.children():
if next_node not in visited:
dfs(next_node, visited)
//非递归写法
def DFS(self, tree):
if tree.root is None:
return []
visited, stack = [], [tree.root]
while stack:
node = stack.pop()
visited.add(node)
process (node)
nodes = generate_related_nodes(node)
stack.push(nodes)
# other processing work
...
LeetCode
贪心算法
- 贪心算法是弱化版的动态规划。很多时候都得先判断是否能用贪心算法解决,再使用贪心算法。如最经典的
找零问题,在保证大的币种肯定是小的币种的倍数的情况下,可以利用贪心算法解决问题。
LeetCode
柠檬水找零
二分查找
left, right = 0, len(array) - 1
while left <= right:
mid = (left + right) / 2
if array[mid] == target:
# find the target!!
break or return result
elif array[mid] < target:
left = mid + 1
else:
right = mid - 1
注意,一般防止越界,第三行代码会改写为mid = right + (left - right) >> 1
LeetCode
动态规划
动态规划在大多数情况下和记忆化递归的时间复杂度相近,如经典的爬楼梯问题,既可以使用斐波那契数列,f(n) = f(n-1) + f(n-2),加上缓存cache的记忆化递归的方式求解,也可以使用动态规划,利用dp方程,dp[i] = dp[i-1] + dp[i-2]进行求解。
- 自底向上递推
- 核心:状态转移方程、最近重复子问题、最优子结构
LeetCode
字典树
- 字典树,又称前缀树、Trie树。主要应用于搜索时前缀单词提示。
- 核心:理解和记忆Trie树的代码模板
- Trie树模板
class Trie(object):
def __init__(self):
self.root = {}
self.end_of_word = "#"
def insert(self, word):
node = self.root
for char in word:
node = node.setdefault(char, {})
node[self.end_of_word] = self.end_of_word
def search(self, word):
node = self.root
for char in word:
if char not in node:
return False
node = node[char]
return self.end_of_word in node
def startsWith(self, prefix):
node = self.root
for char in prefix:
if char not in node:
return False
node = node[char]
return True
LeetCode
并查集
- 并查集,主要应用于解决站队问题,即朋友圈问题。
- 核心:熟悉并查集的基本操作,即初始化、查询、合并。
- 并查集模板
class UnionFind {
private int count = 0;
private int[] parent;
public UnionFind(int n) {
count = n;
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public int find(int p) {
while (p != parent[p]) {
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
parent[rootP] = rootQ;
count--;
}
}
LeetCode
学习总结
概述
复杂度分析
时间复杂度
空间复杂度
数组与链表
LeetCode
Array
Linked
栈和队列
LeetCode
Stack
Queue
哈希表、映射、集合
LeetCode
二叉树、二叉搜索树
LeetCode
递归
LeetCode
分治、回溯
分治代码模板
LeetCode
BFS、DFS
LeetCode
贪心算法
找零问题,在保证大的币种肯定是小的币种的倍数的情况下,可以利用贪心算法解决问题。LeetCode
柠檬水找零
二分查找
LeetCode
动态规划
LeetCode
字典树
LeetCode
并查集
LeetCode