每日一題: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.pswp.cn/news/383578.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383578.shtml
英文地址,請注明出處:http://en.pswp.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;對待難題不要畏難。 今天的主要問題是沒有看數據范圍&…

C++Primer學習筆記:第2章 變量和基本類型

空類型不對應具體的值&#xff0c;僅用于一些特殊的場合 long的長度為32位&#xff0c;float有&#xff17;個有效位&#xff0c;double有16個有效位 如果數值超過了int的范圍&#xff0c;應該用long long而不是long&#xff0c;long一般和int一樣大 在算術表達式中不要使用…

C++Primer學習筆記:第3章 字符串、向量和數組

可以使用using聲明而無需專門的前綴&#xff1a;using namespace::name;.。位于頭文件的代碼一般來說不應該使用using聲明&#xff0c;這是因為頭文件的內容會拷貝到所有引用他的文件中去&#xff0c;如果頭文件中有某個using聲明&#xff0c;那么每個使用了該頭文件的文件都會…

C++Primer學習筆記:第4章 表達式

表達式由一個或多個運算對象組成&#xff0c;對表達式求值將得到一個結果。字面值和變量是最簡單的表達式&#xff0c;其結果就是字面值和變量的值。把一個運算符和一個或多個運算對象組合起來可以生成較復雜的表達式。 重載運算符包括運算對象的類型和返回值的類型&#xff0…

C++Primer學習筆記:第5章 語句

一個表達式末尾加上分號就變成了表達式語句。最簡單的語句是空語句&#xff08;一個單獨的分號&#xff09;&#xff1a;語法上需要一條語句但是邏輯上不需要 復合語句是指用花括號括起來的&#xff08;可能為空&#xff09;語句和聲明的序列&#xff1a;用在語法上需要一條語…

z3 C++學習筆記

因為項目需要使用z3庫來解決問題&#xff0c;所以自己學習了一下&#xff0c;結果發現網上教程比較少&#xff0c;而且大部分都是使用Python&#xff0c;而我本人是C的忠實信徒&#xff0c;在知道C也可以使用z3庫以后我毫不猶豫地著手用C使用z3&#xff0c;但是我很快發現&…

C++Primer學習筆記:第6章 函數

通過調用運算符()調用函數 函數的調用完成兩項工作&#xff1a; 用實參初始化函數對應的形參將控制權轉移給被調用函數&#xff1a;主調函數的執行被暫時中斷&#xff0c;被調函數開始執行 盡管實參與形參存在對應關系&#xff0c;但是并沒有規定實參的求值順序。編譯器能以任…

C++Primer學習筆記:第8章 IO庫

C語言不直接處理輸入輸出&#xff0c;而是通過一族定義在標準庫中的類型來處理IO iostream定義了用于讀寫流的基本類型fstream定義了讀寫命名文件的類型sstream定義了讀寫內存string對象的類型 標準庫使得我們能夠忽略這些不同類型的流之間的差異&#xff0c;是通過繼承機制實…

C++Primer學習筆記:第7章 類

類的基本思想是數據抽象data abstraction和封裝encapsulation。數據抽象是一種依賴于接口interface和實現implementation分離的編程技術 在類中&#xff0c;由類的設計者負責考慮類的實現過程&#xff0c;使用該類的程序員只需要抽象地思考類型做了什么&#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(), …