【每日一题】2603. 收集树中金币

2023-09-21 10:49:38

Tag

【拓扑排序】【树】


题目来源

2603. 收集树中金币


题目解读

有一个有 n 个节点的无相无根图,节点编号从 0n-1。有一个表示图中节点间连接关系的数组 edges,长度为 n-1edges[i] = [ai, bi] 表示节点 aibi 之间有一条边。还有一个数组 coinscoins[i]0 或者 1,表示节点 i 处有一个金币。

你可以选择从任意一个节点出发,你可以收集距离当前节点距离为 2 以内的所有金币,或者移动到当前节点的相邻节点。你需要收集完所有的金币并且最后要返回出发的节点,返回你至少要经过的边数。

比如 示例 1 中,你从节点 2 出发,收集节点 0 处的金币,移动到节点 3 ,收集节点 5 处的金币,然后移动回节点 2。你一共经历了边 2——33——2,所以返回 2,你可以验证这是经历的最少的边数。

示例 1


解题思路

方法一:拓扑排序

我们要经历尽可能少的边来拿完所有的金币,因此一些不必要的边就可以不走,对于那些 “最外侧的” 没有金币的节点,我们不走就好了。而那些 “最外侧” 的节点就是树的叶子节点。

我们可以利用 度数 来判断 “最外侧”,将度数为 1 的节点认定是 “最外侧” 的节点,也就是叶子节点,对于去除了没有金币的叶子节点之后漏出的新的没有金币的叶子节点,同样是 “最外侧” 的节点,我们也要去除掉。从度数为 1 的节点出发,迭代去找度数为 1 的节点进行相应的操作,正是 拓扑排序

因为,树中一共有 n-1 条边,有 “最外侧” 节点的边不用走,所以在拓扑排序的时候同步更新剩余的边数 left_edges

经过第一次拓扑排序处理,现在树中的叶子节点都是包含金币的节点了,因为 “你可以收集距离当前节点距离为 2 以内的所有金币”,所以目前的叶子节点及其父节点都不需要走,我们也可以使用拓扑排序的思想砍掉不需要走的边。

我们只需要砍掉 叶子节点——叶节点的父节点 以及新漏出来的 叶子节点——叶节点的父节点 这两类边,因此,可以使用数组来模拟拓扑排序(我们只需要从当前叶子节点向上再找一层)。具体地:

  • 首先将叶子节点存入数组 nodes 中;
  • 接着 left_edges - nodes.size() 模拟砍掉不必要的边;
  • 然后遍历 ndoes,枚举数组 nodes 中的节点 x 的相邻节点 y,如果 --degree[y] == 1,则删除 x——y 之间的边即 --left_edges

最后剩下的 left_edges 就是需要经历的边,因为 “你需要收集完所有的金币并且最后要返回出发的节点”,所以最后的答案为 2 * left_edges

特别地,如果所有点都要被删除,那么当剩下两个点时,这两个点之间的边我们会删除两次,这会导致剩余边数等于 -1,而此时答案应该是 0。所以最后答案要和 0 取最大值。

以上内容部分参考 拓扑排序(Python/Java/C++/Go/JS/Rust)

实现代码

