目录
随笔
快速排序基于二分的思想
队列是广度优先搜索和队列优化的Bellman-Ford最短路算法的核心数据结构
深度优先搜索(depth first search,DFS),关键在于解决当下该如何做,至于下一步怎么做和当下怎么做是一样的。可用于寻找最短路径等,山洞里面找出口,放一个绳子一起走的思想
void dfs(int step){
判断边界
尝试每一种可能 for(i=1;i<n;i++){
继续下一步 dfs(step+1)
}
返回
}
深度优先遍历沿着图的某一个分支遍历到末端,然后回溯,再沿着另一条做同样的遍历,知道所有的点都被访问过。
广度优先算法:Breadth First Search ,BFS。可用于寻找最短路径等
某种程度,动态规划的暴力阶段就是回溯算法,只是有些问题可以通过最优子结构,找到重叠部分,使用DP table或者备忘录优化
DFS(depth first search)深度优先算法可以被看做回溯算法
双指针分为快慢指针和左右指针。快慢指针解决链表问题如链表是否包含环,左右指针解决数组或字符串问题,如二分搜索
动态规划
动态规划的一般形式就是求最值,是运筹学的一种最优方法,如求最长递增子序列,最小编辑距离
动态规划的核心问题是穷举
动态规划三要素:重叠子问题,最优子结构,状态转移方程。
重叠子问题解决方法:用 备忘录或者 DP table 方法优化穷举过程
回溯算法
穷举,解决决策树的遍历过程
- 路径:已经做出的选择
- 选择列表:当前可以做的选择
- 结束条件:到达决策树底层,无法再做出选择
result =[]
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
凑零钱问题-动态规划
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
coins = [1, 2, 5]
amount = 11
def coin_change(coins, amount):
def dp(n):
if n == 0: return 0
if n < 0: return -1
res = float('INF')
for coin in coins:
sub = dp(n - coin)
if sub == -1: continue
res = min(res,1+sub)
return res
return dp(amount)
print(coin_change(coins,amount))
自下而上解法
coins = [1,2,5]
amount = 11
def coinChange( coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x - coin] + 1)
# print(dp)
return dp[amount]
print(coinChange(coins,amount))
全排列问题-回溯算法
numbers = [1, 2, 3]
def pai_lie(numbers):
trace = []
def backtrace(numbers: list, trace: list):
if len(trace) == len(numbers):
print(trace)
return
for num in numbers:
if num in trace:
continue
trace.append(num)
backtrace(numbers, trace)
trace.pop()
backtrace(numbers, trace)
pai_lie(numbers)
BFS算法框架
int BFS(Node start, Node target){
// 核心数据结果:队列
queue<Node> q;
// 标记集合,避免走回头路
set<Node> visited;
// 首先将起点加入队尾
q.push(start);
// 记录扩散的次数(其实就是要求的路径长度)
int step = 0;
while(!q.empty()){
int sz = q.size();
// 将当前队列中的所有节点向其”周围(图就是邻接点,二叉树就是子节点)“扩散
for(int i = 0; i < sz; i++){
// 获取队首元素并将其出列
Node cur = q.front();
q.pop();
// 划重点,这里判断是否到达终点
if(cur is target)
return step;
// 将当前节点cur的所有相邻节点加入队尾
for(Node p : cur.adj()){
if(p not in visited){
// 加入队尾
q.push(p);
// 标记
visited.add(p);
}
}
}
// 划重点,更新步数在这里
step++;
}
}
二叉树最小高度
int minDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
// root 本身就是一层,depth 初始化为 1
int depth = 1;
while (!q.isEmpty()) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
/* 判断是否到达终点 */
if (cur.left == null && cur.right == null)
return depth;
/* 将 cur 的相邻节点加入队列 */
if (cur.left != null)
q.offer(cur.left);
if (cur.right != null)
q.offer(cur.right);
}
/* 这里增加步数 */
depth++;
}
return depth;
}
双指针套路-快慢和左右和滑动
判断链表是否有环。快慢指针相遇就有环
boolean hasCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow)
return true;
}
return false;
}
左右指针在数组中实际是指两个索引值,一般初始化为 left = 0, right = nums.length - 1
# 二分查找
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
滑动窗口算法框架
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}