2903. Find Indices With Index and Value Difference I
Problem Description
You are given an integer array nums
(0-indexed) with length n
, along with two integer parameters: indexDifference
and valueDifference
.
Your task is to find two indices i
and j
(both in the range [0, n - 1]
) that satisfy both of these conditions:
- The absolute difference between the indices must be at least
indexDifference
:abs(i - j) >= indexDifference
- The absolute difference between the values at these indices must be at least
valueDifference
:abs(nums[i] - nums[j]) >= valueDifference
The function should return an array answer
where:
answer = [i, j]
if such a pair of indices existsanswer = [-1, -1]
if no such pair exists
Key points to note:
- The indices
i
andj
can be equal (when bothindexDifference
andvalueDifference
are 0) - If multiple valid pairs exist, you can return any one of them
- The order matters in the return value - you need to return exactly
[i, j]
as found
The solution uses a sliding window approach with two pointers maintaining a gap of indexDifference
. As the window slides through the array, it tracks the minimum and maximum values seen in the left portion of the window. For each position i
, it checks if the current element forms a valid pair with either the minimum or maximum element from the valid range (indices that are at least indexDifference
away). This ensures an efficient O(n) time complexity solution.
Intuition
The key insight is that for any index i
, we need to find an index j
where abs(i - j) >= indexDifference
and abs(nums[i] - nums[j]) >= valueDifference
.
Let's think about this systematically. For a given index i
, all valid j
indices must be at least indexDifference
away from i
. This means we're looking at indices in the range [0, i - indexDifference]
when i >= indexDifference
.
Now, for the value difference condition abs(nums[i] - nums[j]) >= valueDifference
, we need to maximize the absolute difference. The absolute difference is maximized when we pair nums[i]
with either:
- The smallest value in the valid range (to maximize
nums[i] - nums[j]
whennums[i]
is large) - The largest value in the valid range (to maximize
nums[j] - nums[i]
whennums[i]
is small)
This leads us to the core strategy: as we iterate through the array with index i
, we only need to track two things from the valid range [0, i - indexDifference]
:
- The index of the minimum value (
mi
) - The index of the maximum value (
mx
)
Why does this work? Because if there exists any valid pair involving index i
, it must involve either the minimum or maximum value from the valid range. Any other value would give us a smaller absolute difference, so checking just these two extremes is sufficient.
The sliding window naturally emerges from this insight. As i
moves forward, the valid range grows by including one more element at position i - indexDifference
. We update our tracked minimum and maximum indices if this new element is smaller or larger than our current extremes, then check if either extreme forms a valid pair with nums[i]
.
This reduces our problem from checking all possible pairs (O(nΒ²)) to just checking two specific pairs at each position (O(n)).
Learn more about Two Pointers patterns.
Solution Approach
The implementation uses a two-pointer technique combined with tracking minimum and maximum values to achieve an O(n) time complexity solution.
Initialize tracking variables:
mi = mx = 0
: These variables store the indices of the minimum and maximum values in the valid range. Initially both point to index 0.
Main iteration:
Starting from i = indexDifference
to len(nums) - 1
:
-
Calculate the left boundary:
j = i - indexDifference
- This represents the newest element that enters our valid range (elements at least
indexDifference
away fromi
)
- This represents the newest element that enters our valid range (elements at least
-
Update minimum and maximum indices:
- If
nums[j] < nums[mi]
, updatemi = j
(found a new minimum in the valid range) - If
nums[j] > nums[mx]
, updatemx = j
(found a new maximum in the valid range)
Note that we use strict inequalities here. This ensures we only update when we find strictly better values, maintaining stability in our choices.
- If
-
Check for valid pairs:
- First check:
nums[i] - nums[mi] >= valueDifference
- This checks if the current element minus the minimum value meets the threshold
- If true, return
[mi, i]
- Second check:
nums[mx] - nums[i] >= valueDifference
- This checks if the maximum value minus the current element meets the threshold
- If true, return
[mx, i]
- First check:
Why this order of checks works:
- By checking
nums[i] - nums[mi]
first, we handle cases wherenums[i]
is relatively large - By checking
nums[mx] - nums[i]
second, we handle cases wherenums[i]
is relatively small - Together, these two checks cover all possible ways to achieve
abs(nums[i] - nums[j]) >= valueDifference
Return value:
If the loop completes without finding a valid pair, return [-1, -1]
.
Time and Space Complexity:
- Time: O(n) - single pass through the array
- Space: O(1) - only using a constant amount of extra variables
The elegance of this solution lies in recognizing that we only need to track extremes (minimum and maximum) from the valid range, rather than considering all elements, which transforms a potentially quadratic problem into a linear one.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with a concrete example:
Input: nums = [5, 1, 4, 1, 9]
, indexDifference = 2
, valueDifference = 4
We need to find indices i
and j
where:
abs(i - j) >= 2
(indices are at least 2 apart)abs(nums[i] - nums[j]) >= 4
(values differ by at least 4)
Initial Setup:
mi = 0, mx = 0
(both tracking index 0 initially, wherenums[0] = 5
)
Iteration 1: i = 2
- Calculate left boundary:
j = 2 - 2 = 0
- Check if
nums[0] = 5
updates our min/max:- Is
5 < nums[mi] = 5
? No, somi
stays 0 - Is
5 > nums[mx] = 5
? No, somx
stays 0
- Is
- Check for valid pairs with
nums[2] = 4
:nums[2] - nums[mi] = 4 - 5 = -1
. Is-1 >= 4
? Nonums[mx] - nums[2] = 5 - 4 = 1
. Is1 >= 4
? No
- No valid pair found, continue
Iteration 2: i = 3
- Calculate left boundary:
j = 3 - 2 = 1
- Check if
nums[1] = 1
updates our min/max:- Is
1 < nums[mi] = 5
? Yes! Updatemi = 1
- Is
1 > nums[mx] = 5
? No, somx
stays 0
- Is
- Now
mi = 1
(value 1),mx = 0
(value 5) - Check for valid pairs with
nums[3] = 1
:nums[3] - nums[mi] = 1 - 1 = 0
. Is0 >= 4
? Nonums[mx] - nums[3] = 5 - 1 = 4
. Is4 >= 4
? Yes!
- Found valid pair: return
[0, 3]
Verification:
- Index difference:
abs(0 - 3) = 3 >= 2
β - Value difference:
abs(nums[0] - nums[3]) = abs(5 - 1) = 4 >= 4
β
The algorithm correctly identifies [0, 3]
as a valid answer. Note how tracking just the minimum and maximum indices from the valid range was sufficient - we didn't need to check all possible pairs, just the extremes against the current position.
Solution Implementation
1class Solution:
2 def findIndices(
3 self, nums: List[int], indexDifference: int, valueDifference: int
4 ) -> List[int]:
5 # Track indices of minimum and maximum values seen so far
6 min_index = 0
7 max_index = 0
8
9 # Start from indexDifference position to ensure index difference constraint
10 for i in range(indexDifference, len(nums)):
11 # Calculate the corresponding left boundary index
12 j = i - indexDifference
13
14 # Update minimum value index if we found a smaller value at position j
15 if nums[j] < nums[min_index]:
16 min_index = j
17
18 # Update maximum value index if we found a larger value at position j
19 if nums[j] > nums[max_index]:
20 max_index = j
21
22 # Check if current element minus minimum meets value difference requirement
23 if nums[i] - nums[min_index] >= valueDifference:
24 return [min_index, i]
25
26 # Check if maximum minus current element meets value difference requirement
27 if nums[max_index] - nums[i] >= valueDifference:
28 return [max_index, i]
29
30 # No valid pair found
31 return [-1, -1]
32
1class Solution {
2 public int[] findIndices(int[] nums, int indexDifference, int valueDifference) {
3 // Track indices of minimum and maximum values seen so far
4 int minIndex = 0;
5 int maxIndex = 0;
6
7 // Iterate through array starting from indexDifference position
8 for (int i = indexDifference; i < nums.length; i++) {
9 // Calculate the corresponding index that maintains required index difference
10 int j = i - indexDifference;
11
12 // Update minimum value index if current element at j is smaller
13 if (nums[j] < nums[minIndex]) {
14 minIndex = j;
15 }
16
17 // Update maximum value index if current element at j is larger
18 if (nums[j] > nums[maxIndex]) {
19 maxIndex = j;
20 }
21
22 // Check if difference between current element and minimum meets value requirement
23 if (nums[i] - nums[minIndex] >= valueDifference) {
24 return new int[] {minIndex, i};
25 }
26
27 // Check if difference between maximum and current element meets value requirement
28 if (nums[maxIndex] - nums[i] >= valueDifference) {
29 return new int[] {maxIndex, i};
30 }
31 }
32
33 // Return [-1, -1] if no valid pair of indices found
34 return new int[] {-1, -1};
35 }
36}
37
1class Solution {
2public:
3 vector<int> findIndices(vector<int>& nums, int indexDifference, int valueDifference) {
4 // Track indices of minimum and maximum values seen so far
5 int minIndex = 0;
6 int maxIndex = 0;
7
8 // Start from indexDifference position to ensure index constraint is met
9 for (int i = indexDifference; i < nums.size(); ++i) {
10 // Calculate the corresponding left boundary index
11 int j = i - indexDifference;
12
13 // Update minimum value index if we found a smaller value at position j
14 if (nums[j] < nums[minIndex]) {
15 minIndex = j;
16 }
17
18 // Update maximum value index if we found a larger value at position j
19 if (nums[j] > nums[maxIndex]) {
20 maxIndex = j;
21 }
22
23 // Check if current element minus minimum meets the value difference requirement
24 if (nums[i] - nums[minIndex] >= valueDifference) {
25 return {minIndex, i};
26 }
27
28 // Check if maximum minus current element meets the value difference requirement
29 if (nums[maxIndex] - nums[i] >= valueDifference) {
30 return {maxIndex, i};
31 }
32 }
33
34 // Return {-1, -1} if no valid pair is found
35 return {-1, -1};
36 }
37};
38
1/**
2 * Finds two indices i and j in the array such that:
3 * - The absolute difference between indices is at least indexDifference
4 * - The absolute difference between values is at least valueDifference
5 *
6 * @param nums - The input array of numbers
7 * @param indexDifference - Minimum required difference between indices
8 * @param valueDifference - Minimum required difference between values
9 * @returns Array containing [i, j] if found, otherwise [-1, -1]
10 */
11function findIndices(nums: number[], indexDifference: number, valueDifference: number): number[] {
12 // Track indices of minimum and maximum values seen so far
13 let minIndex: number = 0;
14 let maxIndex: number = 0;
15
16 // Iterate through array starting from indexDifference position
17 for (let currentIndex: number = indexDifference; currentIndex < nums.length; currentIndex++) {
18 // Calculate the corresponding index that maintains minimum index difference
19 const previousIndex: number = currentIndex - indexDifference;
20
21 // Update minimum index if we found a smaller value
22 if (nums[previousIndex] < nums[minIndex]) {
23 minIndex = previousIndex;
24 }
25
26 // Update maximum index if we found a larger value
27 if (nums[previousIndex] > nums[maxIndex]) {
28 maxIndex = previousIndex;
29 }
30
31 // Check if current value minus minimum value meets value difference requirement
32 if (nums[currentIndex] - nums[minIndex] >= valueDifference) {
33 return [minIndex, currentIndex];
34 }
35
36 // Check if maximum value minus current value meets value difference requirement
37 if (nums[maxIndex] - nums[currentIndex] >= valueDifference) {
38 return [maxIndex, currentIndex];
39 }
40 }
41
42 // No valid pair found
43 return [-1, -1];
44}
45
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
.
The algorithm uses a single for loop that iterates from index indexDifference
to len(nums) - 1
. This means it performs at most n - indexDifference
iterations. Inside each iteration, all operations are constant time O(1)
:
- Calculating
j = i - indexDifference
isO(1)
- Comparing and updating
mi
andmx
variables isO(1)
- Checking the two conditions for value difference is
O(1)
Since the loop runs at most n
times and each iteration takes constant time, the overall time complexity is O(n)
.
Space Complexity: O(1)
The algorithm only uses a fixed amount of extra space regardless of the input size:
- Two integer variables
mi
andmx
to track indices of minimum and maximum values - Loop variable
i
and calculated variablej
No additional data structures that scale with input size are used, making the space complexity constant O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling the Index Difference Constraint
A common mistake is misunderstanding how the sliding window maintains the index difference. Developers might incorrectly think they need to check all elements in the range [0, i - indexDifference]
instead of just updating with the single element at position i - indexDifference
.
Incorrect approach:
# Wrong: Checking all elements up to i - indexDifference
for i in range(indexDifference, len(nums)):
for j in range(0, i - indexDifference + 1): # This makes it O(nΒ²)
if abs(nums[i] - nums[j]) >= valueDifference:
return [j, i]
Correct approach:
The solution maintains running min/max indices, only updating them with the newest valid element at position j = i - indexDifference
.
2. Using Non-Strict Inequalities When Updating Min/Max
Using <=
or >=
when updating min/max indices can lead to unnecessary updates and potential inefficiency in certain edge cases.
Problematic:
if nums[j] <= nums[min_index]: # Using <= instead of < min_index = j
Why it matters:
When values are equal, we should keep the earlier index as it's already been compared against more elements. Using strict inequality <
ensures we only update when we find a strictly better value.
3. Forgetting to Check Both Directions for Value Difference
A critical mistake is only checking one direction of the absolute value difference, missing valid pairs.
Incorrect:
# Only checking if current element is larger than minimum if nums[i] - nums[min_index] >= valueDifference: return [min_index, i] # Missing the check for when current element is smaller than maximum!
Correct: The solution must check both:
nums[i] - nums[min_index] >= valueDifference
(when nums[i] is large)nums[max_index] - nums[i] >= valueDifference
(when nums[i] is small)
4. Initializing Min/Max Indices Incorrectly
Starting with invalid indices or extreme values instead of actual array indices.
Wrong:
min_index = -1 # Invalid index
max_index = -1
# Or using value-based initialization
min_val = float('inf')
max_val = float('-inf')
Correct:
Initialize both min_index
and max_index
to 0, as they represent actual valid indices in the array.
5. Off-by-One Error in Loop Range
Starting the loop at the wrong position or using incorrect boundary conditions.
Common mistakes:
# Starting too early (doesn't ensure index difference)
for i in range(len(nums)):
j = i - indexDifference
if j < 0: # Need extra check
continue
# Or starting too late
for i in range(indexDifference + 1, len(nums)): # Misses valid index at indexDifference
Correct:
Start exactly at indexDifference
to ensure the first valid j
is 0, and continue to len(nums) - 1
.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!