《算法导论第二版》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模板

步骤

  1. 找最下左点,当作极点。
  2. 跟据到极点的极角,由小到大排,如果极角相同按照距离由小到大排列。crossProduct(a, b) = a.x*b.y - b.x*a.y.  (如果a在b的逆时针方向,crossProduct < 0)
  3. 排序后的点,如果最后一个极角对应多个点,则交换上面所有点的顺序,按照到距离由大到小排列(上一步是由小到大)。
  4. 按照对点的排序顺序放入堆 前2 个点。从第三个点开始进行下一个步骤
  5. 遍历排序的点
  6. 标记堆的top与top-1为  a ,b,当前遍历的点为c
  7. 判断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加一位。
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 }
View Code

 

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 树状数组 
  1. 频繁单点插入
  2. 频繁区间求和
  3. 用binaryindexedtree.
  4. update. x += (x&-x)
  5. preSum x -= (x&-x)
  6. 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. 自下向上和自上向下两种方法
  2. 将问题拆成子问题,子问题不再有其他问题
  3. 考虑状态变换,比如相等会怎么样啊、大于小于会怎么样啊、+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

  1. 将最优化问题转化为:对其做选择后,可只剩下一个子问题需要求解
  2. 做出贪心选择后,原问题总存在最优解,即贪心选择总是安全的
  3. 做出贪心选择后,剩余的子问题满足:其最优解与贪心选择组合可得到原问题最优解,这样就得到最优子结构

  

  • 活动策划问题。动态规划到贪心的流程
  • 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树

  1. 了解磁盘工作过程
  2. 了解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 松弛操作性质:
  1. 最短路径满足三角不等式原理
  2. 距离是最短路径的上界
  3. 非路径性质
  4. 路径松弛性质: (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) . 则就能得到最短路径。
  5. 前驱子图性质
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
差分系统的约束解。要会证明,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模板
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         }
View Code
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). 可以证明一下可以得到所有的子序列和。 
  1. 设所有 i 为当前的数组下标。则前 i 的所有子序列组合设为 A(i-1)。 A(i-1) + nums[i] U A(i-1) 为 到 i 为止的所有子序列和 设为A(i)。
  2. 对于nums[i-1] 如何通过。怎么得到所有子序列和? A(i) U A(i) + nums[i]。
  3. 对于某一个子数组和 sum[i]. 则通过上述构建方式,构建所有子序列和:
  4. 一种是包含 nums[i] 的子序列和即 sum[i].  next = sum[i] + nums[i+1].
  5. 一种是nums[i]之前的,sum[i]-nums[i]. next = sum[i] - nums[i] + nums[i+1]
  6.  将4、5加入队列。即为新构建的子序列和。
  7. 上述过程实际上每一次对最后加入的nums[i] 的 序列和进行一次 分裂。
  8. 上述方式可以得到所有子序列的组合
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看能用轮次进制表示。每一轮查猪查一个相同状态,确定一位。

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      }
View Code 

1312 最短回文需要添加个数。要么往开头,要么往结尾. 分区间两两相等 和两两部等的情况。公5种比较。

302 连续点的最大最小坐标。看到连续,并且有界限,找一个值。往二分想。或者如果查找方向为一,往二分去想。

 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 }
View Code

 

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 归并排序。统计前面有多少数大、或者小,可以想到归并排序,或者使用离散化树状数组。

posted @ 2020-11-28 13:49  ylxn  阅读(202)  评论(0编辑  收藏  举报