题面
原来的题面
大致意思是给你一个树,然后让你选择一些点作为根,使得树的高度最小。
题解
先说结论,在树的直径上,位于中间的一个或者两个点(奇数就是一个点,偶数就是一个点),至于树的直径是啥,就是树上最长的一条路径,大家可以去搜索一下。
那么怎么求这个直径呢?一般我们从一个随机的点
a
a
a 出发,找到距离他最远的一个点
b
b
b ,然后再次从
b
b
b 点出发,找到距离
b
b
b 点最远的点
c
c
c ,那么从
b
b
b 到
c
c
c 的路径就是这棵树的一条直径。
而对于直径来说,考虑他的性质,我们发现。
例如上图,有好几个直径,并且有分叉,分为两个部分({5,6,7} 和 {0,1,2,3,4}),{5,6,7} 组成直径更长的一半,剩下的组成更短的一半,我们发现即使路径长度是偶数(大家可以自己画一下),我们需要的点也是在一条简单路径下。
所以我们先用 dfs 求出直径的长度和任意一个直径的任意一个端点,然后从这个端点继续 dfs,当通过回溯发现这是一个处于直径上的端点时,我们再次判断目前距离是否是整个长度的一半,如果是的话就把这个端点存下来。
代码
class Solution
{
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>> &edges)
{
#define maxn 20005
vector<int> to[maxn];
for (int i = 0; i < edges.size(); i++) // 建一个无向图
to[edges[i][0]].push_back(edges[i][1]), to[edges[i][1]].push_back(edges[i][0]);
int max_dis = 0, pos = -1;
function<void(int, int, int)> dfs = [&](int x, int dis, int fa) // 用于查找直径长度和端点的函数
{
if (dis > max_dis)
{
max_dis = dis;
pos = x;
}
for (int i = 0; i < to[x].size(); i++)
{
int v = to[x][i];
if (v == fa)
continue;
dfs(v, dis + 1, x);
}
};
dfs(0, 1, -1); // 先找端点 b
dfs(pos, 1, -1); // 在找 c 的同时,再找直径长度
vector<int> ans;
int halfDis1 = (max_dis + 1) / 2; // 距离端点的长度有两种可能性
int halfDis2 = (max_dis % 2 == 0 ? halfDis1 + 1 : halfDis1);
function<bool(int, int, int)> dfs1 = [&](int x, int dis, int fa) // 找根
{
bool f = false;
if (dis == max_dis) // 说明之前经过的点是直径上的点
f = true;
for (int i = 0; i < to[x].size(); i++)
{
int v = to[x][i];
if (v == fa)
continue;
if (dfs1(v, dis + 1, x)) // 回溯发现目前所处的点在直径上
f = true;
}
if ((dis == halfDis1 || dis == halfDis2) && f == true) // 如果发现这个点距离端点的距离恰好是直径长度的一般,这个点可以作为根
ans.push_back(x);
return f;
};
dfs1(pos, 1, -1);
#undef maxn
return ans;
}
};
然后就搞定了,但是为啥大家都这么快……
作者能力有限,如果有任何错误之处,还请各位指教。(~ ̄▽ ̄)~