【图论】【树】 【拓扑排序】2603. 收集树中金币

本文涉及知识点

图论 树 拓扑排序

LeetCode 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 表示一棵合法的树。

拓扑排序

利用拓扑排序不断删除没有金币的叶子节点,不影响答案。
如果根节点没有金币且只有一个孩子,删除。它的孩子成为新的根。简便方式是直接选择有金币的节点为根。其实,拓扑排序后,不会存在这样的根。
一个子树cur有若干金币,且叶子节点的最早公共祖先是cur。则收集所有金币只需要:移动到叶子祖父节点,再回来。
如果cur是整个树的根,则收集结束。等于:除非叶子节点、非叶子节点父亲外所有节点 × \times ×*2。
如果不是根,cur 需要通过根收集金币,所以cur → \rightarrow root 路径上的边,也要来回一次。
对于任意cur,cur → \rightarrow parent 和parent → \rightarrow cur都只需要执行一次。
因为一次就可以收集所有金币。

题解

拓扑排序删除非金币节点后。

错误解法

DFS,返回cur距离叶子最远距离,如果>=2 ,则m_iRet+=2。注意:根节点不算,它没有父节点。DFS的时候排除已经被拓扑排序删除的节点。叶子节点到叶子节点的距离是0。果没有金币,直接返回0。错误原因:不同的根节点,结果不一样。

正确解法

删除叶子节点(无论是否有金币)两次。如果余下的节点为0,则返回0,否则返回( 剩余节点数-1) × \times × 2

代码

核心代码

class CTopSort
{
public:	template <class T = vector<int> >void Init(const vector<T>& vNeiBo,bool bDirect = true ){const int iDelOutDeg = bDirect ? 0 : 1;m_c = vNeiBo.size();m_vBackNeiBo.resize(m_c);vector<int> vOutDeg(m_c);for (int cur = 0; cur < m_c; cur++){vOutDeg[cur] = vNeiBo[cur].size();	for (const auto& next : vNeiBo[cur]){m_vBackNeiBo[next].emplace_back(cur);}}vector<bool> m_vHasDo(m_c);queue<int> que;for (int i = 0; i < m_c; i++){if ( vOutDeg[i] <= iDelOutDeg){m_vHasDo[i] = true;if (OnDo(i)) {que.emplace(i);}}}while (que.size()){const int cur = que.front();que.pop();for (const auto& next : m_vBackNeiBo[cur]){if (m_vHasDo[next] ){continue;}				vOutDeg[next]--;if (vOutDeg[next] <= iDelOutDeg ){m_vHasDo[next] = true;if (OnDo(next)) {que.emplace(next);}}}};}int m_c;
protected:virtual bool OnDo( int cur) = 0;vector<vector<int>> m_vBackNeiBo;	};

测试用例

template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{vector<int> coins;vector<vector<int>> edges;{Solution sln;coins = { 1,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0 }, edges = { {0,1},{0,2},{1,3},{2,4},{2,5},{3,6},{3,7},{4,8},{7,9},{8,10},{10,11},{11,12},{7,13},{8,14},{9,15},{11,16},{13,17} };auto res = sln.collectTheCoins(coins, edges);Assert(10, res);}{Solution sln;coins = { 1,0,0,1,1,0,0,0,0,1,0,0 }, edges = { {0,1},{1,2},{1,3},{2,4},{4,5},{5,6},{5,7},{4,8},{7,9},{7,10},{10,11} };auto res = sln.collectTheCoins(coins, edges);Assert(4, res);}{Solution sln;coins = { 1,0,0,0,0,1 }, edges = { {0,1},{1,2},{2,3},{3,4},{4,5} };auto res = sln.collectTheCoins(coins, edges);Assert(2, res);}{Solution sln;coins = { 0,0,0,1,1,0,0,1 }, edges = { {0,1},{0,2},{1,3},{1,4},{2,5},{5,6},{5,7} };auto res = sln.collectTheCoins(coins, edges);Assert(2, res);}	
}

2023年4月

