【每日一题Day333】LC2603收集树中金币 | 拓扑排序

2023-09-21 09:39:23

收集树中金币【LC2603】

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

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

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

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

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

  • 思路:拓扑排序+贪心

    • 首先,我们可以去掉不包含金币的子树【步骤a】,因为访问其中任何一个点都毫无意义。
      • 实现:拓扑排序,去除不含金币的叶子节点
    • 如果所有在叶子上的金币全部都能收集到,那么我们可以收集到树上所有金币。而由于可以「收集距离当前节点距离为 2以内的所有金币」,因此可以再次去除两轮有金币的叶子【步骤b】,剩余的结点即为必须经过的结点
      • 贪心:从距离有金币叶子为2的节点处出发【局部最优】,收集树中所有的金币,并且回到出发节点时经过的边数最少【全局最优】。
      • 实现:再次进行拓扑排序
    • 最终答案:从某个节点出发后,仍需要回到起点;因此最终答案为剩余边数乘以2。
      • 实现1:统计每个必须经过的节点的入队时间,如果一条边的两个节点的入队时间均大于2,那么这条边必须经过两次
      • 实现2:在拓扑排序时记录不需要遍历的边的数目【步骤a和步骤b的边数】,最后需要进行特判,如果所有节点均需要删除时,答案为-1,此时应与0取较大值
  • 实现

    • 无向图,节点度为1时为叶子
    class Solution {
        public int collectTheCoins(int[] coins, int[][] edges) {
            int n = coins.length;
            List<Integer> g[] = new ArrayList[n];
            Arrays.setAll(g, e -> new ArrayList<>());
            var deg = new int[n];
            for (var e : edges) {
                int x = e[0], y = e[1];
                g[x].add(y);
                g[y].add(x); // 建图
                ++deg[x];
                ++deg[y];
            }
    
            // 用拓扑排序「剪枝」:去掉没有金币的子树
            var q = new ArrayDeque<Integer>();
            for (int i = 0; i < n; ++i)
                if (deg[i] == 1 && coins[i] == 0) // 无金币叶子
                    q.add(i);
            while (!q.isEmpty()) {
                int x = q.peek();
                q.pop();
                for (int y : g[x])
                    if (--deg[y] == 1 && coins[y] == 0)// 无金币叶子
                        q.add(y);
            }
    
            // 再次拓扑排序
            for (int i = 0; i < n; ++i)
                if (deg[i] == 1 && coins[i] == 1) // 有金币叶子
                    q.add(i);
            if (q.size() <= 1) return 0; // 至多一个有金币的叶子,直接收集
            var time = new int[n];
            while (!q.isEmpty()) {
                int x = q.peek();
                q.pop();
                for (int y : g[x])
                    if (--deg[y] == 1) {
                        time[y] = time[x] + 1; // 记录入队时间
                        q.add(y);
                    }
            }
    
            // 统计答案
            int ans = 0;
            for (var e : edges)
                if (time[e[0]] >= 2 && time[e[1]] >= 2)
                    ans += 2;
            return ans;
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度

      • 时间复杂度: O ( E + V ) O(E+V) O(E+V) E E E表示邻边的跳数,V为结点的个数

      • 空间复杂度: O ( V ) O(V) O(V)

  • 实现2

    class Solution {
        public int collectTheCoins(int[] coins, int[][] edges) {
            int n = coins.length;
            List<Integer> g[] = new ArrayList[n];
            Arrays.setAll(g, e -> new ArrayList<>());
            var deg = new int[n];
            for (var e : edges) {
                int x = e[0], y = e[1];
                g[x].add(y);
                g[y].add(x); // 建图
                deg[x]++;
                deg[y]++; // 统计每个节点的度数(邻居个数)
            }
    
            int leftEdges = n - 1; // 剩余边数
            // 拓扑排序,去掉没有金币的子树
            var q = new ArrayDeque<Integer>();
            for (int i = 0; i < n; i++) {
                if (deg[i] == 1 && coins[i] == 0) { // 没有金币的叶子
                    q.add(i);
                }
            }
            while (!q.isEmpty()) {
                leftEdges--; // 删除节点到其父节点的边
                for (int y : g[q.poll()]) {
                    if (--deg[y] == 1 && coins[y] == 0) { // 没有金币的叶子
                        q.add(y);
                    }
                }
            }
    
            // 再次拓扑排序
            for (int i = 0; i < n; i++) {
                if (deg[i] == 1 && coins[i] == 1) { // 有金币的叶子(判断 coins[i] 是避免把没有金币的叶子也算进来)
                    q.add(i);
                }
            }
            leftEdges -= q.size(); // 删除所有叶子(到其父节点的边)
            for (int x : q) { // 遍历所有叶子
                for (int y : g[x]) {
                    if (--deg[y] == 1) { // y 现在是叶子了
                        leftEdges--; // 删除 y(到其父节点的边)
                    }
                }
            }
            return Math.max(leftEdges * 2, 0);
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度

      • 时间复杂度: O ( E + V ) O(E+V) O(E+V) E E E表示邻边的跳数,V为结点的个数

      • 空间复杂度: O ( V ) O(V) O(V)

  • 升级:如果把题目中的 2 换成 0,1,2,3,⋯ ,n−1,你能把这些情况对应的答案全部算出来吗?

    遍历所有的边,如果两个节点的入队时间均大于q(最大距离),那么表示这条边必须要经过,结果+2

更多推荐

肖sir___环境的讲解__001

环境的讲解一、搭建环境此测试环境主要用于功能测试、寻找bug、编写后台测试点、熟悉环境的架构,搭建流程二、搭建多有米前后台所需要的工具包1、虚拟机(centos6.5)2、数据库3、代码包4、服务器5、数据库脚本6、jdk三、搭建测试环境实战1、将本地的服务器上传到linux中,并解压tar-zxvf服务器包名2、上传

利用cms主题构造木马(CVE-2022-26965)

简介CVE-2022-26965是PluckCMS4.7.16版本存在一个远程shell上传执行漏洞。攻击者可利用此漏洞通过构造恶意的主题包进行上传并执行,未经授权访问服务器,造成潜在的安全隐患。过程1.打开环境,查看源码,发现login.php2.进入查看,登陆页面,弱口令admin进行登录,全英文界面,可以翻译的哈

Hyperopt:分布式异步超参数优化(Distributed Asynchronous Hyperparameter Optimization)

1、概述在深度学习的训练模型过程中,参数的优化是一个比较繁琐的过程,一般使用网格搜索Gridsearch与人工搜索Manualsearch,所以这个参数优化有时候看起来就像太上老君炼丹,是一个有点玄的东西。那有没有一种可以自动去调优的工具呢?恩,本节介绍的这个Hyperopt工具就是这个用途。Hyperopt是一个Py

下一代实时数据库:Apache Doris 【一】简介

文章目录第1章Doris简介1.1Doris概述1.2Doris架构后记第1章Doris简介1.1Doris概述ApacheDoris由百度大数据部研发(之前叫百度Palo,2018年贡献到Apache社区后,更名为Doris),在百度内部,有超过200个产品线在使用,部署机器超过1000台,单一业务最大可达到上百TB

下一代实时数据库:Apache Doris 【二】编译与安装

文章目录第2章编译与安装2.1安装Docker环境2.2使用Docker开发镜像编译后记第2章编译与安装安装Doris,需要先通过源码编译,主要有两种方式:使用Docker开发镜像编译(推荐)、直接编译。直接编译的方式,可以参考官网:https://doris.apache.org/zh-CN/installing/c

vuex的state,getters,mutations,actions,modules

目录Vuex核心概念:1、State1)全局state2)使用modules中的state2、Getters1)全局Getters2)使用modules中的getters3、Mutations1)全局Mutations2)使用modules中的mutations(namespaced:true)4、Actions1)全

