每日一题:leetcode1489. 找到最小生成树里的关键边和伪关键边

时隔多年我终于又开始写博客了,主要是已经放假了,之前一直忙于考试和课设没有时间写博客,学习笔记也因为买了iPad的缘故大部分都是手写的了。

假期想要把以前做过的项目都整理一下放在githubCSDN上。

也已经很久没有写算法题了,直接导致今天这道题虽然我看了题解但是自己还是写了好久。

题目描述

传送门
在这里插入图片描述

题目解析

题解有两种解法
第一种解法比较朴素,就是按照关键边和伪关键边的定义。
关键边:在所有MST中都会出现的边
关键边性质:删除以后只能得到一个边权和更大的MST(或者无法得到MST)
伪关键边:会出现在一些MST中但是不会出现在所有MST中的边

因此,我们对每条边先判断是不是关键边,如果不是再判断是否是伪关键边。
判断关键边的思路很清晰,就是删去这条边再判断是否还能得到和之前边权和相同的MST。

但是判断伪关键边就有一些技巧了:我们很难得到所有的最小生成树,对于一条边我们如何判断这条边在不在MST中呢,题解的做法是最先将这条边加入到MST中,然后再对剩下的求解MST,如果最后MST和之前的权值和相同则说明这条边在MST中。

我和题解不同的做法在于(我认为是一点小优化):

  • 刚开始需要求一次MST,求关键边的时候只枚举这个MST中的边(其他的边不可能在伪关键边中)
  • 使用kind数组记录每条边的属性,在求完所有的关键边以后再求伪关键边,如果某条边已经在一个MST中则直接加入伪关键边(因为他不是关键边,满足伪关键边的定义)