class Solution {
public:
    int collectTheCoins(vector<int>& coins, vector<vector<int>>& edges) {
        int n = coins.size();
        vector<int> degree(n);
        vector<vector<int>> graph(n);
        for (auto edge : edges) {
            int x = edge[0], y = edge[1];
            // 统计节点度数
            ++ degree[x];
            ++ degree[y];
            // 建边
            graph[x].push_back(y);
            graph[y].push_back(x);
        }

        int left_edges = n - 1;
        queue<int> que;
        for (int i = 0; i < n; ++i) {
            if (degree[i] == 1 && coins[i] == 0) {
                que.push(i);
            }
        }
		// 模拟删除不含金币的叶子节点以及新漏出的不含金币的叶子节点
        while (!que.empty()) {
            int x = que.front(); que.pop();
            -- left_edges;
            for (int y : graph[x]) {
                if (--degree[y] == 1 && coins[y] == 0) {
                    que.push(y);
                }
            }
        }

        vector<int> nodes;
        for (int i = 0; i < n; ++i) {
            if (degree[i] == 1 && coins[i] == 1) {
                nodes.push_back(i);
            }
        }
        left_edges -= nodes.size();
        
		// 模拟删除含金币的叶子节点以及新漏出的一层叶子节点
        for (int x : nodes) {
            for (int y : graph[x]) {
                if (--degree[y] == 1)
                    --left_edges;
            }
        }
        return max(left_edges *2, 0);
    } 
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 为树中节点个数。

空间复杂度: O ( n ) O(n) O(n)


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

更多推荐

netty之数据读写源码阅读

数据读写write从client端的写开始看client与服务端建立完connect后可以从future里拿到连接的channel对象。这里的channel是io.netty.channel.Channel对象。调用其channel.writeAndFlush(msg);方法可以进行数据发送。writeAndFlush

Android 应用上线注意事项

将Android应用上线到GooglePlay商店需要仔细注意一系列问题,以确保应用的质量、安全性和用户体验。以下是一些在Android应用上线过程中需要注意的关键问题,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。1.开发者账号:确保你拥有有效的GooglePlay开发者账号,可

【ASP.NET Core】应用脱机文件 (app_offline.htm)

文章目录概述锁定的部署文件来源jenkins进行CI失败是可能app_offline.htm不会被自动删除导致ASP.NETCore应用异常,发布成功后则需手动删除该文件概述在很多情况下,需要在对相关组件(如数据库或Web服务)进行更改时使Web应用程序脱机。通常,在IIS和ASP.NET中,可以通过将名为App_of

【深度学习实验】前馈神经网络(四):自定义逻辑回归模型:前向传播、反向传播算法

目录一、实验介绍二、实验环境1.配置虚拟环境2.库版本介绍三、实验内容0.导入必要的工具包1.逻辑回归Logistic类a.构造函数__init__b.__call__(self,x)方法c.前向传播forwardd.反向传播backward2.模型训练3.代码整合一、实验介绍实现逻辑回归模型(Logistic类)实现

动态规划:子序列问题(C++)

动态规划:子序列问题前言子序列问题1.最长递增子序列(中等)2.摆动序列(中等)3.最长递增子序列的个数(中等)4.最长数对链(中等)5.最长定差子序列(中等)6.最长的斐波那契子序列的长度(中等)7.最长等差序列(中等)8.等差数列划分II-子序列(困难)前言动态规划往期文章:动态规划入门:斐波那契数列模型以及多状态

8种结构型设计模式对比

一、适配器模式简介适配器模式是一种结构型设计模式,它用于将不兼容的接口转换为可兼容的接口。适配器模式允许两个不兼容的类能够协同工作,通过将一个类的接口转换为另一个类所期望的接口形式。这样就能够在不修改现有代码的情况下,使两个不兼容的类能够相互协作。使用场景适配器模式通常在以下场景中使用:当需要将现有类的接口转换为其他接

Llama2-Chinese项目:2.1-Atom-7B预训练

虽然Llama2的预训练数据相对于第一代LLaMA扩大了一倍,但是中文预训练数据的比例依然非常少,仅占0.13%,这也导致了原始Llama2的中文能力较弱。为了能够提升模型的中文能力,可以采用微调和预训练两种路径,其中:微调需要的算力资源少,能够快速实现一个中文Llama的雏形。但缺点也显而易见,只能激发基座模型已有的

el-table表格动态设置最大高度 高度根据窗口可视高度大小改变自适应

由于表格内容过多,如果不给高度限制,每页100条数据的情况下,去操作底部的分页或者其他功能都需要划到数据最底部操作,用户体验性较差。解决方法是让表格一屏展示,超出部分滚动展示。1.效果及思路图:思路是:屏幕可视区域-最大盒子的上下内边距-搜索部分-按钮部分-分页部分因为表格是项目公用表格每个项目需求不同根据实际需求去减

推荐一些常用的api接口,包括天气、物流、IP查询等

天气预报查询:支持输入经纬度或者区域编码查询全国以及全球多个城市的天气,包含15天天气预报查询。天气预警:支持输入经纬度或者区域编码,获取指定城市当前生效中的各类天气预警,如寒潮蓝色预警信号,或一次性拉取全国所有生效中的天气预警。空气质量查询:支持输入经纬度或者区域编码查询国内3400+个城市的整点观测,获取指定城市的

C++&QT day7

仿照vector手动实现自己的myVector,最主要实现二倍扩容功能#include<iostream>usingnamespacestd;template<typenameT>classmy_vector{intsize;//可存储的容量大小intnum;//当前存储的元素个数T*data;//存储数据的空间地址p

第74步 时间序列建模实战:多步滚动预测 vol-2(以决策树回归为例)

基于WIN10的64位系统演示一、写在前面上一期,我们讲了多步滚动预测的第一种策略:对于重复的预测值,取平均处理。例如,(1,2,3)预测出3.9和4.5,(2,3,4)预测出5.2和6.3,那么拼起来的结果就是3.9,(4.5+5.2)/2,6.3。这一期,我们来介绍第二种策略:删除一半的输入数据集。例如,4,5由(

热文推荐