{x}
blog image

Checking Existence of Edge Length Limited Paths

An undirected graph of n nodes is defined by edgeList, where edgeList[i] = [ui, vi, disi] denotes an edge between nodes ui and vi with distance disi. Note that there may be multiple edges between two nodes.

Given an array queries, where queries[j] = [pj, qj, limitj], your task is to determine for each queries[j] whether there is a path between pj and qj such that each edge on the path has a distance strictly less than limitj .

Return a boolean array answer, where answer.length == queries.length and the jth value of answer is true if there is a path for queries[j] is true, and false otherwise.

 

Example 1:

Input: n = 3, edgeList = [[0,1,2],[1,2,4],[2,0,8],[1,0,16]], queries = [[0,1,2],[0,2,5]]
Output: [false,true]
Explanation: The above figure shows the given graph. Note that there are two overlapping edges between 0 and 1 with distances 2 and 16.
For the first query, between 0 and 1 there is no path where each distance is less than 2, thus we return false for this query.
For the second query, there is a path (0 -> 1 -> 2) of two edges with distances less than 5, thus we return true for this query.

Example 2:

Input: n = 5, edgeList = [[0,1,10],[1,2,5],[2,3,9],[3,4,13]], queries = [[0,4,14],[1,4,13]]
Output: [true,false]
Explanation: The above figure shows the given graph.

 

Constraints:

  • 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
  • There may be multiple edges between two nodes.

Solution Explanation: Checking Existence of Edge Length Limited Paths

This problem asks to determine, for each query in a list, whether there exists a path between two given nodes in a graph such that all edges along that path have weights strictly less than a given limit. The efficient solution leverages offline processing and the Union-Find data structure.

Approach:

  1. Offline Processing: The queries are processed offline, meaning we don't need to answer each query immediately. Instead, we sort both the queries and the edges. We sort the queries by their limit in ascending order and the edges by their weight in ascending order. This allows us to efficiently determine connectivity as we process the queries.

  2. Union-Find: A Union-Find data structure is used to maintain the connectivity of the graph. As we iterate through the sorted edges, we add edges whose weights are less than the current query's limit to the Union-Find data structure. This is done by performing a union operation on the nodes connected by that edge.

  3. Querying Connectivity: For each query (p, q, limit), we iterate through the sorted edges until we find an edge with weight greater than or equal to limit. At this point, the Union-Find data structure represents the connectivity of the graph considering only edges with weights strictly less than limit. We then use the find operation of the Union-Find data structure to check if nodes p and q belong to the same set (connected component). If they are in the same set, the query is answered as true; otherwise, it's false.

Time Complexity Analysis:

  • Sorting the edges: O(M log M), where M is the number of edges.
  • Sorting the queries: O(Q log Q), where Q is the number of queries.
  • Iterating through the edges and performing union operations: In the worst case, we iterate through all edges for each query. However, the amortized time complexity of the Union-Find operations is close to O(1) due to path compression and union by rank optimizations.
  • Performing find operations: Each find operation has an amortized time complexity of nearly O(1).

Therefore, the overall time complexity is dominated by the sorting steps, resulting in O(M log M + Q log Q).

Space Complexity Analysis:

  • Union-Find data structure: O(N), where N is the number of nodes.
  • Other variables: O(Q) for storing results and query IDs.

Therefore, the overall space complexity is O(N + Q).

Code Examples (Python):

import bisect
 
class Solution:
    def distanceLimitedPathsExist(self, n: int, edgeList: List[List[int]], queries: List[List[int]]) -> List[bool]:
        parent = list(range(n))
        rank = [0] * n
 
        def find(i):
            if parent[i] == i:
                return i
            parent[i] = find(parent[i])
            return parent[i]
 
        def union(i, j):
            root_i = find(i)
            root_j = find(j)
            if root_i != root_j:
                if rank[root_i] < rank[root_j]:
                    parent[root_i] = root_j
                elif rank[root_i] > rank[root_j]:
                    parent[root_j] = root_i
                else:
                    parent[root_j] = root_i
                    rank[root_i] += 1
                return True
            return False
 
        edgeList.sort(key=lambda x: x[2])
        queries_with_indices = sorted([(q[0], q[1], q[2], i) for i, q in enumerate(queries)], key=lambda x: x[2])
        result = [False] * len(queries)
        edge_index = 0
        for u, v, limit, index in queries_with_indices:
            while edge_index < len(edgeList) and edgeList[edge_index][2] < limit:
                union(edgeList[edge_index][0], edgeList[edge_index][1])
                edge_index += 1
            result[index] = find(u) == find(v)
        return result

This Python code incorporates Union-Find with path compression and union by rank for optimal performance. The sorting of queries and edges is crucial for efficiency. Other language implementations would follow a similar structure, utilizing their respective Union-Find implementations and sorting methods.