leetcode每日一题:2603.收集金币
定义一个变量 cnt ,记录此时图中剩余的边数,初始时为 n-1
1. 删除没有操作必要性的节点
对于下图所示的示例
(灰色表示没有金币,金色表示有金币) 节点6和节点9就属于没有必要操作的节点,我们把度为1的节点定义为叶子节点,那么节点6和节点9就是属于没有金币的叶子节点,这些节点没有操作的必要性,因此是可以去除的。如果去除过程中又产生了新的叶子节点,那么也一并去除;
我们选用拓扑排序来去除,对于某一个节点 u ,如果其没有金币并且度数为1,那么该节点就属于此次操作要去除的节点,并且去除 u 时,与之相邻的其他节点 v 度数也需要减少,每次删除一个叶子节点 u , cnt 减少1
2. 删除范围2内的节点
对于操作1后,图的情况如上图所示,由于可以收集距离当前节点距离为 2 以内的所有金币,因此,我们不必要到达金币所在的节点,而只需要到达金币所在节点的父节点的父节点,就可以收集到该金币. 那么我们就可以把含有金币的叶子节点去掉,并一并去掉过程中产生的其他叶子节点;而剩下的节点,就是必须要访问的节点;每去掉一个节点,cnt 减 1
3. 得出结果
通过上述两个操作之后,剩余的节点就是必须要访问的节点,无论此时从哪个节点出发(1,2,0) 都必须要原路去原路回,因此,需要走的步数都是剩余的边数 cnt 的两倍
class Solution {
public:
int collectTheCoins(vector<int>& coins, vector<vector<int>>& edges) {
int n = coins.size();
vector<vector<int>> edge(n);
vector<int> deg(n, 0);
for (const auto& e : edges) {
int u = e[0];
int v = e[1];
edge[u].emplace_back(v);
edge[v].emplace_back(u);
deg[u]++;
deg[v]++;
}
// 记录剩余的边数
int leftEdges = n - 1;
queue<int> q;
// 操作1,去掉没有操作必要的叶子节点
for (int i = 0; i < n; i++) {
if (deg[i] == 1 && coins[i] == 0)
q.emplace(i);
}
while (!q.empty()) {
leftEdges--;
int u = q.front();
q.pop();
for (const auto& v : edge[u]) {
if (--deg[v] == 1 && coins[v] == 0)
q.emplace(v);
}
}
// 操作2 ,去掉含有金币的叶子节点,并且一并去掉过程中产生的新的叶子节点
for (int i = 0; i < n; i++) {
if (deg[i] == 1 && coins[i] == 1)
q.emplace(i);
}
// 更新剩余边数
leftEdges -= q.size();
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : edge[u]) {
if (--deg[v] == 1)
leftEdges--;
}
}
// 剩余的边数就是必须要访问的边数,可能为负数(假如一开始就只有两个节点并且都有金币)
return max(2 * leftEdges, 0);
}
};
参考题解:灵茶山艾府 链接:https://leetcode.cn/problems/collect-coins-in-a-tree/