LeetCode 310. Minimum Height Trees 解法
最简单的解法就是DFS遍历,即从每个节点出发,计算到达末端节点的最大路径长度,那么此路径长度最小的就是最矮树的根(可能有多个)。但是这个方法会超时,代码如下(Java):
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if (n < 0) return new ArrayList<Integer>(0);
if (n == 1) return Arrays.asList(0);
// construct adjacent list
List<Integer>[] adj = (List<Integer>[]) new ArrayList[n];
for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
for (int[] edge : edges) {
adj[edge[0]].add(edge[1]);
adj[edge[1]].add(edge[0]);
}
int[] height = new int[n];
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
// DFS from node i and record maxHeight
Arrays.fill(visited, false);
int maxHeight = 0;
Stack<int[]> s = new Stack<>();
s.push(new int[] {i, 0});
while (!s.isEmpty()) {
int[] curr = s.pop();
visited[curr[0]] = true;
for (int next : adj[curr[0]]) {
if (!visited[next]) {
s.push(new int[] {next, curr[1] + 1});
}
}
maxHeight = Math.max(maxHeight, curr[1]);
}
height[i] = maxHeight;
}
int minHeight = height[0];
for (int h : height) minHeight = Math.min(minHeight, h);
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (minHeight == height[i]) {
result.add(i);
}
}
return result;
}
}
看来需要想一种聪明的办法。我们可以这样直观地想:每次删除这颗最矮树的叶子节点,删到最后,剩下的1个或者2个节点就是最矮树的根。我们可以确定最矮树的叶子节点就是所有度为1的叶子节点,也就是说只需要每次删除所有度为1的节点就可以了。接下来我们严格证明这样做是可行的:
Lemma: 设V为有向无环图G(N; E)中最矮树的根,且size(N) >= 3,则删除所有度为1的节点后,V仍然是G中最矮树的根。
Proof:
首先,V的度必不为1。若V的度为1,则它的相邻节点W的度一定大于1(否则G中就只有2个节点),且树W的高度比树V小1(画图看看就知道了),矛盾。因此删除的节点中不包括V。
在删除所有度为1的系欸但后,则删除了树V所有的叶子,使得它高度减小1。假设删除后最矮树的根(之一)为W,且W和V不同,则有:
height
after
(
W
)
<
height
before
(
V
)
−
1
\text{height}_{\text{after}}(W) < \text{height}_{\text{before}}(V) - 1
heightafter(W)<heightbefore(V)−1
height after ( W ) + 1 < height before ( V ) \text{height}_{\text{after}}(W) + 1 < \text{height}_{\text{before}}(V) heightafter(W)+1<heightbefore(V)
height before ( W ) < height before ( V ) \text{height}_{\text{before}}(W) < \text{height}_{\text{before}}(V) heightbefore(W)<heightbefore(V)
与删除前(before)树V的高度最小矛盾,求证得证。
好,算法题成功地被做成了数学题,接下来可以写代码了,代码的思路就是从叶子节点开始BFS(新的叶子一定是通过删除当前叶子产生的,因此只需检查与当前叶子的邻居的度),直到整个图遍历完了,就把最后一次删掉的节点返回。这段代码挺难写的,忽略一些特殊情况容易出bug。
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if (n <= 0) return new ArrayList<Integer>(0);
if (n == 1) return Collections.singletonList(0);
// construct adjacent list and compute degree
List<Integer>[] adj = (List<Integer>[]) new ArrayList[n];
int[] degree = new int[n];
Arrays.fill(degree, 0);
for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
for (int[] edge : edges) {
adj[edge[0]].add(edge[1]);
adj[edge[1]].add(edge[0]);
degree[edge[0]]++;
degree[edge[1]]++;
}
// just bfs, we need to know what
ArrayList<Integer> lastDelete = new ArrayList<>(); // last leaves
Queue<Integer> leaves = new LinkedList<>(); // current leaves
for (int i = 0; i < n; i++) {
if (degree[i] == 1) leaves.add(i);
}
while (!leaves.isEmpty()) {
// we need to delete all leaves
lastDelete.clear();
lastDelete.addAll(leaves);
int leafCount = leaves.size();
for (int i = 0; i < leafCount; i++) {
int leaf = leaves.remove();
degree[leaf]--; // becomes zero, no need in fact
for (int possibleNewLeaf : adj[leaf]) {
if (--degree[possibleNewLeaf] == 1) {
// it's new leaf, the condition includes some special cases
// special cases: [[0, 1]], [[0, 1], [0, 2]]
leaves.add(possibleNewLeaf);
}
}
}
}
return lastDelete;
}
}