Path Existence Queries in a Graph I

Med
#3143Time: `O(Q * n^2)`. For each of the `Q` queries, we build a graph which takes `O(n^2)` time to check all pairs of nodes. The subsequent BFS/DFS takes `O(n + E)`, where `E` can be up to `O(n^2)`. Thus, the total time is dominated by `O(Q * n^2)`.Space: `O(n^2)`. The adjacency list can store up to `O(n^2)` edges in a dense graph.

Prompt

You are given an integer n representing the number of nodes in a graph, labeled from 0 to n - 1.

You are also given an integer array nums of length n sorted in non-decreasing order, and an integer maxDiff.

An undirected edge exists between nodes i and j if the absolute difference between nums[i] and nums[j] is at most maxDiff (i.e., |nums[i] - nums[j]| <= maxDiff).

You are also given a 2D integer array queries. For each queries[i] = [ui, vi], determine whether there exists a path between nodes ui and vi.

Return a boolean array answer, where answer[i] is true if there exists a path between ui and vi in the ith query and false otherwise.

 

Example 1:

Input: n = 2, nums = [1,3], maxDiff = 1, queries = [[0,0],[0,1]]

Output: [true,false]

Explanation:

  • Query [0,0]: Node 0 has a trivial path to itself.
  • Query [0,1]: There is no edge between Node 0 and Node 1 because |nums[0] - nums[1]| = |1 - 3| = 2, which is greater than maxDiff.
  • Thus, the final answer after processing all the queries is [true, false].

Example 2:

Input: n = 4, nums = [2,5,6,8], maxDiff = 2, queries = [[0,1],[0,2],[1,3],[2,3]]

Output: [false,false,true,true]

Explanation:

The resulting graph is:

  • Query [0,1]: There is no edge between Node 0 and Node 1 because |nums[0] - nums[1]| = |2 - 5| = 3, which is greater than maxDiff.
  • Query [0,2]: There is no edge between Node 0 and Node 2 because |nums[0] - nums[2]| = |2 - 6| = 4, which is greater than maxDiff.
  • Query [1,3]: There is a path between Node 1 and Node 3 through Node 2 since |nums[1] - nums[2]| = |5 - 6| = 1 and |nums[2] - nums[3]| = |6 - 8| = 2, both of which are within maxDiff.
  • Query [2,3]: There is an edge between Node 2 and Node 3 because |nums[2] - nums[3]| = |6 - 8| = 2, which is equal to maxDiff.
  • Thus, the final answer after processing all the queries is [false, false, true, true].

 

Constraints:

  • 1 <= n == nums.length <= 105
  • 0 <= nums[i] <= 105
  • nums is sorted in non-decreasing order.
  • 0 <= maxDiff <= 105
  • 1 <= queries.length <= 105
  • queries[i] == [ui, vi]
  • 0 <= ui, vi < n

Approaches

3 approaches with complexity analysis and trade-offs.

This approach tackles each query independently. For every query [u, v], it first constructs the entire graph based on the given conditions: an edge exists between nodes i and j if |nums[i] - nums[j]| <= maxDiff. After building the graph, it performs a graph traversal, such as Breadth-First Search (BFS) or Depth-First Search (DFS), starting from node u to determine if node v is reachable. This process is repeated for all queries.

Algorithm

  • For each query [u, v]:
  • Build an adjacency list for the graph by checking all pairs of nodes (i, j).
  • If |nums[i] - nums[j]| <= maxDiff, add an edge (i, j).
  • Perform a graph traversal (BFS/DFS) from u.
  • If v is reached, a path exists.

Walkthrough

The algorithm for this approach is as follows:

  1. Initialize a boolean array answer to store the results for each query.
  2. For each query [u, v] in the queries array: a. Create an adjacency list to represent the graph. b. Iterate through all unique pairs of nodes (i, j). c. If |nums[i] - nums[j]| <= maxDiff, add an undirected edge between i and j in the adjacency list. d. Use BFS or DFS, starting from u, to traverse the graph. Keep track of visited nodes. e. If v is visited during the traversal, a path exists. Set the corresponding entry in answer to true. Otherwise, set it to false.
  3. Return the answer array.

Here is a code snippet implementing this approach using BFS:

import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue; class Solution {    public boolean[] pathExists(int n, int[] nums, int maxDiff, int[][] queries) {        boolean[] answer = new boolean[queries.length];        for (int i = 0; i < queries.length; i++) {            int u = queries[i][0];            int v = queries[i][1];                        if (u == v) {                answer[i] = true;                continue;            }             List<List<Integer>> adj = new ArrayList<>();            for (int j = 0; j < n; j++) {                adj.add(new ArrayList<>());            }             for (int j = 0; j < n; j++) {                for (int k = j + 1; k < n; k++) {                    if (Math.abs(nums[j] - nums[k]) <= maxDiff) {                        adj.get(j).add(k);                        adj.get(k).add(j);                    }                }            }                        answer[i] = hasPath(n, adj, u, v);        }        return answer;    }     private boolean hasPath(int n, List<List<Integer>> adj, int start, int end) {        Queue<Integer> queue = new LinkedList<>();        boolean[] visited = new boolean[n];                queue.offer(start);        visited[start] = true;                while (!queue.isEmpty()) {            int curr = queue.poll();            if (curr == end) {                return true;            }            for (int neighbor : adj.get(curr)) {                if (!visited[neighbor]) {                    visited[neighbor] = true;                    queue.offer(neighbor);                }            }        }        return false;    }}

Complexity

Time

`O(Q * n^2)`. For each of the `Q` queries, we build a graph which takes `O(n^2)` time to check all pairs of nodes. The subsequent BFS/DFS takes `O(n + E)`, where `E` can be up to `O(n^2)`. Thus, the total time is dominated by `O(Q * n^2)`.

Space

`O(n^2)`. The adjacency list can store up to `O(n^2)` edges in a dense graph.

Trade-offs

Pros

  • Conceptually simple and easy to implement.

Cons

  • Highly inefficient due to redundant graph construction for each query.

  • Will result in a "Time Limit Exceeded" error for the given constraints.

Solutions

class Solution {public  boolean[] pathExistenceQueries(int n, int[] nums, int maxDiff,                                 int[][] queries) {    int[] g = new int[n];    int cnt = 0;    for (int i = 1; i < n; ++i) {      if (nums[i] - nums[i - 1] > maxDiff) {        cnt++;      }      g[i] = cnt;    }    int m = queries.length;    boolean[] ans = new boolean[m];    for (int i = 0; i < m; ++i) {      int u = queries[i][0];      int v = queries[i][1];      ans[i] = g[u] == g[v];    }    return ans;  }}

Video walkthrough

Newsletter

One sharp idea, every week

System design and interview prep — short enough to finish.

No spam. Unsubscribe anytime.

Practice

Same difficulty — related problems to reinforce the pattern.