- 求🌲高
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)
class Solution {
public int treeDiameter(int[][] edges) {
//build graph
int n = edges.length;
List<Integer>[] 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<Integer> 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<Integer>[] 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<Integer>[] 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<Integer> findMinHeightTrees(int n, int[][] edges) {
//build graph
List<Integer>[] 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<Integer> 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<Integer>[] 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<Integer>[] 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<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer>[] 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<Integer> 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<Integer> res = new ArrayList<>();
res.add(ans1);
if (ans2 != -1) res.add(ans2);
return res;
}
}
//方法2. indegree, 8ms
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer>[] 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<Integer> 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<Integer> res = new ArrayList<>();
if (n == 1) {
res.add(que.poll());
} else {
res.add(que.poll());
res.add(que.poll());
}
return res;
}
}
四. 树的重心