Facebook Pixel

3532. Path Existence Queries in a Graph I

Problem Description

You are given an integer n representing the number of nodes in a graph, labeled from 0 to n - 1. You also receive 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). Additionally, you are provided with a 2D integer array queries. For each queries[i] = [u_i, v_i], you must determine whether there exists a path between nodes u_i and v_i. The objective is to return a boolean array answer, where answer[i] is true if there exists a path between u_i and v_i for the i-th query, and false otherwise.

Intuition

To solve this problem, we need to understand how nodes are connected. Given that nums is sorted and a connection (an edge) between nodes exists if their values' absolute difference is within maxDiff, the nodes can be divided into separate connected components. These components are contiguous segments of nodes connected directly or indirectly where each node in a segment can reach any other node within the same segment.

To achieve this grouping, we process nums from start to finish, initializing an array g that tracks the component index for each node. Starting from index 1, for each node, if the difference between this node and the previous one exceeds maxDiff, they belong to different components. Thus, we increment a component counter cnt. We assign this counter as the component identifier for each node.

Subsequently, when processing each query, we can simply check if the nodes u and v belong to the same component by comparing their values in g. If they have the same component index, they are connected, and the answer is true. Otherwise, it is false.

Learn more about Union Find, Graph and Binary Search patterns.

Solution Approach

In this solution, we use an approach called "Grouping." The main idea is to determine which nodes are part of the same connected component by looking at the differences between consecutive elements in the array nums.

  1. Initialization: We start by initializing an array g of size n with all elements set to 0. This array will track the component index for each node. We also initialize a counter cnt to 0, which keeps track of the current component index.

  2. Grouping Nodes: We iterate through nums starting from index 1. For each node i, we check the difference between nums[i] and nums[i-1]. If this difference is greater than maxDiff, it implies that node i starts a new connected component from the previous node. When this condition holds, we increment the cnt by 1 to denote a new component beginning. We then assign this cnt value to g[i] to mark that node i belongs to this component.

    for i in range(1, n):
        if nums[i] - nums[i - 1] > maxDiff:
            cnt += 1
        g[i] = cnt
  3. Answering Queries: For each query [u, v] from the queries array, we simply check if g[u] is equal to g[v]. If they are equal, it means nodes u and v are in the same connected component, and we return true for this query. If not, we return false.

    return [g[u] == g[v] for u, v in queries]

The algorithm effectively uses the sorted nature of nums and the fixed maxDiff threshold to determine connectivity, leading to a time complexity of O(n) for processing the nums and O(m) for answering the queries where m is the number of queries.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's go through a small example to illustrate the solution approach.

Suppose we have:

  • n = 5, which means there are 5 nodes labeled from 0 to 4.
  • nums = [1, 3, 4, 8, 9], which is a sorted array.
  • maxDiff = 2, meaning an edge exists between nodes if the absolute difference between their values is at most 2.
  • queries = [[0, 2], [0, 3], [3, 4]].

Step 1: Initialization

We begin by initializing an array g of size n with zeros: g = [0, 0, 0, 0, 0]. We also initialize our component counter cnt to 0.

Step 2: Grouping Nodes

Now, we iterate through the nums array starting from index 1.

  • At index 1: nums[1] - nums[0] = 3 - 1 = 2 which is less than or equal to maxDiff. Thus, node 1 is in the same component as node 0: g = [0, 0, 0, 0, 0].

  • At index 2: nums[2] - nums[1] = 4 - 3 = 1 which is less than or equal to maxDiff. Thus, node 2 is in the same component: g = [0, 0, 0, 0, 0].

  • At index 3: nums[3] - nums[2] = 8 - 4 = 4 which is greater than maxDiff. Hence, node 3 starts a new component. Increment cnt to 1: g = [0, 0, 0, 1, 0].

  • At index 4: nums[4] - nums[3] = 9 - 8 = 1 which is less than or equal to maxDiff. Thus, node 4 is in the same component as node 3: g = [0, 0, 0, 1, 1].

Step 3: Answering Queries

Lastly, we address each query:

  • Query [0, 2]: Nodes 0 and 2 are in the same component since g[0] == g[2], so return true.

  • Query [0, 3]: Nodes 0 and 3 are in different components because g[0] != g[3], resulting in false.

  • Query [3, 4]: Nodes 3 and 4 are in the same component since g[3] == g[4], so return true.

Final Answer

The boolean array reflecting the answers to the queries is [true, false, true].

Solution Implementation

