检查边长度限制的路径是否存在【LC1679】
给你一个
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
。
An undirected graph of
n
nodes is defined byedgeList
, whereedgeList[i] = [ui, vi, disi]
denotes an edge between nodesui
andvi
with distancedisi
. Note that there may be multiple edges between two nodes.Given an array
queries
, wherequeries[j] = [pj, qj, limitj]
, your task is to determine for eachqueries[j]
whether there is a path betweenpj
andqj
such that each edge on the path has a distance strictly less thanlimitj
.Return a boolean array
answer
, whereanswer.length == queries.length
and thejth
value ofanswer
istrue
if there is a path forqueries[j]
istrue
, andfalse
otherwise.
同门阳了 我指希望不要太难受 这周把这类并查集搞定
BFS【超时】
-
思路:将输入的
edgeList
转换成邻接表adList
,维护一个小顶堆pq
可以暴力计算出查询数组queries
中各个起点到各个终点边长度最短的路径,从而判断是否存在办长度限制的路径pq
中的元素为节点编号以及起点到该节点的路径长度,并以路径长度为比较元素。每次取出未访问过的节点中的路径最短的节点,并访问其邻接点,若路径长度仍小于等于queries[2]
且未访问过,可将其放入pq
,直至pq
为空或 边大于queries[2]
。
-
实现
class Solution { public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) { boolean[] res = new boolean[queries.length]; // 构建邻接表 List<int[]>[] adList = new List[n]; for (int i = 0; i < n; i++){ adList[i] = new ArrayList<int[]>(); } for (int[] edge : edgeList){ int u = edge[0], v = edge[1], dis = edge[2]; adList[u].add(new int[]{v, dis}); adList[v].add(new int[]{u, dis}); } for (int i = 0; i < queries.length; i++){ int start = queries[i][0], end = queries[i][1], limit = queries[i][2]; PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]); pq.offer(new int[]{0, start}); Set<Integer> visited = new HashSet<>(); // 寻找路径 while(!pq.isEmpty() && pq.peek()[0] < limit){ int[] poll = pq.poll(); int dis = poll[0], u = poll[1]; if (!visited.add(u)){ continue; } if (u == end){ res[i] = true; break; } // 加入下一个可以访问的节点 for (int[] next : adList[u]){ int v = next[0], disNext = next[1]; if (!visited.contains(v)){ pq.offer(new int[]{disNext, v}); } } } } return res; } }
-
复杂度
- 时间复杂度:
O
(
E
×
l
o
g
V
×
m
)
O(E×logV×m)
O(E×logV×m),
V
V
V 为节点数,即
n
n
n,
E
E
E为输入
edgeList
的长度, m m m为queries
的长度。邻接表 adList的时间复杂度为 O ( E ) O(E) O(E),搜索路径的时间复杂度为 O ( E × l o g V ) O(E×logV) O(E×logV)。 - 空间复杂度:
O
(
V
+
E
)
O(V+E)
O(V+E),邻接表
adList
的空间复杂度为 O ( E ) O(E) O(E),pq
的空间复杂度为 O ( E ) O(E) O(E),visited
的空间复杂度为 O ( V ) O(V) O(V),总的空间复杂度为 O ( V + E ) O(V+E) O(V+E)。
- 时间复杂度:
O
(
E
×
l
o
g
V
×
m
)
O(E×logV×m)
O(E×logV×m),
V
V
V 为节点数,即
n
n
n,
E
E
E为输入
-
并查集
-
离线查询:对于一道题目会给出若干询问,而这些询问是全部提前给出的,也就是说,你不必按照询问的顺序依次对它们进行处理,而是可以按照某种顺序(例如全序、偏序(拓扑序)、树的 DFS 序等)或者把所有询问看成一个整体(例如整体二分、莫队算法等)进行处理。
-
思路:
- 使用并查集维护图的连通性,对于
queries[i]
,我们可以将edgeList
中所有长度小于limit
的边加入到并查集中,然后使用并查集查询u
和v
是否属于同一个集合。如果u
和v
属于同一个集合,则说明存在从u
到v
的路径,且这条路径上的每一条边的长度都严格小于limit
,查询返回true
,否则查询返回false
。 - 因此可以将
queries
按照limit
从小到大进行排序,并将edgeList
按照距离dis
从小到大进行排序,使用指针k
定位上一次查询中不满足limit
要求的长度最小的边,以便于使用离线查询的思维处理queries
中的询问,降低时间复杂度
- 使用并查集维护图的连通性,对于
-
实现
- 排序
- 依次遍历
queries
:如果k
指向的边的长度小于对应查询的limit
,则将该边加入并查集中,然后将k
加 1,直到k
指向的边不满足要求;最后根据并查集查询对应的u
和v
是否属于同一集合来保存查询的结果。
class Solution { private int[] father; public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) { // 记录初始下标 Integer[] index = new Integer[queries.length]; for (int i = 0; i < queries.length; i++){ index[i] = i; } // 排序 Arrays.sort(edgeList, (a, b) -> a[2] - b[2]); Arrays.sort(index, (a, b) -> queries[a][2] - queries[b][2]); // 并查集初始化 father = new int[n]; for (int i = 0; i < n; i++){ father[i] = i; } boolean[] res = new boolean[queries.length]; int k = 0; for (int i : index){ while (k < edgeList.length && edgeList[k][2] < queries[i][2]){ join(edgeList[k][0],edgeList[k][1]); k++; } res[i] = find(queries[i][0]) == find(queries[i][1]); } return res; } private int find(int u){ if (u == father[u]){ return u; } father[u] = find(father[u]); return father[u]; } private void join (int u, int v){ u = find(u); v = find(v); if (u == v) return; father[v] = u; } }
-
复杂度
- 时间复杂度:
O
(
E
l
o
g
E
+
m
l
o
g
m
+
(
E
+
m
)
l
o
g
n
+
n
)
O(ElogE+mlogm+(E+m)log n + n)
O(ElogE+mlogm+(E+m)logn+n),
n
n
n 为节点数,
E
E
E为输入
edgeList
的长度, m m m为queries
的长度。对edgeList
和queries
排序时间复杂度分别为 O ( E l o g E ) O(ElogE) O(ElogE)和 O ( m l o g m ) O(mlogm) O(mlogm),并查集初始化的时间复杂度为 O ( n ) O(n) O(n),并查集查询和合并的时间复杂度为 O ( ( E + m ) l o g n ) O((E+m)logn) O((E+m)logn)。 - 空间复杂度:
O
(
l
o
g
E
+
m
+
n
)
O(logE+m+n)
O(logE+m+n),保存并查集的空间复杂度为
O
(
n
)
O(n)
O(n),保存下标的空间复杂度为
O
(
m
)
O(m)
O(m),对
edgeList
排序的空间复杂度为 O ( l o g E ) O(logE) O(logE),对queries
排序的空间复杂度可以忽略
- 时间复杂度:
O
(
E
l
o
g
E
+
m
l
o
g
m
+
(
E
+
m
)
l
o
g
n
+
n
)
O(ElogE+mlogm+(E+m)log n + n)
O(ElogE+mlogm+(E+m)logn+n),
n
n
n 为节点数,
E
E
E为输入