目錄
- 任務
- 684. 冗餘連線
- 思路
- 685. 冗餘連線 II
- 思路
- 684. 冗餘連線
任務
684. 冗餘連線
樹可以看成是一個連通且 無環 的 無向 圖。
給定往一棵 n 個節點 (節點值 1~n) 的樹中新增一條邊後的圖。新增的邊的兩個頂點包含在 1 到 n 中間,且這條附加的邊不屬於樹中已存在的邊。圖的資訊記錄於長度為 n 的二維陣列 edges ,edges[i] = [ai, bi] 表示圖中在 ai 和 bi 之間存在一條邊。
請找出一條可以刪去的邊,刪除後可使得剩餘部分是一個有著 n 個節點的樹。如果有多個答案,則返回陣列 edges 中最後出現的那個。
思路
這題的題意感覺很晦澀,目前理解就是遍歷邊將一個一個點加入到並查集中,此時如果發現有已經加入過的兩個同一集合的點,說明形成了環,返回這個最後的邊就是冗餘邊(使得樹變成圖的邊),因為冗餘邊只有一條,按照題目描述全部連線後,這個圖的點數==邊數,按順序找到冗餘邊刪除即可。
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
unionFind = UnionFind(len(edges))
for i in range(len(edges)):
if unionFind.is_same(edges[i][0],edges[i][1]):
return edges[i]
else:
unionFind.join(edges[i][0],edges[i][1])
class UnionFind:
def __init__(self,n):
self.father = [0] * (n+1)
for i in range(1,n+1):
self.father[i] = i
# (查)
def find(self,u):
if u == self.father[u]:
return u
else:
self.father[u] = self.find(self.father[u]) # 路徑壓縮(一直像上層返回最底層的值,並且一直賦值)
return self.father[u]
# 判斷 u 和 v 是否找到同一個根
def is_same(self,u, v):
u = self.find(u)
v = self.find(v)
return u == v
# 將 v->u 這條邊加入並查集 (並)
def join(self,u, v):
u = self.find(u) # 尋找 u 的根
v = self.find(v) # 尋找 v 的根
if u == v:
return # 如果發現根相同,說明在一個集合,不用兩個節點相連,直接返回
self.father[v] = u #否則其中一根歸於另一根
685. 冗餘連線 II
在本問題中,有根樹指滿足以下條件的 有向 圖。該樹只有一個根節點,所有其他節點都是該根節點的後繼。該樹除了根節點之外的每一個節點都有且只有一個父節點,而根節點沒有父節點。
輸入一個有向圖,該圖由一個有著 n 個節點(節點值不重複,從 1 到 n)的樹及一條附加的有向邊構成。附加的邊包含在 1 到 n 中的兩個不同頂點間,這條附加的邊不屬於樹中已存在的邊。
結果圖是一個以邊組成的二維陣列 edges 。 每個元素是一對 [ui, vi],用以表示 有向 圖中連線頂點 ui 和頂點 vi 的邊,其中 ui 是 vi 的一個父節點。
返回一條能刪除的邊,使得剩下的圖是有 n 個節點的有根樹。若有多個答案,返回最後出現在給定二維陣列的答案。
思路
從無向圖變成了有向圖,就不是像上題那樣直接任意刪同一集合重複加入的節點構成的邊了,這裡分兩種情況。難點在想到情況2
- 如果沒有入度為2的點,實際上和無向圖是一樣的,就是將最後讓這個圖形成環拆開即可,和無向圖是一模一樣的
- 如果存在入度為2的點,則將以這些點為終點的邊收集起來,根據,實際只需要收集最後兩條邊,那麼,結果就在這兩條邊裡,為什麼不是最後一條邊呢,因為有可能刪除最後一條邊後並沒有形成樹(比如同時存在環和入度為2的節點時,必須刪除這兩條邊中構成環的那條邊),所以需要還判斷刪除後是否是一顆樹,這個用並查集也很好判斷。
class Solution:
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
# 確定所有點的入度,點的值的範圍為[1,n]
inDegree = [0] * (len(edges) + 1)
for i in range(len(edges)):
inDegree[edges[i][1]] += 1
inDegree2List = [] #所有和入度為2的點相連的邊
#倒序找到所有入度為2涉及到的邊,如果存在入度為2的點,則刪除的邊必在最後兩條中找到
for i in range(len(edges)-1,-1,-1):
if inDegree[edges[i][1]] == 2:
inDegree2List.append(edges[i])
if len(inDegree2List) == 2:
break
if len(inDegree2List) > 0:
if self.checkIsTree(edges,inDegree2List[0]):
return inDegree2List[0]
else:
return inDegree2List[1]
unionFind = UnionFind(len(edges))
for i in range(len(edges)):
if unionFind.is_same(edges[i][0],edges[i][1]):
return edges[i]
else:
unionFind.join(edges[i][0],edges[i][1])
def checkIsTree(self,edges,delEdge): #確定刪除一條邊後是否是一棵樹
unionFind = UnionFind(len(edges))
for i in range(len(edges)):
if edges[i] == delEdge:
continue #繼續判斷下一個邊,或並查集新增節點,或發現已經同一集合的節點直接返回False,這裡直接去判斷下一個相當於忽略或者說刪除了當前邊
if not unionFind.is_same(edges[i][0],edges[i][1]):
unionFind.join(edges[i][0],edges[i][1])
else:
return False
return True
class UnionFind:
def __init__(self,n):
self.father = [0] * (n+1)
for i in range(1,n+1):
self.father[i] = i
# (查)
def find(self,u):
if u == self.father[u]:
return u
else:
self.father[u] = self.find(self.father[u]) # 路徑壓縮(一直像上層返回最底層的值,並且一直賦值)
return self.father[u]
# 判斷 u 和 v 是否找到同一個根
def is_same(self,u, v):
u = self.find(u)
v = self.find(v)
return u == v
# 將 v->u 這條邊加入並查集 (並)
def join(self,u, v):
u = self.find(u) # 尋找 u 的根
v = self.find(v) # 尋找 v 的根
if u == v:
return # 如果發現根相同,說明在一個集合,不用兩個節點相連,直接返回
self.father[v] = u #否則其中一根歸於另一根