class Solution {
public:
int collectTheCoins(vector& coins, vector<vector>& edges) {
m_c = coins.size();
vector vDeg(m_c);
vector<vector> vNeiB(m_c);
for (const auto& v : edges)
{
vNeiB[v[0]].emplace_back(v[1]);
vNeiB[v[1]].emplace_back(v[0]);
vDeg[v[0]]++;
vDeg[v[1]]++;
}
std::queue que;
for (int i = 0; i < m_c; i++)
{
if ((0 == coins[i]) && (1 == vDeg[i]))
{
que.emplace(i);
}
}
//根据Top排序,依次删除没有金币的节点,自己和子节点没有金币的节点, 自己和子孙节点没金币的节点…
while (que.size())
{
int iCur = que.front();
que.pop();
for (const auto& next : vNeiB[iCur])
{
vDeg[next]–;
if ((0 == coins[next]) && (1 == vDeg[next]))
{
que.emplace(next);
}
}
vNeiB[iCur].clear();
}
//通过top排序生成平衡树,Dis为0,表示页节点;1表示离最近的叶节点为1
//访问所有完所有Dis为2的节点,可以收集所有金币
//每个叶子只有一个父节点,自然只有一个祖父节点。必须访问所有Dis为2的节点,否则它子孙的金币无法收集
//
//叶节点入队
vector vDis(m_c, 0);
for (int i = 0; i < m_c; i++)
{
if (1 == vDeg[i])
{
que.emplace(i);
}
}
if (que.size() <=1)
{
return 0;
}
while (que.size())
{
const int iCur = que.front();
que.pop();
for (const auto& next : vNeiB[iCur])
{
vDeg[next]–;
if (1 == vDeg[next])
{
que.emplace(next);
vDis[next] = vDis[iCur] + 1;
}
}
vNeiB[iCur].clear();
}
//
int iRet = -2;
for (int i = 0; i < m_c; i++)
{
if (vDis[i] >= 2)
{
iRet += 2;
}
}
return max(0,iRet);
}
int m_c;
};

2023年8月

class Solution {
public:
int collectTheCoins(vector& coins, vector<vector>& edges) {
m_c = coins.size();

	CNeiBo2 neiBo(m_c, edges, false);vector<int> vDeg(m_c);for (const auto& v : edges){vDeg[v[0]]++;vDeg[v[1]]++;}std::queue<int> que,queCoins;//非金币叶子 金币叶子vector<int> vDel(m_c);for (int i = 0; i < m_c; i++){if (1 == vDeg[i]){if (0 == coins[i]){que.emplace(i);					}else{queCoins.emplace(i);}}}while (que.size()){const int cur = que.front();que.pop();vDel[cur] = true;for (const auto& next : neiBo.m_vNeiB[cur]){vDeg[next]--;if (1 == vDeg[next]){if (0 == coins[next]){que.emplace(next);}else{queCoins.emplace(next);}}}}queue<int> queParent;while (queCoins.size()){const int cur = queCoins.front();queCoins.pop();vDel[cur] = true;for (const auto& next : neiBo.m_vNeiB[cur]){vDeg[next]--;if (1 == vDeg[next]){queParent.emplace(next);vDel[next] = true;}}}int iRet = 0;for (const auto& v : edges){if (vDel[v[0]] || vDel[v[1]]){continue;}iRet += 2;}return iRet;
}
int m_c;

};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/589042.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并切片,计算文件MD5,上传进度显示,秒传】等功能

Vue 大文件切片上传实现指南 背景 在Web开发中&#xff0c;文件上传是一个常见的功能需求&#xff0c;尤其是当涉及到大文件上传时&#xff0c;为了提高上传的稳定性和效率&#xff0c;文件切片上传技术便显得尤为重要。通过将大文件切分成多个小块&#xff08;切片&#xff0…

提效提速的快捷回复工具

在数字化交流日益增长的今天&#xff0c;客服工作显得尤为重要。为了提升对话质量和回复速度&#xff0c;同时减少重复劳动&#xff0c;我同事给我介绍了一款快捷回复工具&#xff0c;叫做客服宝聊天助手。我用了几天真心觉得好好用&#xff0c;今天特地分享这个软件给你们&…