Day69:283. 移动零、11. 盛最多水的容器、42. 接雨水

283.移动零leetcode链接:https://leetcode.cn/problems/move-zeroes/给定一个数组nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。请注意,必须在不复制数组的情况下原地对数组进行操作。示例1:输入:nums=[0,1,0,3,12]输出:[1,3

华为云云耀云服务器L实例评测|怎么搭建企业综合Web平台

前言记得2019年,公司搞混合云的时候,测试过多家公有云,其中就有华为云。因公司也在深圳,项目也比较急,我司业务上云经验又不足,华为官方获悉情况后,第二天就派了4人小团队到我司来交流,整个交流过程非常流畅,从华为云的优势,华为POP点到我司机房网络,我司金融Web业务的特点,金融行业的通用迁云经验,甚至到后台团队连线,

数据库的模糊查询

命中率越高–策略越好数据库的模糊查询work918在SQL中,模糊查询可以使用LIKE关键字来实现。LIKE关键字后面可以跟一个模式,其中%表示任意数量的字符,_表示一个字符。例如,如果你想在一个名为students的表中查找所有名字以Li开头的学生,你可以这样做:SELECT*FROMstudentsWHEREnam

uview组件库的安装

更多的请查看官方文档uView2.0-全面兼容nvue的uni-app生态框架-uni-appUI框架(uviewui.com)//如果您的根目录没有package.json文件的话,请先执行如下命令://npminit-y安装npminstalluview-ui@2.0.36//更新//npmupdateuview-

【基本数据结构 四】线性数据结构:队列

学习了栈后,再来看看第四种线性表结构,也就是队列,队列和栈一样也是一种受限的线性表结构,和栈后进先出的操作方式不同的是,队列是FIFO的结构,也就是先进先出的操作方式。队列的定义队列这个概念非常好理解。可以把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列”.栈只支持两个基本

热文推荐