1from typing import List
2
3class Solution:
4    def pathExistenceQueries(
5        self, n: int, nums: List[int], maxDiff: int, queries: List[List[int]]
6    ) -> List[bool]:
7        # Initialize a list g to keep track of the number of segments exceeding maxDiff
8        g = [0] * n
9        # Initialize a counter to track the number of problematic segments
10        count_segments_exceeded = 0
11
12        # Iterate through nums starting from the second element
13        for i in range(1, n):
14            # Check if the difference between consecutive elements exceeds maxDiff
15            if nums[i] - nums[i - 1] > maxDiff:
16                # Increment the counter if the difference exceeds maxDiff
17                count_segments_exceeded += 1
18            # Assign the current count to g to mark segments
19            g[i] = count_segments_exceeded
20
21        # For each query, check if both points belong to the same segment
22        return [g[u] == g[v] for u, v in queries]
23
1class Solution {
2
3    /**
4     * This method returns a boolean array indicating whether a path exists between pairs 
5     * of indices in the input array nums such that the difference between consecutive 
6     * elements in nums does not exceed maxDiff.
7     *
8     * @param n        Number of elements in the input array nums.
9     * @param nums     An array of integers.
10     * @param maxDiff  The maximum allowed difference between consecutive elements in nums.
11     * @param queries  A 2D array where each element is a pair of indices representing a query.
12     * @return         A boolean array indicating whether there's a valid path for each query.
13     */
14    public boolean[] pathExistenceQueries(int n, int[] nums, int maxDiff, int[][] queries) {
15        // Array to track the number of breaks (differences greater than maxDiff) up to each index.
16        int[] breakCounts = new int[n];
17        int count = 0;
18
19        // Populate the breakCounts array by counting differences greater than maxDiff.
20        for (int i = 1; i < n; ++i) {
21            if (nums[i] - nums[i - 1] > maxDiff) {
22                count++;
23            }
24            breakCounts[i] = count;
25        }
26
27        int totalQueries = queries.length;
28        boolean[] results = new boolean[totalQueries];
29
30        // Process each query to determine if a path exists between the given indices.
31        for (int i = 0; i < totalQueries; ++i) {
32            int startIndex = queries[i][0];
33            int endIndex = queries[i][1];
34            // A path exists if the number of breaks is the same between the two indices.
35            results[i] = breakCounts[startIndex] == breakCounts[endIndex];
36        }
37
38        return results;
39    }
40}
41
1class Solution {
2public:
3    vector<bool> pathExistenceQueries(int n, vector<int>& nums, int maxDiff, vector<vector<int>>& queries) {
4        // Initialize a vector to store the group identifier for each index
5        vector<int> group(n);
6        int count = 0;
7      
8        // Iterate through the nums array to calculate the group identifiers
9        for (int i = 1; i < n; ++i) {
10            // If the difference between consecutive numbers exceeds maxDiff, 
11            // increment the count indicating a new group
12            if (nums[i] - nums[i - 1] > maxDiff) {
13                ++count;
14            }
15            // Assign the group identifier to the current index
16            group[i] = count;
17        }
18
19        // Vector to store the results of the queries
20        vector<bool> answer;
21      
22        // Process each query
23        for (const auto& query : queries) {
24            int u = query[0], v = query[1];
25            // Check if the two positions belong to the same group
26            answer.push_back(group[u] == group[v]);
27        }
28      
29        return answer; // Return the list of results
30    }
31};
32
1// Function to determine the existence of valid paths based on query conditions
2function pathExistenceQueries(
3    n: number,          // Number of elements in nums array
4    nums: number[],     // Array containing the sequence of numbers
5    maxDiff: number,    // Maximum allowed difference between consecutive numbers for path continuity
6    queries: number[][] // Array of query pairs [u, v]
7): boolean[] {
8    const g: number[] = Array(n).fill(0); // Array to store the group difference count
9    let cnt = 0; // Counter to track the number of breaks where difference exceeds maxDiff
10
11    // Loop through the nums array to compute the number of breaks in paths
12    for (let i = 1; i < n; ++i) {
13        // Increment the counter if the difference exceeds maxDiff
14        if (nums[i] - nums[i - 1] > maxDiff) {
15            ++cnt;
16        }
17        g[i] = cnt; // Store the current count in the corresponding position in g
18    }
19
20    // Process each query to determine path existence
21    return queries.map(([u, v]) => g[u] === g[v]);
22}
23

Time and Space Complexity

The time complexity of the code is O(n). This is because the code involves a single loop that iterates over the nums array once to compute the g array, where n is the length of the nums array. Each iteration involves simple arithmetic operations and comparisons that take constant time.

The space complexity is O(n), as an additional array g of size n is used to store cumulative counts based on the difference condition. Other space uses are negligible compared to the g array.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More