并查集
图
数组
双指针
排序
题目描述
给你一个 n
个点组成的无向图边集 edgeList
,其中 edgeList[i] = [ui , vi , disi ]
表示点 ui
和点 vi
之间有一条长度为 disi
的边。请注意,两个点之间可能有 超过一条边 。
给你一个查询数组queries
,其中 queries[j] = [pj , qj , limitj ]
,你的任务是对于每个查询 queries[j]
,判断是否存在从 pj
到 qj
的路径,且这条路径上的每一条边都 严格小于 limitj
。
请你返回一个 布尔数组 answer
,其中 answer.length == queries.length
,当 queries[j]
的查询结果为 true
时, answer
第 j
个值为 true
,否则为 false
。
示例 1:
输入: n = 3, edgeList = [[0,1,2],[1,2,4],[2,0,8],[1,0,16]], queries = [[0,1,2],[0,2,5]]
输出: [false,true]
解释: 上图为给定的输入数据。注意到 0 和 1 之间有两条重边,分别为 2 和 16 。
对于第一个查询,0 和 1 之间没有小于 2 的边,所以我们返回 false 。
对于第二个查询,有一条路径(0 -> 1 -> 2)两条边都小于 5 ,所以这个查询我们返回 true 。
示例 2:
输入: n = 5, edgeList = [[0,1,10],[1,2,5],[2,3,9],[3,4,13]], queries = [[0,4,14],[1,4,13]]
输出: [true,false]
解释: 上图为给定数据。
提示:
2 <= n <= 105
1 <= edgeList.length, queries.length <= 105
edgeList[i].length == 3
queries[j].length == 3
0 <= ui , vi , pj , qj <= n - 1
ui != vi
pj != qj
1 <= disi , limitj <= 109
两个点之间可能有 多条 边。
解法
方法一:离线查询 + 并查集
根据题目要求,我们需要对每个查询 $queries[i]$ 进行判断,即判断当前查询的两个点 $a$ 和 $b$ 之间是否存在一条边权小于等于 $limit$ 的路径。
判断两点是否连通可以通过并查集来实现。另外,由于查询的顺序对结果没有影响,因此我们可以先将所有查询按照 $limit$ 从小到大排序,所有边也按照边权从小到大排序。
然后对于每个查询,我们从边权最小的边开始,将边权严格小于 $limit$ 的所有边加入并查集,接着利用并查集的查询操作判断两点是否连通即可。
时间复杂度 $O(m \times \log m + q \times \log q)$,其中 $m$ 和 $q$ 分别为边数和查询数。
Python3 Java C++ Go Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 class Solution :
def distanceLimitedPathsExist (
self , n : int , edgeList : List [ List [ int ]], queries : List [ List [ int ]]
) -> List [ bool ]:
def find ( x ):
if p [ x ] != x :
p [ x ] = find ( p [ x ])
return p [ x ]
p = list ( range ( n ))
edgeList . sort ( key = lambda x : x [ 2 ])
j = 0
ans = [ False ] * len ( queries )
for i , ( a , b , limit ) in sorted ( enumerate ( queries ), key = lambda x : x [ 1 ][ 2 ]):
while j < len ( edgeList ) and edgeList [ j ][ 2 ] < limit :
u , v , _ = edgeList [ j ]
p [ find ( u )] = find ( v )
j += 1
ans [ i ] = find ( a ) == find ( b )
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 class Solution {
private int [] p ;
public boolean [] distanceLimitedPathsExist ( int n , int [][] edgeList , int [][] queries ) {
p = new int [ n ] ;
for ( int i = 0 ; i < n ; ++ i ) {
p [ i ] = i ;
}
Arrays . sort ( edgeList , ( a , b ) -> a [ 2 ] - b [ 2 ] );
int m = queries . length ;
boolean [] ans = new boolean [ m ] ;
Integer [] qid = new Integer [ m ] ;
for ( int i = 0 ; i < m ; ++ i ) {
qid [ i ] = i ;
}
Arrays . sort ( qid , ( i , j ) -> queries [ i ][ 2 ] - queries [ j ][ 2 ] );
int j = 0 ;
for ( int i : qid ) {
int a = queries [ i ][ 0 ] , b = queries [ i ][ 1 ] , limit = queries [ i ][ 2 ] ;
while ( j < edgeList . length && edgeList [ j ][ 2 ] < limit ) {
int u = edgeList [ j ][ 0 ] , v = edgeList [ j ][ 1 ] ;
p [ find ( u ) ] = find ( v );
++ j ;
}
ans [ i ] = find ( a ) == find ( b );
}
return ans ;
}
private int find ( int x ) {
if ( p [ x ] != x ) {
p [ x ] = find ( p [ x ] );
}
return p [ x ] ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 class Solution {
public :
vector < bool > distanceLimitedPathsExist ( int n , vector < vector < int >>& edgeList , vector < vector < int >>& queries ) {
vector < int > p ( n );
iota ( p . begin (), p . end (), 0 );
sort ( edgeList . begin (), edgeList . end (), []( auto & a , auto & b ) { return a [ 2 ] < b [ 2 ]; });
function < int ( int ) > find = [ & ]( int x ) -> int {
if ( p [ x ] != x ) p [ x ] = find ( p [ x ]);
return p [ x ];
};
int m = queries . size ();
vector < bool > ans ( m );
vector < int > qid ( m );
iota ( qid . begin (), qid . end (), 0 );
sort ( qid . begin (), qid . end (), [ & ]( int i , int j ) { return queries [ i ][ 2 ] < queries [ j ][ 2 ]; });
int j = 0 ;
for ( int i : qid ) {
int a = queries [ i ][ 0 ], b = queries [ i ][ 1 ], limit = queries [ i ][ 2 ];
while ( j < edgeList . size () && edgeList [ j ][ 2 ] < limit ) {
int u = edgeList [ j ][ 0 ], v = edgeList [ j ][ 1 ];
p [ find ( u )] = find ( v );
++ j ;
}
ans [ i ] = find ( a ) == find ( b );
}
return ans ;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 func distanceLimitedPathsExist ( n int , edgeList [][] int , queries [][] int ) [] bool {
p := make ([] int , n )
for i := range p {
p [ i ] = i
}
sort . Slice ( edgeList , func ( i , j int ) bool { return edgeList [ i ][ 2 ] < edgeList [ j ][ 2 ] })
var find func ( int ) int
find = func ( x int ) int {
if p [ x ] != x {
p [ x ] = find ( p [ x ])
}
return p [ x ]
}
m := len ( queries )
qid := make ([] int , m )
ans := make ([] bool , m )
for i := range qid {
qid [ i ] = i
}
sort . Slice ( qid , func ( i , j int ) bool { return queries [ qid [ i ]][ 2 ] < queries [ qid [ j ]][ 2 ] })
j := 0
for _ , i := range qid {
a , b , limit := queries [ i ][ 0 ], queries [ i ][ 1 ], queries [ i ][ 2 ]
for j < len ( edgeList ) && edgeList [ j ][ 2 ] < limit {
u , v := edgeList [ j ][ 0 ], edgeList [ j ][ 1 ]
p [ find ( u )] = find ( v )
j ++
}
ans [ i ] = find ( a ) == find ( b )
}
return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 impl Solution {
#[allow(dead_code)]
pub fn distance_limited_paths_exist (
n : i32 ,
edge_list : Vec < Vec < i32 >> ,
queries : Vec < Vec < i32 >> ,
) -> Vec < bool > {
let mut disjoint_set : Vec < usize > = vec! [ 0 ; n as usize ];
let mut ans_vec : Vec < bool > = vec! [ false ; queries . len ()];
let mut q_vec : Vec < usize > = vec! [ 0 ; queries . len ()];
// Initialize the set
for i in 0 .. n {
disjoint_set [ i as usize ] = i as usize ;
}
// Initialize the q_vec
for i in 0 .. queries . len () {
q_vec [ i ] = i ;
}
// Sort the q_vec based on the query limit, from the lowest to highest
q_vec . sort_by ( | i , j | queries [ * i ][ 2 ]. cmp ( & queries [ * j ][ 2 ]));
// Sort the edge_list based on the edge weight, from the lowest to highest
let mut edge_list = edge_list . clone ();
edge_list . sort_by ( | i , j | i [ 2 ]. cmp ( & j [ 2 ]));
let mut edge_idx : usize = 0 ;
for q_idx in & q_vec {
let s = queries [ * q_idx ][ 0 ] as usize ;
let d = queries [ * q_idx ][ 1 ] as usize ;
let limit = queries [ * q_idx ][ 2 ];
// Construct the disjoint set
while edge_idx < edge_list . len () && edge_list [ edge_idx ][ 2 ] < limit {
Solution :: union (
edge_list [ edge_idx ][ 0 ] as usize ,
edge_list [ edge_idx ][ 1 ] as usize ,
& mut disjoint_set ,
);
edge_idx += 1 ;
}
// If the parents of s & d are the same, this query should be `true`
// Otherwise, the current query is `false`
ans_vec [ * q_idx ] = Solution :: check_valid ( s , d , & mut disjoint_set );
}
ans_vec
}
#[allow(dead_code)]
pub fn find ( x : usize , d_set : & mut Vec < usize > ) -> usize {
if d_set [ x ] != x {
d_set [ x ] = Solution :: find ( d_set [ x ], d_set );
}
return d_set [ x ];
}
#[allow(dead_code)]
pub fn union ( s : usize , d : usize , d_set : & mut Vec < usize > ) {
let p_s = Solution :: find ( s , d_set );
let p_d = Solution :: find ( d , d_set );
d_set [ p_s ] = p_d ;
}
#[allow(dead_code)]
pub fn check_valid ( s : usize , d : usize , d_set : & mut Vec < usize > ) -> bool {
let p_s = Solution :: find ( s , d_set );
let p_d = Solution :: find ( d , d_set );
p_s == p_d
}
}
附并查集相关介绍以及常用模板:
并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的合并 及查询 问题。 它支持两种操作:
查找(Find):确定某个元素处于哪个子集,单次操作时间复杂度 $O(\alpha(n))$
合并(Union):将两个子集合并成一个集合,单次操作时间复杂度 $O(\alpha(n))$
其中 $\alpha$ 为阿克曼函数的反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。
以下是并查集的常用模板,需要熟练掌握。其中:
n
表示节点数
p
存储每个点的父节点,初始时每个点的父节点都是自己
size
只有当节点是祖宗节点时才有意义,表示祖宗节点所在集合中,点的数量
find(x)
函数用于查找 $x$ 所在集合的祖宗节点
union(a, b)
函数用于合并 $a$ 和 $b$ 所在的集合
Python3 Java C++ Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 p = list ( range ( n ))
size = [ 1 ] * n
def find ( x ):
if p [ x ] != x :
# 路径压缩
p [ x ] = find ( p [ x ])
return p [ x ]
def union ( a , b ):
pa , pb = find ( a ), find ( b )
if pa == pb :
return
p [ pa ] = pb
size [ pb ] += size [ pa ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 int [] p = new int [ n ] ;
int [] size = new int [ n ] ;
for ( int i = 0 ; i < n ; ++ i ) {
p [ i ] = i ;
size [ i ] = 1 ;
}
int find ( int x ) {
if ( p [ x ] != x ) {
// 路径压缩
p [ x ] = find ( p [ x ] );
}
return p [ x ] ;
}
void union ( int a , int b ) {
int pa = find ( a ), pb = find ( b );
if ( pa == pb ) {
return ;
}
p [ pa ] = pb ;
size [ pb ] += size [ pa ] ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 vector < int > p ( n );
iota ( p . begin (), p . end (), 0 );
vector < int > size ( n , 1 );
int find ( int x ) {
if ( p [ x ] != x ) {
// 路径压缩
p [ x ] = find ( p [ x ]);
}
return p [ x ];
}
void unite ( int a , int b ) {
int pa = find ( a ), pb = find ( b );
if ( pa == pb ) return ;
p [ pa ] = pb ;
size [ pb ] += size [ pa ];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 p := make ([] int , n )
size := make ([] int , n )
for i := range p {
p [ i ] = i
size [ i ] = 1
}
func find ( x int ) int {
if p [ x ] != x {
// 路径压缩
p [ x ] = find ( p [ x ])
}
return p [ x ]
}
func union ( a , b int ) {
pa , pb := find ( a ), find ( b )
if pa == pb {
return
}
p [ pa ] = pb
size [ pb ] += size [ pa ]
}