1.コンセプト
ユニオンクエリセットは、いくつかの互いに素なセット(いわゆるユニオンとクエリ)のマージとクエリの問題を処理するために使用されるツリータイプのデータ構造です。たとえば、ユニオン検索を使用して、フォレスト内にあるツリーの数、ノードが特定のツリーに属しているかどうかなどを判別できます。
主な構成:
ユニオン検索セットは、主に整数配列pre []と2つの関数find()およびjoin()で構成されています。
配列pre []は、各ポイントの先行ノードが誰であるかを記録し(一般に、ポイントの父であると理解されます)、関数find(x)を使用して、指定されたノードxが属するセットを検索します(理解できます)。祖先を見つける場合)、関数join(x、y)を使用して、2つのノードxとyをマージします(通常、xをyの父と見なします)。
機能:
ユニオン検索セットの主な機能は、連結成分の数を見つけることです(グラフ内のすべての点が到達可能(直接または間接的に接続されている場合)の場合、グラフの連結成分の数は1です。グラフに2つのサブグラフすべてが到達可能である場合、このグラフの連結成分の数は2 ...)です。
例:
たとえば、大陸には天国、地球、玄、黄の4つの宗派があり、各宗派には異なるレベルの複数の弟子がいます。pre[]配列には各弟子の上司の名前が記録され、find()メソッドは次のようになります。弟子の上司を見つけるために上司のボス...は宗派の長であり、join(A、B)メソッドは弟子Aを弟子Bのボスとして設定することです。
次に、Pythonはコードのフルバージョンを実装します
フルバージョンのデータ構造には、主に理解を容易にするための初期化、検索、マージなどのメソッドがあります。コードの冗長性が増えると時間とスペースが複雑になるため、通常、フルバージョンは問題の解決には使用されません。
class UnionFind:
def __init__(self):
self.father = {
} ##记录每个节点的父节点
def find(self, x):
root = x
while self.father[root] != root: #寻找根节点
root = self.father[root]
while root != x: #路径压缩
preFather = self.father[x]
self.father[x] = root
x = preFather
return root
def merge(self, x, y): #合并节点
root_x, root_y = self.find(x), self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
def is_connected(self, x, y): #判断联通性
return self.find(x) == self.find(y)
def add(self, x): #增加节点
if x not in self.father:
self.father[x] = x
3. Pythonコードの圧縮バージョンを収集して確認します(問題を解決するために最も一般的に使用される方法)
問題を解決するとき、コードの単純さを保証し、時間とスペースの複雑さを減らすために、通常、1つの父配列と1つのfind()メソッドのみが定義されます。
注:findメソッドには、父親を継続的に更新する機能があり、通常は再帰的に実装されます。
dus ={
。。。。。。} # 先定义爸爸数组的形式及初始元素值
# 定义查找祖值方法(含更新祖值作用)
def find(i):
if dus[i] != i:
dus[i] = find(dus[i])
return dus[i]
4つ、そして典型的な例を集める
(1)Likou Question Bank:947、同じ列で最も削除されたピアまたは石
圧縮バージョンを適用し、テンプレートを確認します
n個の石は、2次元平面内のいくつかの整数座標点に配置されます。各座標点には最大で1つの石を配置できます。
同じ列または同じ列に他の石がある場合は、その石を取り除くことができます。
長さnの石の配列を指定します。ここで、stones [i] = [xi、yi]はi番目の石の位置を表し、削除できる石の最大数を返します。
例1:
入力:stones = [[0,0]、[0,1]、[1,0]、[1,2]、[2,1]、[2,2]]
出力:5
説明:削除メソッド5つの石の次のとおりです。
- [2,1]と同じなので、石[2,2]を取り除きます。
- [0,1]と同じ列にあるため、石[2,1]を削除します。
- [1,0]と一緒になるので、石[1,2]を取り除きます。
- [0,0]と同じ列にあるので、石[1,0]を削除します。
- [0,0]と同じなので、石[0,1]を削除します。
石[0,0]は、他の石と一列に並んでいないため、削除できません。
例2:
入力:石= [[0,0]、[0,2]、[1,1]、[2,0]、[2,2]]
出力:3
説明:3つの石を取り除く方法は次のとおりです。 :
- [2,0]と同じなので、石[2,2]を取り除きます。
- [0,0]と同じ列にあるため、石[2,0]を削除します。
- [0,0]と同じなので、石[0,2]を削除します。
石[0,0]と[1,1]は、他の石と一列に並んでいないため、削除できません。
例3:
入力:stones = [[0,0]]
出力:0
説明:[0,0]は平面上の唯一の石であるため、削除できません。
促す:
1 <= stones.length <= 1000
0 <= xi、yi <= 104
同じ座標点に2つの石が配置されることはありません
問題解決のアイデア:
トピックを読んだ後、最初の感覚は、接続された石のセットの数を見つけることです。
そして、接続された石の各グループは少なくとも1つの石を保持する必要があるため、答えは石の数、つまり接続された石のグループの数です。
公式の問題解決策を読んだ後、私はトリックを理解しました。すべての座標の縦座標に10000(座標の最大値)を追加します。座標はエッジと見なすことができます。
たとえば、[[0,0]、[1,1]、[1,2]] => [[0,10000]、[1,10001]、[1,10002]です。
ここで、0、10000、1、10001、10002は5つのノードを表します。
[0,10000]は、ノード0とノード10000がエッジで接続されていることを意味します。
[1,10001]は、ノード1とノード10001にエッジ接続があることを示します。
[1,10002]は、ノード1とノード10002にエッジ接続があることを示します。
これはおなじみのグラフに変わり、問題を見つけ、すべてのエッジをトラバースし、接続されているすべてのグループを見つけることができます。
時間計算量はO(nlogm)です。ここで、nはエッジの数、mはノードの数です。
スペースの複雑さはO(m)で、ここでは辞書を使用して実装と検索を行います。もちろん、リストもOKです。
さらに、最後にすべてのノードでfindを呼び出して、それらが属する接続グループを更新するように注意してください。
問題解決コード:
class Solution:
def removeStones(self, stones: List[List[int]]) -> int:
# 以字典形式定义初始化的parent集
dus ={
s+i*10000:s+i*10000 for stone in stones for i, s in enumerate(stone)}
print(dus)
# 定义查找祖值方法(含更新祖值作用)
def find(i):
if dus[i] != i:
dus[i] = find(dus[i])
return dus[i]
# 遍历全部石头,按顺序更新祖值
for i, (s1, s2) in enumerate(stones):
if s1 in dus and find(s1) != find(s2+10000):
# union
dus[find(s1)] = find(s2+10000)
print(i, dus)
# 防止按顺序更新的祖集中有重复未更新,再次更新
for k in dus:
print(k)
find(k)
# 最后用总数减去不重复的祖值数即可
return len(stones) - len(set(dus.values()))
(2)Likou Question Bank:1202、文字列内の要素を交換します
フルバージョンを適用し、テンプレートを確認します
文字列sと、文字列内のいくつかの「インデックスペア」配列ペアを指定します。ここで、pairs [i] = [a、b]は、文字列内の2つのインデックスを表します(番号付けは0から始まります)。
インデックスの任意のペアの文字を、ペアで何度でも交換できます。
sが数回の交換後になる可能性のある最小の文字列を辞書式に返します。
例1:
入力:s = "dcab"、ペア= [[0,3]、[1,2]]
出力: "bacd"
説明:
交換s [0]およびs [3]、s = "bcad"
交換s [1 ]そしてs [2]、s = "bacd"
例2:
入力:s = "dcab"、ペア= [[0,3]、[1,2]、[0,2]]
出力: "abcd"
説明:
s [0]とs [3]を交換します。s = " 「bcad」は
s [0]とs [2]を
交換し、s = "acbd"はs [1]とs [2]を交換します。s = "abcd"
例3:
入力:s = "cba"、ペア= [[0,1]、[1,2]]
出力: "abc"
説明:
s [0]とs [1]を
交換します。s= "bca"はs [1を交換します。 ]およびs [2]、s = "bac"
交換s [0]およびs [1]、s = "abc"
促す:
1 <= s.length <= 10 ^ 5
0 <= pairs.length <= 10 ^ 5
0 <= pair [i] [0]、pairs [i] [1] <s.lengths
には小文字の英語文字のみが含まれます
問題解決のアイデア:
主なことは、コレクションのアイデアを調査して確認することです。コレクションが使用された後、各コレクションに含まれるノードを個別に見つけ、それらを並べ替えて、再割り当てします。
問題解決のPythonコード(詳細なコメント):
# 定义一个并查集类,包含初始化祖先、查找祖先、合并祖先三种方法
class DSU:
def __init__(self, nodecount):
self.parent=[-1]*nodecount#初始化,每个节点的祖先都是自己,记住-1,这里node_count为节点总数
def findboss(self, node):# 首先,是找到自己所在集合的最上面那一层的祖先,若值不为-1,说明当前自己的祖先并不是最终祖先,循环进行再去找他的祖先,直到找到最终祖先
temp=node
while self.parent[node]!=-1:
node=self.parent[node]
if temp!=node:#路径压缩,使得所有节点的祖先都是最终的祖先
self.parent[temp]=node
return node
def mergeboss(self, node1, node2):#查询相互连通的两个人的祖先是不是同一个人
node1boss=self.findboss(node1)
node2boss=self.findboss(node2)
if node1boss!=node2boss:#如果不是,那就合并两个集合,从两人中选举一个新祖先
self.parent[node1boss]=node2boss
class Solution:
def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
n=len(s)
if n<2:
return s
dsu=DSU(n)#n个节点数,初始化并查集
for node1,node2 in pairs:#先用并查集遍历一遍,使得每个节点都找到自己的祖先
dsu.mergeboss(node1,node2)
h={
}
for i in range(n):#再将所有公共祖先的子节点划分到一起,公共祖先自己也在该集合里
if dsu.findboss(i) not in h:
h[dsu.findboss(i)]=[i]
else:
h[dsu.findboss(i)].append(i)
res=list(s)
#print(dsu.parent)
#print(h)
for nodes in h.values():
indices=sorted(nodes)#这里的每个节点都是相互连通的,即可以随意互相置换,直接按题意排序即可
string=sorted(res[node] for node in nodes)#按最小字典序排列即从小到大
# print(indices)
# print(string)
for index,letter in zip(indices,string):#按排好位置后,放回字母
res[index]=letter
return "".join(res)