《算法导论第二版》CLRS 以及常用算法总结
一、各部分介绍
二、学习要求
- 各主流算法可以默写。然后证明可以写出来为止
- 先学习对应方法,简单思考后。不要一直对不熟悉的算法自己去思考,先去了解,熟练,再应用
- 对于递归的方法练习一下翻译,即用非递归形式写
- 已经学过的算法,快速写,然后理解,然后背,节约时间。
1 其他算法:
1 快速排序
- 一般把起始点当作中心值base
- <= base 的点都移动到左边
- >= base 的点都移动到右边
- 注意计算顺序,先所右边还是左边会影响 l 和 r的位置,我先算r,再l。l <= base, r > base.
- 最后把base赋予l
- 然后切数组 s~l-1, r+1~e
package algorithm; public class QuickSort { public void quickSort(int [] nums, int s, int e){ if(s >= e) return; int l = s, r = e, base = nums[s]; while (l < r){ while (l < r && nums[r] > base) r--; while (l < r && nums[l]<=base) l++; if(l< r){ int temp = nums[l]; nums[l] = nums[r]; nums[r] = temp; } } nums[s]=nums[l]; nums[l] = base; quickSort(nums, s, l-1); quickSort(nums, l+1, e); } public static void main(String [] args){ } }
2 归并排序
package algorithm; public class MergeSort { public void merge(int [] nums, int s, int m, int e){ if(s >= e) return; int [] tempNums = new int[e-s+1]; int l = s, r = m+1, c = 0; while (l <= m && r <= e){ if(nums[l] <= nums[r]) tempNums[c++] = nums[l++]; else tempNums[c++] = nums[r++]; } while (l <= m) tempNums[c++] = nums[l++]; while (r <= e) tempNums[c++] = nums[r++]; for(int i = s; i <= e; i++){ nums[i] = tempNums[i-s]; } } public void mergeSort(int [] nums, int s, int e){ if(s >= e) return; int m = (s+e)/2; mergeSort(nums, s, m); mergeSort(nums, m+1,e); merge(nums, s, m, e); } }
3 堆排序
- 由后向前创建最大堆
- left = parent*2+1, right = parent*2+2,则0点为最大值
- 把堆顶和堆低值互换,调整堆,每次长度-1
package algorithm; public class HeapSort { public void headAdjust(int [] nums, int index, int n){ int parent = index; while (parent < n){ int left = parent * 2 + 1; if(left < n && left + 1 < n && nums[left] < nums[left+1]) left = left + 1; if(left < n && nums[parent] >= nums[left]) break; if(left >= n) break; int temp =nums[parent]; nums[parent] = nums[left]; nums[left] = temp; parent = left; } } public void heapSort(int [] nums){ //create heap int parent = nums.length/2-1; for(; parent >= 0; parent--){ headAdjust(nums, parent, nums.length); } for(int n = nums.length-1; n >= 1; n--){ int max = nums[0]; nums[0] = nums[n]; nums[n] = max; headAdjust(nums, 0, n); } } }
4 优先队列
-
根据堆排序实现
- extract操作,相当于堆排序并且把堆顶元素输出
- insert操作,把元素放到最后,然后不断比较parent,并且交换元素
5 链表基本操作
6 二叉搜索树
-
中序遍历排序
-
插入、删除、最小、最大、改变父节点、左右子节点
7 区间树
- 构建、查询、更新
- 左孩子2*parent, 右孩子 2*parent +1
- 构建,从低向上,递归.
- 延迟操作:增加一个标记mark。当更新或者查询范围完全包括了树的左、右范围,则mark增加要增加的值
- 当遇到查询、更新时候就pushDown。
package algorithm; public class SegmentTreeSum { class Node{ int ls, rs; int val, add; } int N = (int)1e9, M = 120010, cnt = 1; Node [] tr = new Node[M]; void pushDown(int u, int len){ tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add; tr[tr[u].ls].val += (len-len/2)*tr[u].add; tr[tr[u].rs].val += len/2 * tr[u].add; tr[u].add = 0; } void pushUp(int u){ tr[u].val = tr[tr[u].ls].val + tr[tr[u].rs].val; } void lazyCreate(int u){ if(tr[u] == null) tr[u] = new Node(); if(tr[u].ls == 0){ tr[u].ls = ++cnt; tr[tr[u].ls] = new Node(); } if(tr[u].rs == 0){ tr[u].rs = ++cnt; tr[tr[u].rs] = new Node(); } } void update(int u, int lc, int rc, int L, int R, int v){ if(L <= lc && rc <= R){ tr[u].val += (rc - lc + 1) * v; tr[u].add += v; return; } lazyCreate(u); pushDown(u, rc-lc+1); int mid = (lc + rc)/2; if(L <= mid) update(tr[u].ls, lc, mid, L, R, v); if(R > mid) update(tr[u].rs, mid+1, rc, L, R, v); pushUp(u); } int query(int u, int lc, int rc, int L, int R){ if(L <= lc && rc <= R) return tr[u].val; lazyCreate(u); pushDown(u, rc-lc+1); int mid = (lc+rc)/2, ans = 0; if(L <= mid) ans = query(tr[u].ls, lc, mid, L, R); else ans += query(tr[u].rs, mid + 1, rc, L, R); return ans; } }
最大值线段树
package algorithm; import java.util.List; public class SegmentTreeMax { int N = (int)1e9; class Node{ Node ls, rs; int val, add; } Node root = new Node(); void pushDown(Node node){ if(node.ls == null) node.ls = new Node(); if(node.rs == null) node.rs = new Node(); if(node.add == 0) return; node.ls.add = node.add; node.rs.add = node.add; node.ls.val = node.add; node.rs.val = node.add; } void pushUp(Node node){ node.val = Math.max(node.ls.val, node.rs.val); } void update(Node node, int lc, int rc, int L, int R, int v){ if(L <= lc && rc <= R){ node.add = v; node.val = v; return; } pushDown(node); int mid = (lc + rc) /2; if(L <= mid) update(node.ls, lc, mid, L, R, v); if(R > mid) update(node.rs, mid+1, rc, L, R, v); pushUp(node); } }
8 红黑树
9 拓扑排序
- DAG,所有顶点的线性序列。1)每个顶点只出现一次 2)。
- 如果存在一条边A-B,则顶点A出现在顶点B的前面。
- 应用:任务依赖解析。依赖少的排在前面,依赖多的排在后面,然后按顺序输出任务。
package algorithm; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; public class TopologicalSort { int [] inDegrees; public void initInDegrees(int [][] graph){ int v = graph.length; inDegrees = new int[v]; for(int i = 0; i < graph.length; i++){ for(int j = 0; j < graph[0].length; j++){ if(graph[i][j] != 0) inDegrees[j]++; } } } public List<Integer> topologicalSort(int [][] graph) { int [] inDegrees = new int[graph.length]; ArrayDeque<Integer> q = new ArrayDeque<>(); List<Integer> res = new ArrayList<>(); for(int i = 0; i < inDegrees.length; i++) if(inDegrees[i] == 0) q.push(i); while (!q.isEmpty()){ int s = q.pop(); res.add(s); for(int t = 0; t < graph[s].length; t++){ if(graph[s][t] != 0){ inDegrees[t]--; if(inDegrees[t] == 0) q.push(t); } } } return res; } }
10 TRIE
- 前缀树
- 跟据前缀查找、统计单词、数字等可以用此数据结构
package algorithm; import algorithm.objs.TreeNode; public class Trie { public class TrieNode{ public boolean isEnd = false; public TrieNode [] next = new TrieNode[26]; } public TrieNode root = new TrieNode(); public void insert(String word){ TrieNode cur = root; for(char ch : word.toCharArray()){ if(cur.next[ch-'a'] == null){ cur.next[ch-'a'] = new TrieNode(); } cur = cur.next[ch-'a']; } cur.isEnd = true; } public boolean find(String word){ TrieNode cur = root; for(char ch : word.toCharArray()){ if(cur.next[ch-'a'] == null) return false; cur = cur.next[ch-'a']; } return cur.isEnd; } public static void main(String [] args){ Trie trie = new Trie(); trie.insert("abc"); System.out.println(trie.find("abcd")); } }
11 Treap
- 一个BST和一个Heap。
- 通过左旋、右旋来保持树是平衡的
- 增加一个随机数RAND,来检查是否需要旋转。可以让 rand[root] > rand[left] && rand[root] > rand[right] 或者反过来
- 增加计数器来判断子孩子的数目。
package algorithm; public class Treap { public static int r= 2333; public int [] lc;//左孩子 public int [] rc;//右孩子 public int [] val;//值 public int [] ord;//优先值 public int [] siz;//以x为根节点,子节点的数目 public int [] w;//与节点相同的数目 public int sz = 0, rt = 0; public int Rand(){ r = r*232323%Integer.MAX_VALUE; return r; } public void pushUp(int x){ siz[x] = siz[lc[x]] + siz[rc[x]] + w[x]; } public void lRotate(int root){ int temp = rc[root]; rc[root] = lc[temp]; lc[temp] = root; siz[temp] = siz[root]; pushUp(root); } public void rRotate(int root){ int temp = lc[root]; lc[root] = rc[temp]; rc[temp] = root; siz[temp] = siz[root]; pushUp(root); } public void insert(int root, int x){ if(root == -1){ sz++; root = sz; siz[root] = w[root] = 1; val[root] = x; ord[root] = Rand(); return; } siz[root]++; if(val[root] == x) w[root]++; else if(val[root] < x){ insert(rc[root], x); if(ord[rc[root]] < ord[root]) lRotate(root); }else{ insert(lc[root], x); if(ord[lc[root]] < ord[root]) rRotate(root); } } boolean del(int root, int x){ if(root == -1) return false; if(val[root] == x){ if(w[root] > 1){ w[root]--; siz[root]--; return true; } if(lc[root] == -1 || rc[root] == -1){ root = lc[root] + rc[root]; return true; }else if(ord[lc[root]] < ord[rc[root]]){ rRotate(root); return del(root, x); }else { lRotate(root); return del(root, x); } }else if(val[root] < x){ boolean flag= del(rc[root], x); if(flag) siz[root]--; return flag; }else { boolean flag = del(lc[root], x); if(flag) siz[root]--; return flag; } } public int queryRank(int root, int x){ if(root == -1) return 0; if(val[root] == x) return siz[lc[root]] + 1; else if(x > val[root]) return siz[lc[root]] + w[root] + queryRank(rc[root], x); else return queryRank(lc[root], x); } int queryNum(int root, int x){ if(root == -1) return 0; if(x <= siz[lc[root]]) return queryNum(lc[root], x); else if(x > siz[lc[root]] + w[root]) return queryNum(rc[root], x - siz[lc[root]]- w[root]); else return val[root]; } }
12 凸包计算GrahamScan模板
步骤
- 找最下左点,当作极点。
- 跟据到极点的极角,由小到大排,如果极角相同按照距离由小到大排列。crossProduct(a, b) = a.x*b.y - b.x*a.y. (如果a在b的逆时针方向,crossProduct < 0)
- 排序后的点,如果最后一个极角对应多个点,则交换上面所有点的顺序,按照到距离由大到小排列(上一步是由小到大)。
- 按照对点的排序顺序放入堆 前2 个点。从第三个点开始进行下一个步骤
- 遍历排序的点
- 标记堆的top与top-1为 a ,b,当前遍历的点为c
- 判断crossProduce(a->b , c->a)看c->a是否是逆时针方向。是则加入堆,遍历下一个点;else则 pop b。到步骤5.
package algorithm; import java.util.*; import static algorithm.GrahamScan.dis; public class GrahamScan { Node apex = new Node(100, 100); class Node implements Comparable<Node>{ int x, y; public Node(int x, int y){ this.x = x; this.y = y; } public int compareTo(Node b){ int vp = vectorProduct(apex, this, b); if(vp == 0) return Double.compare(dis(apex, this), dis(apex, b)); else if(vp < 0) return -1; // a is left to b else return 1; } @Override public String toString(){ return "x " + x + " y " + y; } } public static double dis(Node a, Node b){ return Math.sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); } //x1y2- x2y1 public static int vectorProduct(Node a, Node b, Node c){ return (b.x-a.x)*(c.y-a.y) - (c.x-a.x)*(b.y-a.y); } public ArrayDeque<Node> grahamScan(List<Node> lists){ //最后一条边如果计较相等,则交换最后一边的顺序,距离大的排在前面,距离小的排在后面. 要不然 Collections.sort(lists); int j = lists.size()-2; while (j >= 0){ if(vectorProduct(apex, lists.get(j), lists.get(j+1)) == 0) j--; break; } j = j+1; for(int i = lists.size()-1; i != j; i++, j--){ Node temp = new Node(lists.get(i).x, lists.get(i).y); lists.get(i).x = lists.get(j).x; lists.get(i).y = lists.get(j).y; lists.get(j).x = temp.x; lists.get(j).y = temp.y; } ArrayDeque<Node> q = new ArrayDeque<>(); q.push(lists.get(0)); q.push(lists.get(1)); Node a = lists.get(0); for(int i = 2; i < lists.size(); ){ Node b = q.peek(); Node c = lists.get(i); int vp = vectorProduct(a, b, c); // System.out.println(vp + " " + a.x + " " + a.y + " " + b.x + " " + b.y + " " + c.x + " " + c.y); if(vp <= 0){ q.push(c); a = b; i++; }else{ q.pop(); b = q.pop(); a = q.pop(); q.push(a); q.push(b); } } return q; } public int[][] outerTrees(int[][] trees) { List<Node> lists = new ArrayList<>(); for(int [] cor : trees){ if(cor[0] < apex.x || (cor[0] == apex.x && cor[1] < apex.y)){ apex.x = cor[0]; apex.y = cor[1]; } lists.add(new Node(cor[0], cor[1])); } ArrayDeque<Node> q = grahamScan(lists); //last one System.out.println(vectorProduct(new Node(0,2), new Node(0,0), new Node(1,1))); int [][] res = new int[q.size()][2]; int ind = 0; while(!q.isEmpty()){ Node n = q.pop(); res[ind++] = new int[]{n.x, n.y}; } return res; } }
13 差分算法
- 如果对一个数组的区间变化相同值
- 如果最后得到这个数组
- 可以使用查分算法
- d[i] = a[i]-a[i-1]
- a[i] = d[1] +... + d[i] = a[1] + a[2]-a[1] + a[3] - a[2] +... + a[i]-[i-1]
- 如果对a[i]~a[j] +c -> d[i] += c. d[j+1] -= c. -> a[i] ~ a[j] 都含有d[i] 则这个区间都+c. a[j+1] 到最后都有 d[j+1] 则+c 与 -c相抵消,值不变。
14 欧拉路与欧拉环
欧拉路(在连通的基础上):
- 有向图 有一个点的出度-入度=1,一个点的入度-出度=1,其余点入度=出度
- 无向图 两个点的度为奇数,其余都是偶数
欧拉环(在连通的基础上)
- 有向图 所有点入度=出度
- 无向图 所有点的度相同
算法:Hierholzer
- 任意一点v开始
- 遍历其一条出边,并将其删除。
- 遍历到的点在所有边都遍历完后,再将其加入(这样可以保证还有其他边没走到的时候,这个点是最后加入的点,也就是后通过的i点,然后再走其他)
15 矩阵乘法快速幂算法
一个矩阵的n次幂为 A*A....*A.一共乘了n次。
可以转化为 A^(2^x1)*A^(2^x2)...A(2^xi) . 其中 2^x1 + 2^x2+ ... + 2^xi = n。
- 将n写成2进制形式 1001111000 exp。
- 然后遇到1位时候则将结果于当前位的矩阵相乘。
- 每次n向左移动一位,然后base算个平方为base加一位。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 while(n > 0){ 2 if((n & 1) == 1){ 3 now = mult(now, base); 4 } 5 base= mult(base, base); 6 n = (n >> 1); 7 }
16 基数排序
- 由最低位到最高位排序。
- 先把个位放入桶中。-> (没有10位的)然后挪入一边。10位放入桶->(没有百位)挪一边。以此类推最后得到一个排序的数组
- 数值的位数远远小于数目的时候,基数排序要快。否则其他。其实就是比较 k 和 logn. 其中k是位数,n数目。时间复杂度 O(kn).
package algorithm; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; public class RapixSort { public List<Integer> radix(int [] nums, int digits){ List<ArrayDeque<Integer>> digitsQ = new ArrayList<>(); ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(); List<Integer> res = new ArrayList<>(); for(int i = 0; i <= 9; i++) digitsQ.add(new ArrayDeque<>()); //0 1 2 3 4 5 6 7 8 9 //push all. for(int num : nums){ arrayDeque.offer(num); } for(int i = 1; i <= digits; i++){ //arrange int max = (int)Math.pow(10, i-1); while (!arrayDeque.isEmpty()){ int num = arrayDeque.poll(); int remain = num/max; int pos = remain - remain/10*10; digitsQ.get(pos).offer(num); } //collect for(int d = 0; d < 10; d++){ while (!digitsQ.get(d).isEmpty()){ int num = digitsQ.get(d).poll(); if(num >= max*10) arrayDeque.offer(num); else res.add(num); } } } return res; } public static void main(String[]args){ RapixSort rapidSort = new RapixSort(); List<Integer> res = rapidSort.radix(new int[]{12,11}, 5); System.out.println(res); } }
17 树状数组
- 频繁单点插入
- 频繁区间求和
- 用binaryindexedtree.
- update. x += (x&-x)
- preSum x -= (x&-x)
- 1 index start
package algorithm; public class BinaryIndexedTree { private int [] bt; public void buildBT(int [] list){ bt = new int[list.length+1]; int n = bt.length; for(int i = 0; i < list.length; i++) bt[i+1] = list[i]; for(int i = 1; i < n; i++){ int j = i + (i&-i);// add last one if(j < n) bt[j] += bt[i]; } } public void update(int idx, int delta){ idx += 1; while (idx < bt.length){ bt[idx] += delta; idx += (idx & -idx); } } public int preSum(int idx){ idx += 1; int result = 0; while (idx > 0){ result += bt[idx]; idx -= (idx & -idx); } return result; } }
2 DP:
- 自下向上和自上向下两种方法
- 将问题拆成子问题,子问题不再有其他问题
- 考虑状态变换,比如相等会怎么样啊、大于小于会怎么样啊、+1-1啊这种,有限状态变换、需要子问题怎么去变换
1 LSC
package algorithm; public class LCS { public int longestCommonSubsequence(char [] x, char [] y){ int m = x.length, n = y.length; int [][] dp = new int[m+1][n+1]; for(int i= 1; i <= m; i++){ for(int j = 1; j <= n; j++){ if(x[i-1] == y[j-1]) dp[i][j] = dp[i-1][j-1] +1; else{ dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]); } } } return dp[m-1][n]; } public static void main(String [] args){ char [] x = "ABCCAEF".toCharArray(); char [] y = "AEFBCCC".toCharArray(); LCS lcs = new LCS(); StringBuilder sb = new StringBuilder(); sb.reverse(); System.out.println(); System.out.println(lcs.longestCommonSubsequence(x,y)); } }
3 GREEDY
- 将最优化问题转化为:对其做选择后,可只剩下一个子问题需要求解
- 做出贪心选择后,原问题总存在最优解,即贪心选择总是安全的
- 做出贪心选择后,剩余的子问题满足:其最优解与贪心选择组合可得到原问题最优解,这样就得到最优子结构
- 活动策划问题。动态规划到贪心的流程
- 0-1 背包问题
- 分数背包问题
- 霍夫曼编码 (了解形成过程)
- 拟阵与势能了解
1 01背包问题
- 动态规划. dp[i][v] = max(dp[i-1][v], dp[i-1][v-volume[i]] + value[i]).
- dp[i][v]代表,背包体积v中,前1~i个物品最大价值
- i from 1 to n. v from 1 to maxV
- 最后用一维数组代替二维数组 对空间进行优化。
package algorithm; public class Bag01 { int [] item; //01背包 public int findMax(int maxV, int [] volume, int []value){ int n = volume.length; int [][] dp = new int[n+1][maxV+1]; for(int i = 1; i <= n; i++){ for(int j = 1; j <= maxV; j++){ if(volume[i-1] > j) continue; dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-volume[i-1]] + value[i-1]); //表达式没问题,但是如果加入了,则只加入一次。要不然会把上次结果拿来用。 // dp[j] = Math.max(dp[j], dp[j-volume[i]] + value[i]) 第i个物品 放与不放 } } return dp[n][maxV]; } public void findWhat(int i, int j, int [][] dp, int [] volume, int [] value) { //最优解情况 if (i >= 1) { if (dp[i][j] == dp[i - 1][j]) { item[i] = 0; findWhat(i - 1, j, dp, volume, value); } else if (j - volume[i] >= 0 && dp[i][j] == dp[i - 1][j - volume[i]] + value[i-1]) { item[i] = 1; findWhat(i - 1, j - volume[i], dp, volume, value); } } } //内存覆盖 public int findMaxOneArray(int maxV, int [] volume, int []value) { int [] dp2 = new int[maxV+1]; for(int i = 0; i < value.length; i++) { // 遍历物品 int [] dp3 = new int[maxV+1]; for(int j = 1; j <= maxV; j++){ dp3[j] = dp2[j]; if(volume[i] > j) continue; dp3[j] = Math.max(dp2[j], dp2[j-volume[i]] + value[i]); } dp2 = dp3; } return dp2[maxV]; } public static void main(String [] args){ int [] volume = {4,5,6,7}; int [] value = {3,8,20,8}; Bag01 bag01 = new Bag01(); System.out.println(bag01.findMaxOneArray(22, volume,value)); } }
4 B树
- 了解磁盘工作过程
- 了解B数结构,插入,分裂,删除流程。不写了。
5 斐波那契堆
6 Van Emde Boas树
7 图计算
1)最小生成树:
1 kruskal (无向图。贪心加边)
- 把所有边进行排序,由小到大
- 每次取一条边加入到集合中,如果加入的边不构成环,则加入集合。
- 用并查集算法判断新加入的边是否会构成环 (写错了,父节点那里)
package algorithm; import algorithm.objs.Edge; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Kruskal { public static int M = 0x7ffff; public List<Edge> kruskal(List<Edge> edges, int vertexNum){ Collections.sort(edges); UnionFind unionFind = new UnionFind(vertexNum); List<Edge> res = new ArrayList<>(); int count = 0; for(Edge edge : edges){ if(unionFind.find(edge.s) == unionFind.find(edge.e)) continue; unionFind.merge(edge.s, edge.e); res.add(edge); if(count == vertexNum-1) break; } return res; } }
2 prim(无向图,贪心加顶点)
- 每次加入一个顶点 v到S中,S是V的子集。
- 加入点的条件是,S中选择一个点,然后在V-S中找一点 k,使得边 edge(v, k)值最小。
- 贪心思想
package algorithm; import algorithm.objs.Edge; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Prim { public static int M = 0x7ffff; public List<Edge> prime(int [][] weight){ List<Edge> res = new ArrayList<>(); int vertexNum = weight.length; boolean [] visited = new boolean[vertexNum]; visited[0] = true; for(int k = 1; k < vertexNum; k++){ int min = M, from = 0, to = 0; for(int i = 0; i < vertexNum; i++){ if(!visited[i]) continue; for (int j = 0; j < vertexNum; j++){ if(visited[j]) continue; if(min > weight[i][j]){ from=i; to = j; min = weight[i][j]; } } } visited[to] = true; res.add(new Edge(from, to, min)); } return res; } }
3 并查集 (无向图)
- 把一棵树的点设置到一个公共父节点
- 如果两棵树能合并到一起,则把高度小的树合并到高度大的数(rank1 <= rank2)。以最大程度保证更新父节点时候更新少数点(高度计算不绝对保证)。
package algorithm; public class UnionFind { public int [] f; public int [] rank; public UnionFind(){} public UnionFind(int vertexNum){ f = new int[vertexNum]; rank = new int[vertexNum]; for(int i = 0; i < vertexNum; i++){ f[i] = i; rank[i] = 1; } } public int find(int x){ int end = f[x]; while (f[end] != end) end = f[end]; while (f[x] != end){ int fax = f[x]; f[x] = end; x = fax; } return end; } public void merge(int x, int y){ int fax = find(x), fay = find(y); //not equal. merge if(rank[x] <= rank[y]){ f[fax] = fay; rank[y] += rank[x]; }else { f[fay] = fax; rank[x] += rank[y]; } } }
4 朱刘算法 (有向图)
- 有向图最小生成树,从根节点出发能到达所有其他点
- 初始化,保存所有点入边权重最小值,然后所有点入边的权用最小值替代(即最小权边为入边)
- 最小权重加上所有点入边的权重
- 把有向图的环当成一个点,环需要一个入边。w -= In(v). In(v) 入边最小,把环内最小的权值删了避免重复
- 去环操作,把不成环的边权 -= 最小入边权
- 不断收缩直到没环
package algorithm; import algorithm.objs.Edge; import java.util.ArrayList; import java.util.List; public class ZhuLiu { public int zhuliu(int [][] graph, int root){ //初始化设置 int vertexNum = graph.length; List<Edge> edges = new ArrayList<>(); for(int i = 0; i < vertexNum; i++){ for(int j = 0; j < vertexNum; j++){ if(graph[i][j] != Prim.M){ edges.add(new Edge(i,j,graph[i][j])); } } } int minCost = 0; //重复删除环和重复入边 while (true){ //入权初始化 int [] inCost = new int[vertexNum]; int [] pre = new int[vertexNum]; int [] circleNo = new int[vertexNum]; int totalCircleNum = -1; for(int i = 0; i < vertexNum; i++) {inCost[i] = Prim.M; circleNo[i] =-1;} for(Edge edge:edges){ if(edge.s != edge.e && edge.e != root && edge.w < inCost[edge.e]){ pre[edge.e] = edge.s; inCost[edge.e] = edge.w; } } inCost[root] = 0; pre[root] = root; //是否有入边检查 for(int i = 0; i < vertexNum; i++){ if(i != root && inCost[i] == Prim.M) { return -1; // 无入边返回 } } //遍历所有点看是否有环 for(int i =0; i <vertexNum; i++) { if(i == root) continue; // System.out.println(inCost[i] + " " + vertexNum); minCost += inCost[i]; int s = pre[i]; //找是否有环 while (s != i && s != root && circleNo[s] == -1){ s = pre[s]; } //有环, 给环打标 if(s != root && circleNo[i] == -1){ circleNo[i] = ++totalCircleNum; while (pre[s] != i){ s = pre[s]; circleNo[s] = totalCircleNum; } } } if(totalCircleNum == -1) break; // 无环返回 //给剩余的点加上环标签 for(int uu = 0; uu < vertexNum; uu++){ if(circleNo[uu] == -1) { circleNo[uu] = ++totalCircleNum; } } //如果有环则更新权值, 并且把环的入边,出边统一 for(Edge edge : edges){ int e = edge.e; edge.s = circleNo[edge.s]; edge.e = circleNo[edge.e]; if(edge.s != edge.e) edge.w -= inCost[e]; } //更新边数和点数 vertexNum = totalCircleNum + 1; root = circleNo[root]; } return minCost; } }
2)单源最短路径 (源节点S,到V的距离,现有一条边u,v,松弛操作RELAX了解下)
1 松弛操作性质:
- 最短路径满足三角不等式原理
- 距离是最短路径的上界
- 非路径性质
- 路径松弛性质: (s = v0, t = vk, 如果一个最短路径为 (v0,v1,... , vi,... vk, 则 如果有按次顺序的松弛操作 (vo, v1), (v1,v2),... , (vk-1, vk) 则 vk.d = delta(s, vk)。次最短路径的松弛结果与其他松弛操作无关。即只要按顺序执行过 松弛操作 (vo, v1), (v1,v2),... , (vk-1, vk) . 则就能得到最短路径。
- 前驱子图性质
2 Dijkstra 有向图无负权重
- 以原点为起点,每次松弛上一次最小的距离的节点 j . relax(g, j, k).
- 当前节点 路径最短,可以用当前节点进行松弛操作,因为最短路径,一定是由子最短路径不断松弛得来的。
- 贪心思想,每次会产生一个节点为k的最短路径, 由j的松弛操作产生。 反证法 (存在f在V-S中,有更短的路径 )
package algorithm; // 设点数为v,s为源点,t为终点 // 循环v-1次,每次加入一个点,该点为 delta(s,k) // 每次循环对上一次最小的 V-S 集合中的 k,进行松弛操作 public class Dijkstra { private static int M = 0x7ffffff; public int [] dijkstra(int [][] weight, int source){ int vertexNum = weight.length; boolean [] visited = new boolean[vertexNum]; int [] distance = new int[vertexNum]; if(vertexNum == 0) return distance; for(int i = 0; i < vertexNum; i++) distance[i] = M; distance[source] = 0; //vertexNum-1 relax. find all vertexes. must find source for(int i = 0; i < vertexNum; i++){ //find min. int min = M, k = 0; for(int j = 0; j< vertexNum; j++){ if(!visited[j] && min > distance[j]){ min = distance[j]; k = j; } } if(min == M) break; relax(distance, weight, k); visited[k] = true; } return distance; } public void relax(int [] distance, int [][] weight, int k){ for(int i = 0; i < distance.length; i++) distance[i] = Math.min(distance[i], distance[k] + weight[k][i]); } }
3 Bellman-ford 有向图有负权重。可以检测负权回路
- 根据松弛操作性质路径操作松弛性质,循环节点次数,每次对所有边进行遍历,则一定能找出所有的最短路径松弛操作组合。
- 最后一次对所有边进行遍历,如果还能进行松弛操作,则有负环
package algorithm; import algorithm.objs.Edge; import algorithm.objs.Graph; import java.util.InputMismatchException; import java.util.List; public class BellmanFord { public static int M = 0x7fffffff; public int [] bellmanFord(Graph graph, int source){ int vertexNum = graph.vertexNum; int [] distance = new int[vertexNum]; for(int i = 0; i < vertexNum; i++) distance[i] = M; distance[source] = 0; //n次对每条边进行松弛,则最短路径一定确定 跟据松弛操作的性质 for(int i = 0; i < vertexNum; i++) relax(graph.edges, distance); //负环判断 for(Edge edge : graph.edges) if(distance[edge.e] > distance[edge.s] + distance[edge.e]) return new int[0]; return distance; } public void relax(Edge [] edgeList, int [] distance) { for (Edge edge : edgeList) distance[edge.e] = Math.min(distance[edge.e], distance[edge.s] +edge.w); } }
4 SPFA
5 差分系统的约束解。要会证明,bellman-ford可以解。然后差分系统的矩阵表达形式了解下
3)所有节点对的最短路径问题
1 FLOYD 动态规划 (如果所有边的权重为1,则是传递闭包问题,即判断图中两点是否连通)
- 根据所有点子集 (1,....k) 判断,根据点k是否在最短路径中进行分析。即每次加入一个k点,和之前的 1....k-1点集合最短距离比较
- 写出递归表达式。
- 可以数学归纳法证明下。其他方法证明我觉得比较难。
- 虽然DP公式比较好理解. 如果i到j的最短路径只包含中间节点 1~k。 dp[i][j][k] = dp[i][k][k-1] + dp[k][j][k-1]. dp[i][k][k-1]最短路径只包含1~k-1中间节点. dp[k][j]的中间节点同理。动态规划公式没什么问题。
package algorithm; public class Floyd { public int[][] floyd(int [][] weight){ int vertexNum = weight.length; int [][] dis = new int[vertexNum][vertexNum]; for(int i = 0; i < vertexNum; i++){ for(int j = 0; j < vertexNum; j++){ dis[i][j] = weight[i][j]; } } for(int k = 0; k < vertexNum; k++){ for(int i = 0; i < vertexNum; i++){ for(int j = 0; j < vertexNum; j++){ if(i != j){ dis[i][j] = Math.min(dis[i][j], dis[i][k] + dis[k][j]); } } } } return dis; } }
2 用于稀疏图的Johnson算法 (ford检查负环,然后更新权重,然后用DJ算)
4)最大流 (流网络,源节点,汇点,如果有反平行的边,拆成一个中间节点过度下)
- 单个s,t
- 多个s,多个t,定义超级原点s,超级汇点t
- Ford-Fullkerson, 残存网络,增广路径和切割。抵消操作
1 ford-fullkerson
- 残存容量
- 有残存网络,则相当于以前走过的流量可以从反向走。即谁都可以占用当前方向。
- bfs 在残存网络找一条增广路径,把所有可能点都压入队列头,遍历寻找
- residualGraph[u][v] -= d, residualGraph[v][u] += d
- bfs(s,t)
package algorithm; import java.util.ArrayDeque; public class FordFulkerson { int [][] residualGraph; int [] pre; boolean [] used; int vertexNum; public int fordFulkerson(int s, int t){ int res = 0; while (bfs(s,t )){ int minFlow = 0x7fffff; int u = 0, v= 0; for(int i = 0; i < vertexNum; i++){ minFlow = Math.min(minFlow,residualGraph[i][pre[i]]); } for(int i = 0; i < vertexNum; i++){ residualGraph[i][pre[i]] -= minFlow; residualGraph[pre[i]][i] += minFlow; } } return res; } boolean bfs(int s, int t){ used = new boolean[vertexNum]; ArrayDeque<Integer> q = new ArrayDeque<>(); q.push(s); used[s] = true; while (!q.isEmpty()){ int v = q.pop(); for(int i = 0; i < vertexNum; i++){ if(!used[i] || residualGraph[v][i] >0){ used[i] = true; pre[i] = v; if(i == t) return true; q.push(i); } } } return false; } }
5)二分图最大匹配
1 匈牙利算法
- 为二分图左侧节点去找右侧匹配
- 遍历左侧节点
- 然后遍历右侧节点,看右侧是否能让出来
package algorithm; public class Hungary { int [][] graph; int [] vt; boolean [] visited; boolean find(int x){ for(int i = 0; i < graph.length; i++){ if(visited[i] && graph[x][i] != 1) continue; visited[i] = true; if(vt[i] == -1 || find(vt[i])){ vt[i] = x; return true; } } return false; } public void hungary(){ for(int i = 0; i < vt.length; i++){ if(vt[i] == -1) find(i); } } }
2 KM算法
- 为x,y侧分别分配顶标,x侧为最大期望值
- 为y侧设置一个期望差值数组
- 用匈牙利算法判断每次是否能找到 gap为0的匹配对,并且为每个y设置一个最小期望值
- 如果x找到匹配返回,找不到则用最小gap,去寻找
- 用最小gap更新x侧期望值,y侧期望值,期望差值数组
package algorithm.learn; import static algorithm.learn.BellmanFord.M; public class KM { int n; int [][] graph; int [] ey; int [] ex; boolean [] visitedY; boolean [] visitedX; int [] yMatch; int [] slack; boolean find(int x){ visitedX[x] = true; for(int y = 0; y < n; y++){ if(visitedY[y]) continue; int gap = ex[x] + ex[y] - graph[x][y]; if(gap == 0){ //找到可以match的 visitedY[y] = true; if(yMatch[y] == -1 || find(yMatch[y])){ yMatch[y] = x; return true; } }else { slack[y] = Math.min(slack[y], gap); //更新最小退步 的y } } return false; } int KM(){ int res = 0; for(int i = 0; i < n; i++) yMatch[i] = -1; for(int y = 0; y < n; y++){ for(int x = 0; x < n; x++){ ey[y] = Math.max(ey[y], graph[x][y]); } } for(int i = 0; i < n; i++){ for(int x = 0; x < n; x++) slack[x] = M; while (true){ for(int x = 0; x < n; x++){ visitedY[x] = false; visitedX[x] = false; } if(find(i)) break; int minSlack = M; for(int y = 0; y < n; y++){ if(!visitedY[y]) minSlack = Math.min(minSlack, slack[y]); // 找最小的slack } for(int j = 0; j < n; j++){ if(visitedX[j]) ex[j] -= minSlack; if(visitedY[j]) ey[j] += minSlack; else slack[j] -= minSlack; //差距减小,因为左侧x的期望减小 } } } for(int i = 0; i < n; i++) res += graph[yMatch[i]][i]; return res; } }
5) tarjan算法
8 多线程算法 (spawn, sync, 竞争条件)
- 斐波那契数列举例
- 多线程矩阵乘法(分治,将矩阵划分为子矩阵)
9 数论:
1)
- 约数
- 素数
- 合数
- 最大公约数,互质数(最大公约数为1),a是d的倍数 d|a
- 唯一因子分解定理。a = pi^ei * ... pr^er. pi 为素数
- gcd(a,b) = gcd(b, a mod b). a = cx, b = cy, a = nb + m, m = cx - cyn. 因此 m 、 b 有相同公约数 c. m, b 的最大公约数假设是C,a=nb + m 可知, a 也有公约数 C,则 C = c。
public int gcd(int a, int b) { if(a < b) return gcd(b,a); while(b != 0) { int temp = a; a = b; b = temp%b; } return a;
}
2)字符串匹配算法
- robin karp。把字符串hash预处理。每次相当于遍历一个值就可以。用个唯一标识的hash值。每次换最高位与最低位,与模版hash比较。缺点是如果模版过长,hash值不好设计
- 有限自动机
1 kmp与扩展kmp算法
KMP
- 需要寻找的字符为s,模版是p
- 用p去计算 next数组
- next数组存放,在p中,第j个元素最长的相同前、后缀长度。
- 有了next数组,就可以用这个长度,递归去计算现在要匹配的. s[i], p[j]
package algorithm; import java.util.Locale; public class KMP { private int [] next; public void getNext(char [] p){ int len = p.length; next = new int[len]; next[0] = -1; //将p当作要匹配的字符串,然后用p的起点去对齐,计算前缀与后缀的match程度, int i = 0, j = -1; while (i != len){ if(j == -1 || p[i] == p[j]){ i++; j++; next[i] = j; }else{ j = next[j]; } } } public int kmp(char [] s, char [] p){ getNext(p); int i = 0, j = 0; while (i != s.length && j != p.length){ if(j == -1 || s[i] == p[j]){ i++; j++; }else{ j = next[j]; } } if(j == p.length) return i-j; else return -1; } }
扩展KMP
质数:
求1~n中的质数
1)暴力。 1~n**0.5, 看是否整出. O(n*√n
2) 埃式筛
update every prime. from (prime to n). p[prime*j] = k
时间复杂度 O(nlog(n))
class Solution { public int countPrimes(int n) { int res = 0; boolean [] isNotPrime = new boolean[n]; int sqr = (int)Math.pow(n, 0.5); for(int i = 2; i < n; i++) { if(!isNotPrime[i]) { res++; for(int j = i; i <= sqr && j*i < n; j++) { isNotPrime[j*i] = true; } } } return res; } }
3) 线性筛法
任何一个数都是由质因数组成。假设当前数是 n = axbycz... (现有质数 a, b, c ...由小到大排序,为已有的质因数),按照顺序,如果下一个数是合数,则一定是 ax+1bycz... 。 另一种情况是假设当前数是 n = cz... (现在有质数 a,b,c... 由小到大排序), 则下三个出现的合数是 acz... 和 bcz... 和 cz+1... 。 所以如果每一次,质数由小到大去更新和数,则一定可以把后面访问到的合数更新到。到 n % prime == 0, 就可以停止更新。 时间复杂度 O(n)
class Solution { public int countPrimes(int n) { int res = 0; int size = 0; int [] primes = new int[n]; boolean [] isNotPrime = new boolean[n]; for(int i = 2; i < n; i++) { if(!isNotPrime[i]) { res++; primes[size++] = i; } for(int j = 0; j < size && primes[j] * i < n; j++) { int next = i * primes[j]; isNotPrime[next] = true; if(i % primes[j] == 0) break; } } return res; } }
ST 表 (Spare Table)
解决重复问题。比如求一个数之前 k 步的某个 值,最大值、最小值、可以用ST表。
k = 2a + 2b + 2c + 2d + 2e + ..... k可以用二进制表示。因此可以推导
1) 2a-1 + 2a-1 = 2a
2) st[n][k] = st[st[n][k-1]][k-1] 可以从 k = 0 ~ k-1, 逐渐更新
现在给丁查询 从 n,k步之后是多少 ? 可以:
1) k = 2a + 2b + 2c + 2d + 2e + .....
2) res = st[res][i] , i = a, b, c, d, e....
逆元
取模运算可以用于 乘法、加法、减法。 (a+b)%M = (a%M + b%M) % M.
除法的逆元不能直接用。 (a/b) % M = (a%M * inv(b%M))%M.
其中 inv(x) * x = 1, 在 %M 的情况下。并且, 当 M是质数,如果gcd(x,M) = 1 则 x^(M-1) = 1. 因此 x * inv(x) = x^(M-1) , 则 inv(x) = x^(M-2). x^(M-2) 由快速幂算得。
long qpow(long base, int n, int p) { long ans = 1; while(n != 0) { if((n&1) == 1) { ans = ans * base % M; } n = n >> 1; base = base * base % M; } return ans; } invs[base] = qpow(base, M-2, M); res = res%M * invs[b%M] % M; // (res / b) %M
其他经验总结:
1、树的遍历 根据树字符串的顺序还原二叉树。无递归遍历
package algorithm; import algorithm.objs.TreeNode; import java.util.ArrayDeque; public class TravelBinaryTree { //前序遍历,前,左,右 public void preOrder(TreeNode node) { if(node == null) return; System.out.println(node.value); preOrder(node.left); preOrder(node.right); } //中序遍历 左 中 右 public void inOrder(TreeNode node) { if(node == null) return; inOrder(node.left); System.out.println(node.value); inOrder(node.right); } //后序遍历 左 右 中 public void postOrder(TreeNode node) { if(node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.value); } public void preOrderWithoutRecursion(TreeNode node){ //按顺序压入栈 ArrayDeque<TreeNode> q = new ArrayDeque<>(); q.push(node); while (!q.isEmpty()){ TreeNode first = q.pop(); System.out.println(first.value); if(first.right != null) q.push(first.right); if(first.left != null) q.push(first.left); } } public void inOrderWithoutRecursion(TreeNode node){ ArrayDeque<TreeNode> q = new ArrayDeque<>(); //pop 一次为中间节点 q.push(node); while (!q.isEmpty()){ TreeNode root = q.peek(); while (root.left != null){ TreeNode before = root; q.push(root.left); root = root.left; before.left = null; } TreeNode last = q.pop(); System.out.println(last.value); if(last.right != null) q.push(last.right); } } public void postOrderWithoutRecursive(TreeNode node){ ArrayDeque<TreeNode> q = new ArrayDeque<>(); q.push(node); while (!q.isEmpty()){ TreeNode current = q.pop(); if(current.left == null && current.right == null){ System.out.println(current.value); }else{ q.push(current); } if(current.right != null) q.push(current.right); if(current.left != null) q.push(current.left); current.left = null; current.right =null; } } public int buildBinaryTreePreorderDfs(TreeNode node, String s, int index){ if(index == s.length()) return index; if(s.charAt(index) == '-') return index+1; node.value = s.charAt(index) - '0'; int ind = index+1; if(ind < s.length() && s.charAt(ind) != '-'){ node.left = new TreeNode(); ind = buildBinaryTreePreorderDfs(node.left, s, ind); }else if(ind < s.length()){ ind++; } if(ind < s.length() && s.charAt(ind) != '-'){ node.right = new TreeNode(); ind = buildBinaryTreePreorderDfs(node.right, s, ind); }else if(ind < s.length()){ ind++; } return ind; } public TreeNode buildTreeFromStringPreOrder(){ String s = "126-42--3---36--5--"; TreeNode root = new TreeNode(); buildBinaryTreePreorderDfs(root, s, 0); return root; } public TreeNode buildTreeFromStringInOrder(){ String s = "-6-2-4-3--1-6-3-5-"; TreeNode root = new TreeNode(); buildBinaryTreePreorderDfs(root, s, 0); return root; } public TreeNode buildTreeFromStringPostOrder(){ String s = "126-42--3---36--5--"; TreeNode root = new TreeNode(); buildBinaryTreePreorderDfs(root, s, 0); return root; } // public TreeNode buildTreeFromStringInOrder(){ // String s = "126-42--3---36--5--"; // } public static void main(String [] args){ TravelBinaryTree travelBinaryTree = new TravelBinaryTree(); TreeNode root = travelBinaryTree.buildTreeFromStringPreOrder(); // travelBinaryTree.preOrder(root); travelBinaryTree.postOrderWithoutRecursive(root); } }
2、二维矩阵和
-
以0,0为左上,任意为右下。这个矩阵的和用dp可以计算。为 dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + matrix[i][j]
- x1, y1 左上, x2, y2右下的矩阵和 sum = dp[x2][y2] - dp[x2][y1] - dp[x1][y2] + dp[x1][y1]
- 画图很直观
package algorithm; public class SubmarixSum { public int submatrixSum(int [][] matrix, int x1, int y1, int x2, int y2){ int res = 0, m = matrix.length, n = matrix[0].length; int [][] dp = new int[m+1][n+1]; //多出一行一列位0的 空间位同行同列矩阵做处理 for(int i = 1; i <= m; i++){ for(int j = 1; j <= n;j++){ dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + matrix[i-1][j-1]; } } return dp[x2][y2] - dp[x1][y2] - dp[x2][y1] + dp[x1][y1]; } }
3、维护一个TOP K数组考虑priorityQueue。如果遍历的数组有序,则用普通队列即可。
4、区间dp模板
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 for(int l = 1; l <= n; l++){ // l 长度 2 for(int i = 0; i < n; i++){ //i 起始点 3 j = i+l-1; // j 终点 4 for(int s = 1; s <= j; s++){ // s 中间点 5 do(i~s-1). // 起始点~s-1 干点啥 6 do(s~j) // s ~ 终点干点啥 7 } 8 } 9 }
5、向左找第一个遇到的最大或者最小。用一个队列维护。覆盖问题。
6、如果结果包含有限制,可以枚举所有值,或者所有组合。这样时间复杂度,是一个常数固定值。
7、枚举所有可能。可以用统计二进制的1的位数。则可以统计所有出现的可能。最大值用 1 << N 表示。
8 、数位DP。用一个mask。每一位表示成二进制。1表示访问过,0表示没访问过。主要DFS用来确定好上下限,例如给了一个low,一个high,每一位的值要在里面。其他的至于统计相同数还是不同数只是在里面做计算,没什么其他的。
9、求第k大子数组和。用一个堆维护。记录下标和当前和。向堆里面添加两个和。sum = sum_i + nums(i+1). sum = sum_i - num_i + num(i+1). 可以证明一下可以得到所有的子序列和。
- 设所有 i 为当前的数组下标。则前 i 的所有子序列组合设为 A(i-1)。 A(i-1) + nums[i] U A(i-1) 为 到 i 为止的所有子序列和 设为A(i)。
- 对于nums[i-1] 如何通过。怎么得到所有子序列和? A(i) U A(i) + nums[i]。
- 对于某一个子数组和 sum[i]. 则通过上述构建方式,构建所有子序列和:
- 一种是包含 nums[i] 的子序列和即 sum[i]. next = sum[i] + nums[i+1].
- 一种是nums[i]之前的,sum[i]-nums[i]. next = sum[i] - nums[i] + nums[i+1]
- 将4、5加入队列。即为新构建的子序列和。
- 上述过程实际上每一次对最后加入的nums[i] 的 序列和进行一次 分裂。
- 上述方式可以得到所有子序列的组合
10、判断一个下标是否在存在的范围段里面。可以用一个TreeSet维护(需要注意 lower 和 higher的null情况。)或者TreeMap。
11、meet in the middle算法。在要给集合里找固定长度的。可以将集合折半,然后分别从每一个集合里面找x、y。 x +y=target。对于第二个集合可以用二分加速。
12、构造时间长度和数组的成绩和,可以有小到大 一次增加 a0 + a1 + a2 + a3. 每一次后面加一数ai,然后一次把这个数加上。
13、两个有序数组求中位数。
- 对短的数组用二分。
- 判断是否加入这个数组多一些元素还是加入另一个数组多一些元素
- 最后对 偶数数组、奇数数组分情况讨论。讨论时候加入边界思考
package ltc; public class Q4{ public static double findMedianSortedArrays(int[] A, int[] B) { int m = A.length, n = B.length; if(m > n) return findMedianSortedArrays(B, A); // A[am+1] < B[bm] l = am+1 // A[am] > B[bm+1] r = am-1 if(m == 1 && n == 1) return (A[0]+B[0])/2.0; if(m == 0) { if(n%2 == 1) return B[n/2]; else return (B[n/2-1] + B[n/2])/2.0; } int al = 0, ar = m-1, t = (m+n+1)/2; while (al <= ar){ int am = (al+ar)/2; int bm = t-(am+1)-1; if(am >= 0 && am < m-1 && A[am+1] < B[bm]) al = am+1; // need more A-elements else if(bm >= 0 && bm < n-1 && A[am] > B[bm+1]) ar = am-1; // need more B-elements else{ if(bm == -1) return (A[am] + B[0])/2.0; int l = 0, r= 0; if((m+n)%2 == 0){ l = Math.max(A[am], B[bm]); if(am < m-1 && bm < n-1){ r = Math.min(A[am+1], B[bm+1]); }else if (am == m-1 && bm < n-1){ r = B[bm+1]; }else if (bm == n-1 && am < m-1){ r = A[am+1]; }else{ r = Math.max(A[am], B[bm]); } }else{ l = Math.max(A[am], B[bm]); r = l; } return (l+r)/2.0; } } int l = 0, r = 0; if((m+n)%2==0){ if(t == n){ l = B[t-1]; r = A[al]; }else{ r = B[t-1]; l = Math.min(A[al], B[t]); } }else{ l = B[t-1]; r = l; } return (l+r)/2.0; } public static void main(String [] args){ int [] A = {1,4}; int [] B = {2,3,4,6}; // 3.5 1,2,3,4,4,6 System.out.println(findMedianSortedArrays(A, B)); } }
其他:
lc 复杂暂时不写题目直接跳过( 检查仔细性,写前想明白就好,这种想不明白不要写,要不然出现错误盲目去改会越来越乱):
- 736 Lisp反解
- 749 每次给一个最大区域加墙
- LCP13
- 420
- 936
- 782
lc没做出来
773 bfs解会最快。棋盘求解,最先找到的情况。
458 编码问题,将所有的bucket分组。最终为进制编码问题。 几轮检查完相当于,检查了多少状态。然后所有的bucket看能用轮次进制表示。每一轮查猪查一个相同状态,确定一位。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public int poorPigs(int buckets, int minutesToDie, int minutesToTest) { 2 int x = minutesToTest/minutesToDie + 1; 3 int y = buckets; 4 return (int)(Math.ceil(Math.log(y)/Math.log(x))); 5 }
1312 最短回文需要添加个数。要么往开头,要么往结尾. 分区间两两相等 和两两部等的情况。公5种比较。
302 连续点的最大最小坐标。看到连续,并且有界限,找一个值。往二分想。或者如果查找方向为一,往二分去想。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Solution { 2 3 //把字符反过来写为 bs,bs和s比较各自的第一个字符 4 //要么相同,要么不同。如果相同. the不需要添加。 5 //如果不同,则要么在开头左侧加结尾的消除,要么在结尾添加的右侧添加开头元素消除。 6 // 7 8 9 public int minInsertions(String s) { 10 int n = s.length(); 11 int [][] dp = new int[n][n]; 12 13 for(int r = 2; r <= n; r++){ 14 for(int i = 0; i < n; i++){ 15 int j = i+r-1; 16 if(j >= n) continue; 17 // System.out.println(i + " " + j); 18 if(s.charAt(i) == s.charAt(j)){ 19 int addMin = Math.min(dp[i][j-1], dp[i+1][j]) + 1; 20 dp[i][j] = Math.min(dp[i+1][j-1], addMin); 21 }else{ 22 dp[i][j] = Math.min(dp[i][j-1], dp[i+1][j]) + 1; 23 } 24 } 25 } 26 27 return dp[0][n-1]; 28 } 29 30 }
1074 二位前缀和没学过。用二维前缀和解. 矩阵任意子矩阵的和
1066 能看到的棍子。 暴力求解后思考动态规划,状态转移方程没想出来。还有之间一些比较巧妙的变换没想出来
546 写了个dfs超时,dp状态转移方程没写出。区间DP。不过这个 dp形式不太好想。
730 dp。转移方程想不出来
1387 dp。没思路
798 差分算法
741 dp。从前到后,再从后到前,等驾驭两个机器人一起从起点到终点。这个没想出来。
1383 . 思路想反了。而且没考虑到当前员工加入的话,少于k个数目是如何变化(实际就是不变。真的存在当前员工,前面的数目不可能缩小,如果贪心的顺序正确)
6155 不会求所有子序列和的方法。 刷题刷的都有点破防了。
1363 一个数能被3整除,这个数所有位数的和能被三整除。
1671 求最长单调增序列。dp或者二分。 f[i] = max(f[i], f[j+1]) j from 0 ~ i-1 and nums[i] > nums[j]
1675 一步一步求。步骤没想出来。
321 一步步求。有时候太关注贪心,没有想到所有可能解的方法。有些题经常这样,一步想不出来贪心,我往往忽略了最优解是怎么转化来的,有多少可能转化情况。然后所有情况去求解。
2035 meet in the middle. 无限接近一个值的时候想二分。
1703 数学解法,中位数,没想出来. 一个数组中间选一个点 x,使得所有的点到 x距离和最小。这个点为 数组中位数。 两个点 | x-a| + |b-a| >= |b-a|. 然后首尾点用这个公式。可以证明。这样用一个滑动窗口就可以把时间复杂度控制在O(n)里面。
1406 dp没想出来构造方式
1402 数学公式没想出来
6163 怎么组合最后的元素没想出来
810 异或的情况没想好
913 博弈论没学过。(1、必胜状态 2、必败状态 3、必和状态。 跟据抽屉原理,如果所有状态都遍历过了,则为必和状态)。两种求解方式:1)动态规划。需要把所有状态都遍历一遍。2)拓扑排序。自底向上计算,跟据必胜和必须败状态,去更新上一层状态。
774 直接对结果进行二分。我用的是PQ超时。
879 多重背包问题。没想出来状态转移方程
剑指offer51 归并排序。统计前面有多少数大、或者小,可以想到归并排序,或者使用离散化树状数组。