树状DP

  • 求高
    104. Maximum Depth of Binary Tree
    559. Maximum Depth of N-ary Tree
  • 中最长链
    1245. Tree Diameter
  • 最大距离最小点
    310. Minimum Height Trees
  • 的重心
    P5666 树的重心
  • 最大 点独立集
    P2774 方格取数问题
  • 最小 点覆盖集
  • 最小 点支配集
  • 其他
    337. House Robber III
    P1352 没有上司的舞会
    968. Binary Tree Cameras
    834. Sum of Distances in Tree
    解法:一个简单的树形dp。

一. 求树高度

104. Maximum Depth of Binary Tree

//方法一
//have a global variable to keep track of the max depth
class Solution {
    int res = 0;
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        dfs(root, 1);
        return res;
    }
    private void dfs(TreeNode root, int h) {
        if (root == null) return;
        res = Math.max(res, h);
        dfs(root.left, h+1);
        dfs(root.right, h+1);
    }
}

//方法二
//从子树的视角来看,from the view of subtree
//if node u is leaf, dp[u] is 1
//else, dp[u] is max(dp[v]) + 1
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        return dfs(root);
    }
    private int dfs(TreeNode root) {
        if (root == null) return 0;
        return Math.max(dfs(root.left), dfs(root.right)) + 1;
    }
}

559. Maximum Depth of N-ary Tree

class Solution {
    public int maxDepth(Node root) {
        if (root == null) return 0;
        return dfs(root);
    }
    private int dfs(Node root) {
        if (root == null) return 0;
        int max = 0;
        for (Node child : root.children) {
            max = Math.max(max, dfs(child));
        }
        return max + 1;
    }
}

二. 求树中最长链 (树的直径)

树的性质:任意一对点之间的路径是唯一的

  • 方法1
    布鲁特福斯算法:从每个点出发,BFS ,find the farthest node,O(n^2)
  • 方法2
    贪心:任意一点a开始找最远的b,从b开始再找最远的c,则bc就是最长链(之一),两次BFS,O(n)


    树的直径.jpeg
class Solution {
    public int treeDiameter(int[][] edges) {
        //build graph
        int n = edges.length;
        List[] graph = new ArrayList[n+1];
        for (int i = 0; i <= n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : edges) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        
        //第一次 BFS
        Queue que = new LinkedList<>();
        boolean[] visited = new boolean[n+1];
        que.offer(0);
        visited[0] = true;
        int b = -1;
        while (!que.isEmpty()) {
            int cur = que.poll();
            b = cur;
            for (int next : graph[cur]) {
                if (visited[next]) continue;
                que.offer(next);
                visited[next] = true;
            }
        }
        
        //第二次 BFS
        visited = new boolean[n+1];
        que.offer(b);
        visited[b] = true;
        int step = 0;
        while (!que.isEmpty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                int cur = que.poll();
                for (int next : graph[cur]) {
                    if (visited[next]) continue;
                    que.offer(next);
                    visited[next] = true;
                }
            }
            step++;
        }
        return step - 1;
    }
}
  • 方法3
    树状dp
    对于node cur, 找到前两个child的depth,相加,更新global max length is possible
    return longest depth
class Solution {
    //树状dp
    int res = 0;
    public int treeDiameter(int[][] edges) {
        //build graph with adjacent list
        int n = edges.length;
        List[] graph = new ArrayList[n+1];
        for (int i = 0; i <= n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : edges) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        
        //do DFS from node 0, for each node, there are two cases
        //case1. the longest path go through the cur node, sum(longet path, second longest depth)
        //case2. the longest path don't go through cur node
        dfs(graph, 0, new boolean[n+1]);
        return res;
    }
    private int dfs(List[] graph, int cur, boolean[] visited) {
        if (graph[cur].size() == 1 && visited[graph[cur].get(0)]) return 1;         //cur node is leaf
        visited[cur] = true;
        int longest = -1, second = -1;
        for (int next : graph[cur]) {
            if (visited[next]) continue;
            int depth = dfs(graph, next, visited);
            //find the longest and second longest
            if (depth > longest) {
                second = longest;
                longest = depth;
            } else if (depth > second) {
                second = depth;
            }
        }
        if (longest != -1 && second != -1) res = Math.max(res, longest + second);   //have two or more children
        else if (longest != -1) res = Math.max(res, longest);                       //have only one child
        return longest + 1;
    }
}

三. 最大距离最小点(树的最小高度)

  • 方法1: longest path 取中间点,there maybe multiple longest paths, they share the same middle point(s)
  • 方法2: do BFS form all leaves, the indegree of leaves are 1, if the leaves are removed, some new leaves be be formed
  • 方法3: tree dp,
    For node cur, keep track of the longest path go through his child and the longest path go through his father, compare them, the longer one is the height of the tree that use this cur as root.
    Compute the longest path go through cur's child is easy, just do DFS, the key point is how to compute the longest path go through cur's father.
    We need the second longest path go through each node's children. why, because cur may be in the longest path of cur's father's longest down path, in this case, dpup[cur] is dpdown[cur's father] second longest + 1
