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
.
-
Initialization: We start by initializing an array
g
of sizen
with all elements set to0
. This array will track the component index for each node. We also initialize a countercnt
to0
, which keeps track of the current component index. -
Grouping Nodes: We iterate through
nums
starting from index1
. For each nodei
, we check the difference betweennums[i]
andnums[i-1]
. If this difference is greater thanmaxDiff
, it implies that nodei
starts a new connected component from the previous node. When this condition holds, we increment thecnt
by1
to denote a new component beginning. We then assign thiscnt
value tog[i]
to mark that nodei
belongs to this component.for i in range(1, n): if nums[i] - nums[i - 1] > maxDiff: cnt += 1 g[i] = cnt
-
Answering Queries: For each query
[u, v]
from thequeries
array, we simply check ifg[u]
is equal tog[v]
. If they are equal, it means nodesu
andv
are in the same connected component, and we returntrue
for this query. If not, we returnfalse
.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 EvaluatorExample 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 from0
to4
.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 most2
.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 tomaxDiff
. Thus, node1
is in the same component as node0
:g = [0, 0, 0, 0, 0]
. -
At index
2
:nums[2] - nums[1] = 4 - 3 = 1
which is less than or equal tomaxDiff
. Thus, node2
is in the same component:g = [0, 0, 0, 0, 0]
. -
At index
3
:nums[3] - nums[2] = 8 - 4 = 4
which is greater thanmaxDiff
. Hence, node3
starts a new component. Incrementcnt
to1
:g = [0, 0, 0, 1, 0]
. -
At index
4
:nums[4] - nums[3] = 9 - 8 = 1
which is less than or equal tomaxDiff
. Thus, node4
is in the same component as node3
:g = [0, 0, 0, 1, 1]
.
Step 3: Answering Queries
Lastly, we address each query:
-
Query
[0, 2]
: Nodes0
and2
are in the same component sinceg[0] == g[2]
, so returntrue
. -
Query
[0, 3]
: Nodes0
and3
are in different components becauseg[0] != g[3]
, resulting infalse
. -
Query
[3, 4]
: Nodes3
and4
are in the same component sinceg[3] == g[4]
, so returntrue
.
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.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro Once we have a strong grasp of recursion and Depth First Search we can now introduce Disjoint Set Union DSU This data structure is motivated by the following problem Suppose we have sets of elements
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!