【编译lombok问题】已解决:编译突然找不到符号问题-get/set找不到符号

一、场景&#xff1a;编译突然找不到符号 报错信息&#xff1a; 找不到符号 符号&#xff1a;方法getName() 二、原因&#xff1a; 没有使用lombok支持的编译器 三、解决方法&#xff1a; 打开File-Settings&#xff0c;按以下步骤进行设置&#xff1b; 修改&#xff1a;-Djp…

QuartusII联合Modelsim仿真中最好不要将tb文件设置为顶层,以避免compile错误

QuartusII联合Modelsim仿真中最好不要将tb文件设置为顶层&#xff0c;以避免compiler错误 1&#xff0c;QuartusII下的rtl文件、sim文件如下显示。2&#xff0c;将rtl文件中任一个置于顶层&#xff0c;都不会影响sim仿真输出。3&#xff0c;将tb.v文件置于顶层&#xff0c;comp…

windows无法使用hadoop报错:系统找不到路径

在windows下安装hadoop-3.1.4,进行环境变量配置后&#xff0c;打开window命令行窗口测试hadoop命令&#xff0c;报错&#xff0c;如图所示&#xff1a; 方案&#xff1a;由于JAVA_HOME路径有空格导致&#xff0c;可修改hadoop下\etc\hadoop\hadoop_env.cmd文档中set JAVA_HOME以…

VTK| VTK可视化流程+圆锥示例

要想入门vtk&#xff0c;了解vtk的可视化流程是非常有必要的。 VTK可视化流程 VTK可视化流程主要分为数据处理和渲染两个过程&#xff0c;有一张不错的可视化流程图把这个过程理解为一个舞台剧。 VTKVS运行圆锥示例 先来运行一个简单的示例代码来理解VTK运作的过程&#xff…

k8s 基础入门

1.namespace k8s中的namespace和docker中namespace是两码事&#xff0c;可以理解为k8s中的namespace是为了多租户&#xff0c;dockers中的namespace是为了网络、资源等隔离 2.deployment kubectl create #新建 kubectl aply #新建 更新 升级&#xff1a; 滚动升级&#x…

CSS水波纹效果

效果图&#xff1a; 1.创建一个div <div class"point1" click"handlePoint(1)"></div> 2.设置样式 .point1{width: 1rem;height: 1rem;background: #2ce92f;position: absolute;border-radius: 50%;z-index: 999;cursor: pointer;} 3.设置伪…

C++初学者:优雅创建第一个窗口

我想学习C做一些实用的程序&#xff0c;但是我不想在软件界面上花太多的时间&#xff0c;可是每每就是界面影响我的思绪。 今天学习C类的包装知识&#xff0c;终于整出了一个我的界面类&#xff0c;虽然封装水平很弱&#xff0c; 这次就用这个类&#xff0c;写了自己工作上常用…

什么是视频号小店?视频号小店怎么开通,为什么那么多人都在做?

大家好&#xff0c;我是电商花花。 要说现在最火的行业/项目是什么&#xff1f;相信每个人都下意识觉得是直播电商。 没错&#xff0c;自动直播电商火了之后&#xff0c;更是带动着抖音小店走向了新的高潮&#xff0c;也带动着其它电商平台的发展&#xff0c;不断涌出新的平台…

计算机组成结构1

概念 计算机组成&#xff1a; 运算器、控制器、存储器、输出设备、输入设备 CPU&#xff1a;运算器控制器 运算器&#xff1a;算数逻辑单元ALU、累加寄存器AC、数据缓冲寄存器DR、状态条件寄存器PSW 控制器&#xff1a;指令寄存器IR、程序计数器PC、地址寄存器AR、指令译码器I…

stm32 HAL中断GPIO——1

1选择引脚为中断 中断详细配置 1 模式选择 上拉下拉 再点击NVIC可进行分组 再勾选如图 总结步骤 1选择中断 2配置时钟//选择外部时钟 3配置模式 4勾选NVIC