• LeetCode:2603. 收集树中金币 详解(2023.9.21 C++)


    目录

    2603. 收集树中金币

    题目描述:

    实现代码与解析:

    拓扑 + bfs

    原理思路:


    2603. 收集树中金币

    题目描述:

            给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间有一条边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 1 ,1 表示节点 i 处有一个金币。

    一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

    • 收集距离当前节点距离为 2 以内的所有金币,或者
    • 移动到树中一个相邻节点。

    你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

    如果你多次经过一条边,每一次经过都会给答案加一。

    示例 1:

    输入:coins = [1,0,0,0,0,1], edges = [[0,1],[1,2],[2,3],[3,4],[4,5]]
    输出:2
    解释:从节点 2 出发,收集节点 0 处的金币,移动到节点 3 ,收集节点 5 处的金币,然后移动回节点 2 。
    

    示例 2:

    输入:coins = [0,0,0,1,1,0,0,1], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[5,6],[5,7]]
    输出:2
    解释:从节点 0 出发,收集节点 4 和 3 处的金币,移动到节点 2 处,收集节点 7 处的金币,移动回节点 0 。
    

    提示:

    • n == coins.length
    • 1 <= n <= 3 * 104
    • 0 <= coins[i] <= 1
    • edges.length == n - 1
    • edges[i].length == 2
    • 0 <= ai, bi < n
    • ai != bi
    • edges 表示一棵合法的树。

    实现代码与解析:

    拓扑 + bfs

    1. class Solution {
    2. public:
    3. vector<int> h = vector<int>(30010, -1), e = vector<int>(60010, 0), ne = vector<int>(60100, 0), d = vector<int>(30010, 0);
    4. int idx = 0;
    5. void add(int a, int b)
    6. {
    7. e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
    8. }
    9. int collectTheCoins(vector<int>& coins, vectorint>>& edges) {
    10. int n = coins.size(); // 结点个数
    11. int rest = n; // 剩余的结点 rest翻译剩余
    12. for (auto t: edges)
    13. {
    14. int a = t[0];
    15. int b = t[1];
    16. add(a, b), add(b, a); // 加无向边
    17. d[a]++, d[b]++; // 两个结点 入度++
    18. }
    19. queue<int> q;
    20. // 删除树中 无金币 的叶子结点
    21. for (int i = 0; i < n; i++)
    22. if (d[i] == 1 && !coins[i]) q.push(i); // 无向边,入度为1的是叶子节点,要和有向的区分开
    23. while (q.size())
    24. {
    25. int t = q.front();
    26. d[t]--; // d[t]-- 此结点从1变为0 相当于删除此节点和边
    27. rest--; // 剩余结点--
    28. q.pop();
    29. for (int i = h[t]; ~i; i = ne[i])
    30. {
    31. int j = e[i];
    32. d[j]--; // 相邻结点 入度--
    33. if (d[j] == 1 && !coins[j]) q.push(j); // 入度为1的继续入队,因为有可能有新0金币结点 因为删除 变为叶子结点
    34. }
    35. }
    36. // 拓扑(拓扑排序中的两个去除环节 执行两次) 去除叶子结点 两次
    37. for (int i = 0; i < 2; i++)
    38. {
    39. queue<int> qq;
    40. for (int i = 0; i < n; i++)
    41. if (d[i] == 1) qq.push(i); // 叶子结点入队
    42. while(qq.size())
    43. {
    44. int t = qq.front();
    45. d[t]--;
    46. qq.pop();
    47. rest--;
    48. for (int i = h[t]; ~i; i = ne[i])
    49. {
    50. int j = e[i];
    51. d[j]--;
    52. }
    53. }
    54. }
    55. return rest == 0 ? 0 : 2 * (rest - 1);
    56. }
    57. };

    原理思路:

            首先去掉无金币的叶子结点,因为无金币的叶子结点是不需要收集的,收集绝对会浪费步数,以它为起点也会浪费步数,所以可以直接删除掉(连同它相连的边),直到叶子结点中没有无金币的。

            然后从叶子节点一层一层往剥,举个例子,比如一个三层的饼干,如果左右扩散为1,肯定是要选最中间的那层,就可以获得整个饼干(也就是去掉外面两侧,剩的那一层),如果是4层饼干,那么我们就要选中间两层(也就是去掉外面两侧,剩下的那二层)

            扩散为2,或者更多层数,也是同理,就是逆像思维从外一层一层往里剥(也就是拓扑),最后剩下的就一定是我们需要遍历。

            最后剩 n 个结点的话,需要全部走完,n个节点 n - 1 条边,每条边走两次才能回到原位,所以答案就为 2 * (n - 1)

            核心思想:删除没有金币的叶子节点,然后连续两次删除所有叶子节点。最终结果基于树中剩余节点的数量计算得出。

            代码的话,其实思想明白了就很好写了,就是基础的bfs和拓扑代码,如果不会拓扑可以看看我之前写的拓扑排序的写法和有关的题

    拓扑排序详解(带有C++模板)_Cosmoshhhyyy的博客-CSDN博客

    LeetCode:207. 课程表、210. 课程表 II(拓扑排序 C++)_Cosmoshhhyyy的博客-CSDN博客

            注意点:这里是无向图,我这种图的邻接表写法,需要二倍的边的个数,而且平时拓扑都是为0的时候入队,这里是1,因为是无向图嘛。最后如果没有节点,就返回0

  • 相关阅读:
    PCB线路板蛇形布线要注意哪些问题?
    “RunningEventsDuration“ app Tech Support(URL)
    汪汪熊の模板
    KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(9)
    冰冰学习笔记:反向迭代器的模拟
    ROS C++程序终止/结束进程&& 多线程终止运行程序
    GBase 8a语法格式
    shell编程4-shell嵌套循环及随机数
    栈和队列的应用 —— 循环队列
    tomcat配置jdk环境
  • 原文地址:https://blog.csdn.net/Cosmoshhhyyy/article/details/133153957