本文涉及的基础知识点
LeetCode3203. 合并两棵树后的最小直径
给你两棵 无向 树,分别有 n 和 m 个节点,节点编号分别为 0 到 n - 1 和 0 到 m - 1 。给你两个二维整数数组 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [ai, bi] 表示在第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [ui, vi] 表示在第二棵树中节点 ui 和 vi 之间有一条边。
你必须在第一棵树和第二棵树中分别选一个节点,并用一条边连接它们。
请你返回添加边后得到的树中,最小直径 为多少。
一棵树的 直径 指的是树中任意两个节点之间的最长路径长度。
示例 1:
输入:edges1 = [[0,1],[0,2],[0,3]], edges2 = [[0,1]]
输出:3
解释:
将第一棵树中的节点 0 与第二棵树中的任意节点连接,得到一棵直径为 3 的树。
示例 2:
输入:edges1 = [[0,1],[0,2],[0,3],[2,4],[2,5],[3,6],[2,7]], edges2 = [[0,1],[0,2],[0,3],[2,4],[2,5],[3,6],[2,7]]
输出:5
解释:
将第一棵树中的节点 0 和第二棵树中的节点 0 连接,可以得到一棵直径为 5 的树。
提示:
1 <= n, m <= 105
edges1.length == n - 1
edges2.length == m - 1
edges1[i].length == edges2[i].length == 2
edges1[i] = [ai, bi]
0 <= ai, bi < n
edges2[i] = [ui, vi]
0 <= ui, vi < m
输入保证 edges1 和 edges2 分别表示一棵合法的树。
图论 树
树的层次,距离根的距离,根的层次是0。
树的半径,树的最大层次。
树的重心,树的层次最小的根。
分两种情况:
新树的直径是旧树的直径。
新树的直径跨越两棵树,即两棵树的半径+1。
性质一:如果树的直径是d,令两个端点是n1,n2。d是偶数。则树的半径r = d/2。重心就是直径的中点o。一定不存在端点n3到o的距离大于r。否则n1到n3的距离大于d,与假设矛盾。
性质二:如果d是奇数,直径的两个中点都是重心,r = d/2+1。除了n1,n2外,不存在端点n3距离o的距离大与d/2。
总结: r = (d+1)/2
性质三:如果d是偶数,可以有多条直径,这些直径有共同的中心。否则与性质一矛盾。
性质四:如果d是奇数,只能有一条直径,否则与性质二矛盾。
求树的直径方法一:DFS
以任意节点为根DFS,枚举各子树的直径。DFS(cur)返回值:cur子树的最大层次,cur子树的直径。
求树的直径方法二:两轮BFS
以任意节点root,BFS到任意最大层次节点n3,n3 一定是直径的一个端点。令此树的重心是o,某直径端点是n1,n2。o到n3的路径上不可能同时包括n1,n2。不失一般性,节点n2不在o到n3的路径上。由于o是重心,root到非o方向的距离一定小于等于r,否则此节点到n2的距离大于直径,与题设矛盾。root到o方向的最大距离显然是n2。
以n3为根的最大层次,就是直径。
代码
核心代码
class CNeiBo
{
public:
static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
}
}
return vNeiBo;
}
static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<std::pair<int, int>>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
}
}
return vNeiBo;
}
static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat)
{
vector<vector<int>> neiBo(neiBoMat.size());
for (int i = 0; i < neiBoMat.size(); i++)
{
for (int j = i + 1; j < neiBoMat.size(); j++)
{
if (neiBoMat[i][j])
{
neiBo[i].emplace_back(j);
neiBo[j].emplace_back(i);
}
}
}
return neiBo;
}
};
class CBFSLeve {
public :
static vector<int> Leve(const vector<vector<int>>& neiBo, vector<int> start) {
vector<int> leves(neiBo.size(), -1);
for (const auto& s : start) {
leves[s] = 0;
}
for (int i = 0; i < start.size(); i++) {
for (const auto& next : neiBo[start[i]]) {
if (-1 != leves[next]) { continue; }
leves[next] = leves[start[i]] + 1;
start.emplace_back(next);
}
}
return leves;
}
template<class NextFun>
static vector<int> Leve(int N,NextFun nextFun, vector<int> start) {
vector<int> leves(N, -1);
for (const auto& s : start) {
leves[s] = 0;
}
for (int i = 0; i < start.size(); i++) {
auto nexts = nextFun(start[i]);
for (const auto& next : nexts) {
if (-1 != leves[next]) { continue; }
leves[next] = leves[start[i]] + 1;
start.emplace_back(next);
}
}
return leves;
}
static vector<vector<int>> LeveNodes(const vector<int>& leves) {
const int iMaxLeve = *max_element(leves.begin(), leves.end());
vector<vector<int>> ret(iMaxLeve + 1);
for (int i = 0; i < leves.size(); i++) {
ret[leves[i]].emplace_back(i);
}
return ret;
};
};
class Solution {
public:
int minimumDiameterAfterMerge(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {
const int D1 = D(edges1);
const int D2 = D(edges2);
return max(max(D1, D2), (D1 + 1) / 2 + (D2 + 1) / 2 + 1);
}
int D(vector<vector<int>>& edges) {
auto neiBo = CNeiBo::Two(edges.size() + 1, edges, false);
auto leves = CBFSLeve::Leve(neiBo, { 0 });
int inx = max_element(leves.begin(), leves.end())- leves.begin();
auto leves2 = CBFSLeve::Leve(neiBo, { inx });
return *max_element(leves2.begin(), leves2.end());
}
};
单元测试
vector<vector<int>> edges1,edges2;
TEST_METHOD(TestMethod11)
{
edges1 = { {0,1},{0,2},{0,3} }, edges2 = { {0,1} };
auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
AssertEx(3, res);
}
TEST_METHOD(TestMethod12)
{
edges1 = { {0,1},{0,2},{0,3},{2,4},{2,5},{3,6},{2,7} }, edges2 = { {0,1},{0,2},{0,3},{2,4},{2,5},{3,6},{2,7} } ;
auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
AssertEx(5, res);
}
TEST_METHOD(TestMethod13)
{
edges1 = { {1,0},{2,3},{1,4},{2,1},{2,5} }
, edges2 = { {4,5},{2,6},{3,2},{4,7},{3,4},{0,3},{1,0},{1,8} };
auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
AssertEx(6, res);
}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。