//方法3. tree dp,11ms
class Solution {
    public List findMinHeightTrees(int n, int[][] edges) {
        //build graph
        List[] graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : edges) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        
        int[] fa = new int[n];
        fa[0] = -1;                     //0 is root
        int[][] dpdown = new int[n][2]; //dp[i][0] is the longest path, dp[i][1] is the second longest path
        int[] dpup = new int[n];
        dfs(graph, 0, fa, dpup, dpdown, new boolean[n]);
        
        //update dpup
        dpup[0] = 1;
        dfs2(graph, 0, fa, dpup, dpdown, new boolean[n]);
        
        List res = new ArrayList<>();
        int globalMin = Integer.MAX_VALUE;
        int ans1 = -1, ans2 = -1;
        for (int i = 0; i < n; i++) {
            int curMax = Math.max(dpdown[i][0], dpup[i]); 
            if (curMax < globalMin) {
                globalMin = curMax;
                ans1 = i;
                ans2 = -1;
            } else if (curMax == globalMin) {
                ans2 = i;
            }
        }
        res.add(ans1);
        if (ans2 != -1) res.add(ans2);
        return res;
    }
    private int dfs(List[] graph, int cur, int[] fa, int[] dpup, int[][] dpdown, boolean[] visited) {
        if (graph[cur].size() == 1 && visited[graph[cur].get(0)]) {
            dpdown[cur][0] = 1;
            return 1;
        }
        visited[cur] = true;
        int longest = -1, second = -1;
        for (int next : graph[cur]) {
            if (visited[next]) continue;
            fa[next] = cur;
            int depth = dfs(graph, next, fa, dpup, dpdown, visited);
            if (depth > longest) {
                second = longest;
                longest = depth;
            } else if (depth > second) {
                second = depth;
            }
        }
        
        if (longest != -1) {
            dpdown[cur][0] = longest + 1;
        } 
        if (second != -1) {
            dpdown[cur][1] = second + 1;
        }
        return longest + 1;
    }
    private void dfs2(List[] graph, int cur, int[] fa, int[] dpup, int[][] dpdown, boolean[] visited) {
        visited[cur] = true;
        if (cur != 0) {
            dpup[cur] = dpup[fa[cur]] + 1;
            if (dpdown[fa[cur]][0] == dpdown[cur][0] + 1) {   //cur在其father的最长子链上
                dpup[cur] = Math.max(dpup[cur], dpdown[fa[cur]][1] + 1); 
            } else {                                  
                dpup[cur] = Math.max(dpup[cur], dpdown[fa[cur]][0] + 1);
            }
        }
        for (int next : graph[cur]) {
            if (visited[next]) continue;
            dfs2(graph, next, fa, dpup, dpdown, visited);
        }
    }
}
//方法1, 11ms
class Solution {
    public List findMinHeightTrees(int n, int[][] edges) {
        List[] graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : edges) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        
        int b = -1;
        Queue que = new LinkedList<>();
        boolean[] visited = new boolean[n];       //because this is an undirected graph
        que.offer(0);
        visited[0] = true;
        while (!que.isEmpty()) {
            int cur = que.poll();
            b = cur;
            for (int next : graph[cur]) {
                if (visited[next]) continue;
                que.offer(next);
                visited[next] = true;
            }
        }
        
        visited = new boolean[n];
        int c = -1;
        int[] pre = new int[n];      //use array to keep track of path
        que.offer(b);
        visited[b] = true;
        int step = 0;
        while (!que.isEmpty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                int cur = que.poll();
                c = cur;
                for (int next : graph[cur]) {
                    if (visited[next]) continue;
                    pre[next] = cur;
                    que.offer(next);
                    visited[next] = true;
                }
            }
            step++;
        }
        
        int ans1 = -1, ans2 = -1;
        if (step % 2 == 0) {        //two middles
            for (int i = 0; i < step; i++) {
                if (i == step/2) ans1 = c;
                else if (i == step/2 - 1) ans2 = c;
                c = pre[c];
            }
        } else {
            for (int i = 0; i < step; i++) {
                if (i == step/2) ans1 = c;
                c = pre[c];
            }
        }
        
        List res = new ArrayList<>();
        res.add(ans1);
        if (ans2 != -1) res.add(ans2);
        return res;
    }
}
//方法2. indegree, 8ms
class Solution {
    public List findMinHeightTrees(int n, int[][] edges) {
        List[] graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        int[] indegree = new int[n];
        for (int[] e : edges) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
            indegree[e[0]]++;
            indegree[e[1]]++;
        }
        
        Queue que = new LinkedList<>();
        boolean[] visited = new boolean[n];
        for (int i = 0; i < n; i++) {
            if (indegree[i] <= 1) {
                que.offer(i);
                visited[i] = true;
            }
        }

        while (n > 2) {
            int size = que.size();
            n -= size;
            for (int i = 0; i < size; i++) {
                int cur = que.poll();
                for (int next : graph[cur]) {
                    if (visited[next]) continue;
                    indegree[next]--;
                    if (indegree[next] <= 1) {
                        que.offer(next);
                        visited[next] = true;
                    }
                }
            }
        }
        
        List res = new ArrayList<>();
        if (n == 1) {
            res.add(que.poll());
        } else {
            res.add(que.poll());
            res.add(que.poll());
        }
        return res;
    }
}

四. 树的重心


你可能感兴趣的:(树状DP)