算法進階面試題05——樹形dp解決步驟、返回最大搜索二叉子樹的大小、二叉樹最遠兩節點的距離、晚會最大活躍度、手撕緩存結構LRU

接着第四課的內容,加入部分第五課的內容,主要介紹樹形dp和LRUnode

 

第一題:

給定一棵二叉樹的頭節點head,請返回最大搜索二叉子樹的大小數組

 

二叉樹的套路緩存

統一處理邏輯:假設以每一個節點爲頭的這棵樹,他的最大搜索二叉子樹是什麼。答案必定在其中數據結構

 

第一步,列出可能性(最難部分)app

一、可能來自左子樹上的某課子樹this

二、可能來自右子樹上的某課子樹spa

三、整顆都是(左右子樹都是搜索二叉樹而且左子樹最大小於該節點,右子樹最小大於該節點)設計

 

 

 

第二步,收集信息:3d

一、左樹最大搜索子樹大小指針

二、右樹最大搜索子樹大小

三、左樹最大二叉搜索子樹的頭部(經過查看這個頭部是否等於節點的左孩子,來判斷整個左子樹是否都是二叉搜索樹)

四、右樹最大二叉搜索子樹的頭部

五、左樹最大值

六、右樹最小值

 

 

化簡爲一個信息體

一、左/右搜大小

二、左/右搜頭

三、左max

四、右min

 

無論左樹仍是右樹都存儲

一、最大搜索子樹大小

二、最大搜索子樹的頭部

三、這棵樹上的最大值和最小值

 

若是不理解能夠看引子題(很簡單的)

一棵樹中找最大最小

 

 

 

 

第三步,改遞歸(比較複雜)

先假設左和右都給我這樣的信息了,而後怎麼利用左邊和右邊的信息,組出來我該返回的信息。最後baseKey填什麼,搞定!

 

public class Code_04_BiggestSubBSTInTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node biggestSubBST(Node head) {
        int[] record = new int[3]; // 0->size, 1->min, 2->max
        return posOrder(head, record);
    }
    
    public static class ReturnType{
        public int size;
        public Node head;
        public int min;
        public int max;
        
        public ReturnType(int a, Node b,int c,int d) {
            this.size =a;
            this.head = b;
            this.min = c;
            this.max = d;
        }
    }
    
    public static ReturnType process(Node head) {
        if(head == null) {
            //設置系統最大最小爲了避免干擾判斷最大最小的決策
            return new ReturnType(0,null,Integer.MAX_VALUE, Integer.MIN_VALUE);
        }
        Node left = head.left;
        ReturnType leftSubTressInfo = process(left);//當成一個黑盒
        Node right = head.right;
        ReturnType rightSubTressInfo = process(right);
        
        int includeItSelf = 0;
        if(leftSubTressInfo.head == left 
                &&rightSubTressInfo.head == right
                && head.value > leftSubTressInfo.max
                && head.value < rightSubTressInfo.min
                ) {
            includeItSelf = leftSubTressInfo.size + 1 + rightSubTressInfo.size;
        }
        int p1 = leftSubTressInfo.size;
        int p2 = rightSubTressInfo.size;
        //解黑盒的過程
        int maxSize = Math.max(Math.max(p1, p2), includeItSelf);

        Node maxHead = p1 > p2 ? leftSubTressInfo.head : rightSubTressInfo.head;
        if(maxSize == includeItSelf) {
            maxHead = head;
        }
        
        return new ReturnType(maxSize,
                maxHead, 
                Math.min(Math.min(leftSubTressInfo.min,rightSubTressInfo.min),head.value),
                Math.max(Math.max(leftSubTressInfo.max,rightSubTressInfo.max),head.value));    
    }

    //數組實現版本
    public static Node posOrder(Node head, int[] record) {
        if (head == null) {
            record[0] = 0;
            record[1] = Integer.MAX_VALUE;
            record[2] = Integer.MIN_VALUE;
            return null;
        }
        int value = head.value;
        Node left = head.left;
        Node right = head.right;
        Node lBST = posOrder(left, record);
        int lSize = record[0];
        int lMin = record[1];
        int lMax = record[2];
        Node rBST = posOrder(right, record);
        int rSize = record[0];
        int rMin = record[1];
        int rMax = record[2];
        record[1] = Math.min(rMin, Math.min(lMin, value)); // lmin, value, rmin -> min 
        record[2] =  Math.max(lMax, Math.max(rMax, value)); // lmax, value, rmax -> max
        if (left == lBST && right == rBST && lMax < value && value < rMin) {
            record[0] = lSize + rSize + 1;
            return head;
        }
        record[0] = Math.max(lSize, rSize);
        return lSize > rSize ? lBST : rBST;
    }

    // for test -- print tree
    public static void printTree(Node head) {
        System.out.println("Binary Tree:");
        printInOrder(head, 0, "H", 17);
        System.out.println();
    }

    public static void printInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        printInOrder(head.right, height + 1, "v", len);
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.left, height + 1, "^", len);
    }

    public static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }

    public static void main(String[] args) {

        Node head = new Node(6);
        head.left = new Node(1);
        head.left.left = new Node(0);
        head.left.right = new Node(3);
        head.right = new Node(12);
        head.right.left = new Node(10);
        head.right.left.left = new Node(4);
        head.right.left.left.left = new Node(2);
        head.right.left.left.right = new Node(5);
        head.right.left.right = new Node(14);
        head.right.left.right.left = new Node(11);
        head.right.left.right.right = new Node(15);
        head.right.right = new Node(13);
        head.right.right.left = new Node(20);
        head.right.right.right = new Node(16);

        printTree(head);
        Node bst = biggestSubBST(head);
        printTree(bst);

    }

}

 

 

