题目
给你一个无向连通图,包含 n 个节点,节点编号从 0 到 n - 1,以及一个二维整数数组 edges,其中 edges[i] = [ui, vi, wi] 表示一条连接节点 ui 和节点 vi 的无向边,边权为 wi,另有一个整数 k。
你可以从图中移除任意数量的边,使得最终的图中最多只包含 k 个连通分量。
连通分量的成本定义为该分量中边权的最大值。如果一个连通分量没有边,则其代价为 0。
请返回在移除这些边之后,在所有连通分量之中的最大成本的最小可能值。
示例 1:
- 输入:n = 5, edges = [[0,1,4],[1,2,3],[1,3,2],[3,4,6]], k = 2
- 输出:4
示例 2:
- 输入:n = 4, edges = [[0,1,5],[1,2,5],[2,3,5]], k = 1
- 输出:5
思路
这是一道经典的二分搜索 + 并查集问题。
核心思路:
- 二分搜索答案:我们要找的是"最大成本的最小可能值",这提示我们可以二分搜索这个答案
- 判断可行性:对于每个候选答案
mid,判断是否能通过只使用权重 ≤mid的边来形成至多 k 个连通分量 - 并查集合并:使用并查集来统计连通分量的数量
算法步骤:
- 二分搜索的范围:
left = 0,right = max(所有边权) - 对于每个
mid:- 只考虑权重 ≤
mid的边 - 使用并查集合并这些边连接的节点
- 统计最终的连通分量数量
- 如果连通分量数量 ≤ k,说明答案可能更小
- 否则,答案需要更大
- 只考虑权重 ≤
关键洞察:
- 我们希望连通分量尽可能少(≤ k),所以应该尽可能多地保留边
- 但同时希望每个连通分量的最大边权尽可能小
- 通过二分搜索找到平衡点
代码
C++
class Solution {
public:
int minCost(int n, vector<vector<int>>& edges, int k) {
int left = 0, right = 0;
// 找到所有边权的最大值作为搜索上界
for (auto& edge : edges) {
right = max(right, edge[2]);
}
// 二分搜索答案
while (left < right) {
int mid = left + (right - left) / 2;
if (canFormKComponents(n, edges, k, mid)) {
right = mid; // 可以形成≤k个连通分量,尝试更小的答案
} else {
left = mid + 1; // 不能形成≤k个连通分量,需要更大的答案
}
}
return left;
}
private:
bool canFormKComponents(int n, vector<vector<int>>& edges, int k, int maxCost) {
// 初始化并查集
vector<int> parent(n);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
};
int components = n; // 初始有n个连通分量
// 只考虑权重≤maxCost的边
for (auto& edge : edges) {
if (edge[2] <= maxCost) {
int u = find(edge[0]);
int v = find(edge[1]);
if (u != v) {
parent[u] = v;
components--; // 合并两个连通分量
}
}
}
return components <= k;
}
};
Java
class Solution {
public int minCost(int n, int[][] edges, int k) {
int left = 0, right = 0;
for (int[] edge : edges) {
right = Math.max(right, edge[2]);
}
while (left < right) {
int mid = left + (right - left) / 2;
if (canFormKComponents(n, edges, k, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
private boolean canFormKComponents(int n, int[][] edges, int k, int maxCost) {
int[] parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
int components = n;
for (int[] edge : edges) {
if (edge[2] <= maxCost) {
int u = find(parent, edge[0]);
int v = find(parent, edge[1]);
if (u != v) {
parent[u] = v;
components--;
}
}
}
return components <= k;
}
private int find(int[] parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
}
Python
class Solution:
def minCost(self, n: int, edges: List[List[int]], k: int) -> int:
left, right = 0, max(edge[2] for edge in edges)
while left < right:
mid = (left + right) // 2
if self.canFormKComponents(n, edges, k, mid):
right = mid
else:
left = mid + 1
return left
def canFormKComponents(self, n: int, edges: List[List[int]], k: int, maxCost: int) -> bool:
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
components = n
for u, v, w in edges:
if w <= maxCost:
pu, pv = find(u), find(v)
if pu != pv:
parent[pu] = pv
components -= 1
return components <= k
复杂度分析
时间复杂度
O(E × log(max_weight) × α(n)),其中:
- E 是边的数量
- max_weight 是所有边权的最大值
- α(n) 是阿克曼函数的反函数(并查集的时间复杂度)
- 二分搜索需要 O(log(max_weight)) 次
- 每次判断需要 O(E × α(n)) 时间
空间复杂度
O(n),用于存储并查集的 parent 数组。
结果
- 通过所有测试用例
- 执行用时:较快
- 内存消耗:较少
总结
这道题是一道经典的图论 + 二分搜索问题,考查的是:
- 二分搜索:识别"最小化最大值"的问题模式
- 并查集:高效地统计连通分量数量
- 贪心思想:在满足连通分量数量限制的前提下,尽可能使用小权重的边
解题要点:
- 理解题目要求:我们要最小化"所有连通分量中成本的最大值"
- 二分搜索的对象是答案(最大成本),而不是边的索引
- 判断函数的核心是:给定最大成本限制,能否形成至多k个连通分量
- 并查集的路径压缩优化很重要
类似题目:
- 最小生成树相关问题
- 其他"最小化最大值"的二分搜索问题
这道题很好地结合了图论和二分搜索,是一道很有价值的练习题。
8万+

被折叠的 条评论
为什么被折叠?