第二种我直接没有看,因为Tarjan算法我已经忘光了,而且这道题好像还用到了kraskal算法的一个性质(并不知道

在Kruskal 算法中,对于任意的实数 w,只要我们将给定的边按照权值从小到大进行排序,那么当我们按照顺序处理完所有权值小于等于 w 的边之后,对应的并查集的连通性是唯一确定的,无论我们在排序时如何规定权值相同的边的顺序。

感觉太难了,不想看了。

AC代码

class Solution {
public:static constexpr int MAXN = 105;int father[MAXN];int kind[MAXN*MAXN];int m;  //边数int value = 0;int root(int x) {return x == father[x] ? x : (father[x] = root(father[x]));}void merge(int u, int v) {father[root(u)] = root(v);}vector<int> critical_edges;vector<int> pseudo_critical_edges;/*** 求已经删去第del条边的图的最小生成树* 并差集的状态为father* cnt用来记录当前该最小生成树中有多少条边* ret用来记录当前最小生成树的权值和*/int kruskal(const int n, const vector<vector<int>> &edges, int del, int cnt, int ret) {for (int i = 0; i < m; ++i) {if (i == del) {//如果是已经删除的边,则跳过continue;}int u = edges[i][0];int v = edges[i][1];if (root(u) != root(v)) {merge(u, v);ret += edges[i][2];++cnt;if (kind[i] == -1 && del == -1)kind[i] = 0;    //表示该边是某个最小生成树的一条边}}if (cnt == n-1) {//说明形成了最小生成树return ret;} else {//说明原本不是一个连通分量return value + 122;}}static bool compare(const vector<int>& a, const vector<int>& b) {return a[2] < b[2];}vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {memset(kind, -1, sizeof(kind));m = edges.size();for (int i = 0; i < m; ++i) {edges[i].push_back(i);}sort(edges.begin(), edges.end(), compare);for (int i = 0; i < n; ++i) {//并查集的初始化father[i] = i;}value = kruskal(n, edges, -1, 0, 0);//寻找关键边for (int i = 0; i < m; ++i) {if (kind[i] == -1) {//不是生成树中的边continue;}for (int i = 0; i < n; ++i) {//并查集的初始化father[i] = i;}int v = kruskal(n, edges, i, 0, 0);if (v > value) {//说明是关键边kind[i] = 1;critical_edges.push_back(edges[i][3]);}}//寻找伪关键边for (int i = 0; i < m; ++i) {if (kind[i] == 1) continue; //关键边不可能是伪关键边if (kind[i] == 0) {//如果在某个生成树中还不是关键边则一定是伪关键边pseudo_critical_edges.push_back(edges[i][3]);continue;}//对于普通边,首先将其加入到生成树中,然后再判断for (int i = 0; i < n; ++i) {//并查集的初始化father[i] = i;}merge(edges[i][0], edges[i][1]);int v = kruskal(n, edges, -1, 1, edges[i][2]);if (v == value) {//说明加入这条边以后仍然能够得到最小生成树,是伪关键边pseudo_critical_edges.push_back(edges[i][3]);}}return {critical_edges, pseudo_critical_edges};}
};

官方题解代码

// 并查集模板
class UnionFind {
public:vector<int> parent;vector<int> size;int n;// 当前连通分量数目int setCount;public:UnionFind(int _n): n(_n), setCount(_n), parent(_n), size(_n, 1) {iota(parent.begin(), parent.end(), 0);}int findset(int x) {return parent[x] == x ? x : parent[x] = findset(parent[x]);}bool unite(int x, int y) {x = findset(x);y = findset(y);if (x == y) {return false;}if (size[x] < size[y]) {swap(x, y);}parent[y] = x;size[x] += size[y];--setCount;return true;}bool connected(int x, int y) {x = findset(x);y = findset(y);return x == y;}
};class Solution {
public:vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {int m = edges.size();for (int i = 0; i < m; ++i) {edges[i].push_back(i);}sort(edges.begin(), edges.end(), [](const auto& u, const auto& v) {return u[2] < v[2];});// 计算 valueUnionFind uf_std(n);int value = 0;for (int i = 0; i < m; ++i) {if (uf_std.unite(edges[i][0], edges[i][1])) {value += edges[i][2];}}vector<vector<int>> ans(2);for (int i = 0; i < m; ++i) {// 判断是否是关键边UnionFind uf(n);int v = 0;for (int j = 0; j < m; ++j) {if (i != j && uf.unite(edges[j][0], edges[j][1])) {v += edges[j][2];}}if (uf.setCount != 1 || (uf.setCount == 1 && v > value)) {ans[0].push_back(edges[i][3]);continue;}// 判断是否是伪关键边uf = UnionFind(n);uf.unite(edges[i][0], edges[i][1]);v = edges[i][2];for (int j = 0; j < m; ++j) {if (i != j && uf.unite(edges[j][0], edges[j][1])) {v += edges[j][2];}}if (v == value) {ans[1].push_back(edges[i][3]);}}return ans;}
};//作者:LeetCode-Solution
//链接:https://leetcode-cn.com/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/solution/zhao-dao-zui-xiao-sheng-cheng-shu-li-de-gu57q/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

仔细研究官方题解的代码感觉收益颇多:

  • 使用iota(begin, end, init)对数组进行初始化,其中init为初始值,需要能够和++运算符结合
  • 使用功能完善的并差集模板(我自己每次都是手写,然后写地支离破碎)
  • 使用lamda表达式进行函数定义

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

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

相关文章

每日一题:leetcode989.数组形式的整数加法

题目描述 题目分析 题目非常简单&#xff0c;但是我还是wa了几发&#xff0c;对不起&#xff0c;我太菜了。我的想法是把K转换为数组然后用大整数加法处理。但是因为太久没有写了导致写了好久。 class Solution { public:void add(vector<int> &A, vector<int&g…

每日一题:leetcode674.最长连续递增序列

题目描述 题目分析 一遍遍历&#xff0c;如果硬要说用了什么算法的话觉得应该算是一个简单的滑动窗口吧 AC代码 class Solution { public:int findLengthOfLCIS(vector<int>& nums) {if (nums.size() 0) {return 0;}int ret 1;int cnt 1;for (int i 1; i <…

每日一题:leetcode959.由斜杠划分区域

题目描述 题目分析 仔细分析这道题以后虽然觉得可能要转化为图之类的&#xff0c;但是完全没有具体的想法&#xff0c;因为每个格子都有三种情况&#xff0c;这三种情况的不同的组合又会产生不同的结果。 发现找不到编码转化为图以后&#xff0c;我分析了一下不同数量方块之间…

每日一题:leetcode1319.联通网络的操作次数

题目描述 题目分析 ps&#xff1a;这篇博客是补前天的&#xff0c;前天在老家不方便写博客 题目挺简单的&#xff0c;但是通过题目对图的连通性有了一个更深刻的认识&#xff1a;如果有超过&#xff08;或等于&#xff09;n-1条边&#xff0c;则一定是可以让整个图联通的。 如…

每日一题:leetcode1128.等价多米诺骨牌对数

题目描述 题目分析 看到题目以后第一个想法是遍历数组&#xff0c;对每个元素有一个数据结构中保存了该元素出现的次数&#xff0c;然后往结果中相加&#xff08;表示该元素和前面的对数&#xff09;&#xff0c;然后再将元素出现的次数加一。 思考用什么数据结构保存元素出现…

每日一题:leetcode1579.保证图可完全遍历

题目描述 题目分析 非常惭愧&#xff0c;感觉自己有点畏难心理&#xff0c;看到是困难题第一个想法是自己想不出来。。。 因为自己认为自己做不出来&#xff0c;所以完全不能进行思考&#xff0c;稍微思考一下就觉得不行不行。 我也想到了分别用两个并查集判断各自可以去掉多少…

每日一题:leetcode724.寻找数组的中心索引

题目描述 题目分析 今天这道题原本很简单&#xff0c;我都没打算写题解&#xff0c;当时用手机看的题目&#xff0c;我想着我三分钟应该能写出来&#xff0c;结果没想到wa了三发。。。 对待简单题不要轻视&#xff0c;对待难题不要畏难。 今天的主要问题是没有看数据范围&…

z3 C++学习笔记

因为项目需要使用z3库来解决问题&#xff0c;所以自己学习了一下&#xff0c;结果发现网上教程比较少&#xff0c;而且大部分都是使用Python&#xff0c;而我本人是C的忠实信徒&#xff0c;在知道C也可以使用z3库以后我毫不犹豫地着手用C使用z3&#xff0c;但是我很快发现&…

每日一题:leetcode191.位1的个数

题目描述 题目分析 很自然地想到了二进制枚举&#xff0c;直接循环检查每一个二进制位。 class Solution { public:int hammingWeight(uint32_t n) {int ret 0;uint32_t t 1;for (int i 0; i < 32; i, t << 1) {if (n & t) {ret;}}return ret;} };AC之后看了…

每日一题:leetcode341.扁平化嵌套列表迭代器

题目描述 题目分析 这个题目自己大概花了一个小时&#xff0c;虽然是一遍AC&#xff0c;但是速度有点慢&#xff0c;太长时间不写代码导致自己对代码不太敏感&#xff0c;写起来慢腾腾的。 看到这个的想法就是&#xff0c;要用栈来保存列表的迭代器&#xff0c;这样将孩子列表…

每日一题:leetcode82. 删除排序链表中的重复元素 II

题目描述 题目分析 这才是正常的中等题难度嘛&#xff0c;昨天的中等题题解我半天看不懂。。。 首先&#xff0c;需要增加一个哑节点&#xff08;操作链表的常规操作&#xff09;&#xff0c;因为有可能删除首节点&#xff0c;我们不想要为首节点添加单独的逻辑。其次&#xf…

每日一题:leetcode456.132模式

题目描述 题目分析 我觉得这道题应该是我做过最难的中等题之一了&#xff0c;这是昨天的每日一题&#xff0c;但是昨天用nlogn的做法做出来以后在看题解&#xff0c;发现有些看不懂&#xff08;觉得题解有点故弄玄虚&#xff09;。然后今天中午又花了一点时间才搞懂&#xff0…

leetcode283.移动零

题目描述 题目分析 在写简单题放松&#xff0c;看到这道题第一个想法是用STL库函数&#xff0c;虽然知道大概要用双指针之类的&#xff0c;但是库函数爽哇。 class Solution { public:void moveZeroes(vector<int>& nums) {stable_sort(nums.begin(), nums.end(), …

每日一题:leetcode61.旋转链表

题目描述 题目分析 很容易发现&#xff0c;如果k是n的整数倍&#xff0c;相当于没有移动。这样直接对k%n使得k在一个可以接受的范围。 因为是顺序移动&#xff0c;各元素之间的相对位置保持不变&#xff0c;所以就想着将链表先变成一个环。然后再移动头指针&#xff0c;最后再…

每日一题:leetcode173.二叉搜索树迭代器

题目描述 题目分析 更加地觉得编程重要的不在于如何写代码&#xff0c;用什么具体的技巧&#xff0c;编码本身只是一种将思维呈现的方式&#xff0c;但是如果思维是不清晰的&#xff0c;那么就算懂得再多的编码的奇技淫巧也是没有什么帮助的。相反&#xff0c;如果有一个清晰的…

leetcode11.盛最多水的容器

题目描述 题目分析 看到题目后第一个想法当然是O(n2)O(n^2)O(n2)的&#xff0c;但是数据范围是3e4&#xff0c;应该会超时&#xff0c;而且这种数据范围也不是让暴力求解的 。 相当于求解∑i<jmax((j−i)∗min(a[i],a[j]))\sum_{i<j}{max((j-i)*min(a[i],a[j]))}∑i<…

每日一题:leetcode190.颠倒二进制位

题目描述 题目分析 题目本身很简单&#xff0c;没觉得有什么技巧可以再进行优化了&#xff0c;觉得位运算是无法打乱相对顺序的&#xff0c;而这里需要进行镜像颠倒的操作。因此就踏实地写了一个循环。 在使用位运算得到每一位的时候&#xff0c;我吸取了经验&#xff0c;用一…

每日一题:leetcode74.搜索二维矩阵

题目描述 题目分析 感觉这是一个放错标签的简单题。题目非常简单&#xff0c;思路应该很明确是二分&#xff0c;我很快写了一个&#xff08;虽然不小心把!打成调试了一会&#xff09;。 class Solution { public:bool searchMatrix(vector<vector<int>>& mat…

每日一题:leetcode90.子集贰

题目描述 题目分析 感觉这道题让自己对枚举排列有了一个更好的认识&#xff0c;感觉自己的这种思路不错。 假设没有重复元素&#xff08;退化成78.子集&#xff09;&#xff0c;我们应该怎么做&#xff1f;初始的时候幂集中只有一个空集&#xff0c;然后对每个元素&#xff0…

每日一题:leetcode1006.笨阶乘

题目描述 题目分析 因为顺序一定且没有括号&#xff0c;所以逻辑很简单。我们要顺序处理的矛盾在于&#xff0c;减号后面会再出现乘法和除法&#xff0c;我们不妨将对乘法和除法用一个临时值进行计算&#xff0c;计算结束后再合并到值里面&#xff0c;一般来讲乘法和除法的处理…