第二題,繼續套路:

二叉樹中,一個節點能夠往上走和往下走,那麼從節點A總能走到節點B。

節點A走到節點B的距離爲:A走到B最短路徑上的節點個數。

求一棵二叉樹上的最遠距離

 

 

列可能性:

一、來自左子樹最長距離

二、來自右子樹最長距離

三、通過X的狀況下的最遠距離,左樹最深+右樹最深+1

 

 

  

 

收集信息:

一、最長距離

二、深度

 

 

public class Code_03_MaxDistanceInTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static int maxDistance(Node head) {
        int[] record = new int[1];
        return posOrder(head, record);
    }
    
    public static class ReturnType{
        public int maxDistance;
        public int h;
        
        public ReturnType(int m, int h) {
            this.maxDistance = m;
            this.h = h;
        }
    }
    
    public static ReturnType process(Node head) {
        if(head == null) {
            return new ReturnType(0,0);
        }
        ReturnType leftReturnType = process(head.left);
        ReturnType rightReturnType = process(head.right);
        int includeHeadDistance = leftReturnType.h + 1 + rightReturnType.h;
        int p1 = leftReturnType.maxDistance;
        int p2 = rightReturnType.maxDistance;
        int resultDistance = Math.max(Math.max(p1, p2), includeHeadDistance);
        int hitSelf  = Math.max(leftReturnType.h, leftReturnType.h) + 1;
        return new ReturnType(resultDistance, hitSelf);
    }

    public static int posOrder(Node head, int[] record) {
        if (head == null) {
            record[0] = 0;
            return 0;
        }
        int lMax = posOrder(head.left, record);
        int maxFromLeft = record[0];
        int rMax = posOrder(head.right, record);
        int maxFromRight = record[0];
        int curNodeMax = maxFromLeft + maxFromRight + 1;
        record[0] = Math.max(maxFromLeft, maxFromRight) + 1;
        return Math.max(Math.max(lMax, rMax), curNodeMax);
    }

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.left = new Node(2);
        head1.right = new Node(3);
        head1.left.left = new Node(4);
        head1.left.right = new Node(5);
        head1.right.left = new Node(6);
        head1.right.right = new Node(7);
        head1.left.left.left = new Node(8);
        head1.right.left.right = new Node(9);
        System.out.println(maxDistance(head1));

        Node head2 = new Node(1);
        head2.left = new Node(2);
        head2.right = new Node(3);
        head2.right.left = new Node(4);
        head2.right.right = new Node(5);
        head2.right.left.left = new Node(6);
        head2.right.right.right = new Node(7);
        head2.right.left.left.left = new Node(8);
        head2.right.right.right.right = new Node(9);
        System.out.println(maxDistance(head2));

    }

}

 

擴充:若是是計算兩個固定節點a~b的距離,須要找出他們的最近公共祖先,而後計算a~公共祖先+b~公共祖先。

 

 

第三題

一個公司的上下節關係是一棵多叉樹,這個公司要舉辦晚會,你做爲組織者已經摸清了你們的心理:一個員工的直接上級若是到場,這個員工確定不會來。每一個員工都有一個活躍度的值,決定誰來你會給這個員工發邀請函,怎麼讓舞會的氣氛最活躍?返回最大的活躍值。

舉例:

給定一個矩陣來表述這種關係

matrix =

{

1,6

1,5

1,4

}

這個矩陣的含義是:

matrix[0] = {1 , 6},表示0這個員工的直接上級爲1,0這個員工本身的活躍度爲6

matrix[1] = {1 , 5},表示1這個員工的直接上級爲1(他本身是這個公司的最大boss),1這個員工本身的活躍度爲5

matrix[2] = {1 , 4},表示2這個員工的直接上級爲1,2這個員工本身的活躍度爲4

爲了讓晚會活躍度最大,應該讓1不來,0和2來。最後返回活躍度爲10

 

 

 

可能性

一、X來,活躍度就是x活躍度+x1不來+x2不來+x3不來的總和。

二、X不來,活躍度就是x1/x2/x3來和不來中選最大的總和。

 

 

 

收集信息:

一、一棵樹在頭結點來的活躍度

二、一棵樹在頭結點不來的活躍度

 

 

public class Code_04_MaxHappy {

    public static class Node{
        public int happy;
        public ArrayList<Node> nexts;

        public Node(int happy){
            this.happy  = happy;
            nexts = new ArrayList<Node>();
        }
    }

    public static class ReturnData{
        public int comeHappy;
        public int notComeHappy;

        public ReturnData(int c,int nc){
            comeHappy = c;
            notComeHappy = nc;
        }
    }

    public static ReturnData process(Node head){
        int comeHappy = head.happy;
        int notComeHappy = 0;

        for (int i = 0;i!=head.nexts.size();i++){
            ReturnData data = process(head.nexts.get(i));
            comeHappy += data.notComeHappy;
            notComeHappy += Math.max(data.notComeHappy,data.comeHappy);
        }
        return new ReturnData(comeHappy,notComeHappy);
    }

    public static int calcMaxHappy(Node head){
        ReturnData data = process(head);
        return Math.max(data.comeHappy, data.notComeHappy);
    }

    //下面是用數組結構去求
    public static int maxHappy(int[][] matrix) {
        int[][] dp = new int[matrix.length][2];
        boolean[] visited = new boolean[matrix.length];
        int root = 0;
        for (int i = 0; i < matrix.length; i++) {
            if (i == matrix[i][0]) {
                root = i;
            }
        }
        process(matrix, dp, visited, root);
        return Math.max(dp[root][0], dp[root][1]);
    }

    public static void process(int[][] matrix, int[][] dp, boolean[] visited, int root) {
        visited[root] = true;
        dp[root][1] = matrix[root][1];
        for (int i = 0; i < matrix.length; i++) {
            if (matrix[i][0] == root && !visited[i]) {
                process(matrix, dp, visited, i);
                dp[root][1] += dp[i][0];
                dp[root][0] += Math.max(dp[i][1], dp[i][0]);
            }
        }
    }

    public static void main(String[] args) {
        int[][] matrix = { { 1, 8 }, { 1, 9 }, { 1, 10 } };
        System.out.println(maxHappy(matrix));
    }
}

 

 

上述全部題目都叫樹形dp。(列可能性)

思路:小樹計算完,再算父親樹。

 

summary(總結)

一、分析可能性(先計算小樹,再計算大樹)

二、列信息全集,定下返回值結構。

三、編寫代碼的時候,默認每顆子樹都給你這樣的信息,而後看拿到這些子樹信息後怎麼加工出父的信息。

四、basekey要單獨考慮一下,做爲最簡單的狀況,要給父返回啥,不至於讓他干擾。

 

第四題:(基礎班講過)

判斷一棵樹是不是平衡二叉樹

 

public class c04_04IsBalancedTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static class ReturnData{
        public boolean isBalance;
        public int level;

        public ReturnData(boolean isBalance, int level) {
            this.isBalance = isBalance;
            this.level = level;
        }
    }

    public static ReturnData process(Node head){
        if(head == null){
            return new ReturnData(true,0);
        }
        //若是左子樹或者右子樹返回了他們不是平衡的,那整體也不會是平衡的
        ReturnData lRData = process(head.left);
        if(!lRData.isBalance){
            return new ReturnData(true,0);
        }
        ReturnData rRData = process(head.right);
        if(!rRData.isBalance){
            return new ReturnData(true,0);
        }
        if(Math.abs(lRData.level - rRData.level) > 1){
            return new ReturnData(true,0);
        }
        return new ReturnData(true,Math.max(lRData.level,rRData.level)+1);
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        head.right.right = new Node(7);

        System.out.println(process(head).isBalance);
    }

}

 

第五題:

數據結構設計題(LeetCode中等難度)難在code上

設計能夠變動的緩存結構(LRU)(常用的留下)

【題目】

設計一種緩存結構,該結構在構造時肯定大小,假設大小爲K,並有兩個功能:

set(key,value):將記錄(key,value)插入該結構。

get(key):返回key對應的value值。

【要求】

1.set和get方法的時間複雜度爲O(1)。

2.某個key的set或get操做一旦發生,認爲這個key的記錄成了最常用的。

3.當緩存的大小超過K時,移除最不常用的記錄,即set或get最久遠的。

【舉例】

假設緩存結構的實例是cache,大小爲3,並依次發生以下行爲:

1.cache.set("A",1)。最常用的記錄爲("A",1)。

2.cache.set("B",2)。最常用的記錄爲("B",2),("A",1)變爲最不常常的。

3.cache.set("C",3)。最常用的記錄爲("C",2),("A",1)仍是最不常常的。

4.cache.get("A")。最常用的記錄爲("A",1),("B",2)變爲最不常常的。

5.cache.set("D",4)。大小超過了3,因此移除此時最不常用的記錄("B",2),加入記錄 ("D",4),而且爲最常用的記錄,而後("C",2)變爲最不常用的記錄

 

 

 

思路:hash表(key,Node<key,value>內存地址)+定製的雙向鏈表(尾加頭出)

 

 

加入的時候先把節點從環境分離,掛到最後,再重連其餘節點。

有一個size記錄大小,在刪的時候能夠經過head指針把優先級最低的刪除,再根據key到hash裏面尋找並完全刪除。

 

 

public class Code_02_LRU {

    public static class Node<K,V> {
        public K key;
        public V value;
        public Node<K,V> last;
        public Node<K,V> next;

        public Node(K key,V value) {
            this.key = key;
            this.value = value;
        }
    }
    //定製的雙向鏈表
    public static class NodeDoubleLinkedList<K,V> {
        private Node<K,V> head;
        private Node<K,V> tail;

        public NodeDoubleLinkedList() {
            this.head = null;
            this.tail = null;
        }

        public void addNode(Node<K,V> newNode) {
            if (newNode == null) {
                return;
            }
            if (this.head == null) {
                this.head = newNode;
                this.tail = newNode;
            } else {//最新的添加到尾部
                this.tail.next = newNode;
                newNode.last = this.tail;//新節點的前一個是以前的尾部
                this.tail = newNode;
            }
        }
        //操做節點後把結點調整在尾部
        public void moveNodeToTail(Node<K,V> node) {
            if (this.tail == node) {
                return;
            }
            //先把節點從環境分離
            if (this.head == node) {
                this.head = node.next;
                this.head.last = null;
            } else {//中間的廣泛節點
                node.last.next = node.next;
                node.next.last = node.last;
            }
            node.last = this.tail;
            node.next = null;
            this.tail.next = node;
            this.tail = node;
        }
        //容量滿了刪除最不常常操做的數
        public Node<K,V> removeHead() {
            if (this.head == null) {
                return null;
            }
            Node<K,V> res = this.head;
            if (this.head == this.tail) {//只有一個節點
                this.head = null;
                this.tail = null;
            } else {
                this.head = res.next;
                res.next = null;
                this.head.last = null;
            }
            return res;
        }

    }

    public static class MyCache<K, V> {
        //經過key能夠找到Node
        private HashMap<K, Node<K,V>> keyNodeMap;
        private NodeDoubleLinkedList<K,V> nodeList;
        private int capacity;

        public MyCache(int capacity) {
            if (capacity < 1) {
                throw new RuntimeException("should be more than 0.");
            }
            this.keyNodeMap = new HashMap<K, Node<K,V>>();
            this.nodeList = new NodeDoubleLinkedList<K,V>();
            this.capacity = capacity;
        }

        public V get(K key) {
            if (this.keyNodeMap.containsKey(key)) {
                Node<K,V> res = this.keyNodeMap.get(key);
                this.nodeList.moveNodeToTail(res);
                return res.value;
            }
            return null;
        }

        public void set(K key, V value) {
            if (this.keyNodeMap.containsKey(key)) {
                Node<K,V> node = this.keyNodeMap.get(key);
                node.value = value;
                this.nodeList.moveNodeToTail(node);
            } else {//沒有就新增
                Node<K,V> newNode = new Node<K,V>(key,value);
                this.keyNodeMap.put(key, newNode);
                this.nodeList.addNode(newNode);
                if (this.keyNodeMap.size() == this.capacity + 1) {
                    this.removeMostUnusedCache();
                }
            }
        }

        private void removeMostUnusedCache() {
            Node<K,V> removeNode = this.nodeList.removeHead();//取出優先級最低的
            K removeKey = removeNode.key;
            this.keyNodeMap.remove(removeKey);
        }

    }

    public static void main(String[] args) {
        MyCache<String, Integer> testCache = new MyCache<String, Integer>(3);
        testCache.set("A", 1);
        testCache.set("B", 2);
        testCache.set("C", 3);
        System.out.println(testCache.get("B"));
        System.out.println(testCache.get("A"));
        testCache.set("D", 4);
        System.out.println(testCache.get("D"));
        System.out.println(testCache.get("C"));

    }

}

 

 

就是有限的幾個結構組成出來。(鏈表、hash)

 

 

 

自定義的Node,Map會存內存地址(8字節)。

 

回去看一下LFU。

相關文章
相關標籤/搜索