1906. Minimum Absolute Difference Queries
Problem Description
You are given an integer array nums
and a 2D array queries
where each query is represented as [li, ri]
. For each query, you need to find the minimum absolute difference in the subarray nums[li...ri]
.
The minimum absolute difference of an array is the smallest value of |a[i] - a[j]|
where:
0 <= i < j < array.length
a[i] != a[j]
(the two elements must be different)- If all elements in the array are the same, return
-1
For example, in the array [5, 2, 3, 7, 2]
:
- The minimum absolute difference is
|2 - 3| = 1
- Note that even though there are two 2's, we can't use
|2 - 2| = 0
because the elements must be different values
Your task is to process each query and return an array ans
where ans[i]
is the minimum absolute difference for the subarray specified by the i-th
query.
The solution uses a prefix sum approach to efficiently track which values (1-100) appear in each subarray range. For each query:
- It builds a frequency count of values present in the subarray using prefix sums
- It identifies all distinct values that appear in the range
- It finds the minimum difference between consecutive distinct values
- If only one distinct value exists (all elements are the same), it returns
-1
The time complexity is O(m * 100 + n * 100)
where m
is the length of nums and n
is the number of queries, since values are constrained to the range [1, 100].
Intuition
The key insight is that to find the minimum absolute difference between different elements, we need to identify which distinct values exist in a subarray and find the smallest gap between them.
Since we're dealing with multiple queries on different subarrays, a naive approach of sorting each subarray would be inefficient. We need a way to quickly determine which values are present in any given range.
The crucial observation is that the problem constraints limit values to the range [1, 100]
. This small range allows us to use a fixed-size frequency tracking approach. Instead of sorting or using complex data structures, we can simply track the presence of each possible value.
To efficiently answer range queries, we can use prefix sums. For each position in the array and each possible value (1 to 100), we maintain a count of how many times that value has appeared up to that position. This creates a 2D prefix sum array where pre_sum[i][j]
represents the count of value j
in nums[0...i-1]
.
With this preprocessing, for any query [left, right]
, we can instantly determine if value j
exists in that range by checking if pre_sum[right+1][j] - pre_sum[left][j] > 0
.
Once we know which values are present, finding the minimum difference becomes straightforward: we iterate through all possible values (1 to 100) in order, track consecutive values that appear in the range, and calculate their differences. The minimum of these differences is our answer.
This approach trades space for time - we use O(m * 100)
space to achieve O(100)
time per query, which is constant time given the constraint on values.
Solution Approach
The solution implements a prefix sum approach with the following steps:
1. Building the Prefix Sum Array:
pre_sum = [[0] * 101 for _ in range(m + 1)]
We create a 2D array pre_sum
where pre_sum[i][j]
stores the count of value j
in the first i
elements of nums
. The array has dimensions (m+1) × 101
where m
is the length of nums
.
for i in range(1, m + 1):
for j in range(1, 101):
t = 1 if nums[i - 1] == j else 0
pre_sum[i][j] = pre_sum[i - 1][j] + t
For each position i
and value j
, we check if nums[i-1] == j
. If yes, we increment the count from the previous position by 1, otherwise we keep the same count.
2. Processing Each Query:
For each query [left, right]
:
left, right = queries[i][0], queries[i][1] + 1
We adjust right
to right + 1
to make it inclusive for our prefix sum calculation.
3. Finding Present Values and Minimum Difference:
t = inf
last = -1
for j in range(1, 101):
if pre_sum[right][j] - pre_sum[left][j] > 0:
if last != -1:
t = min(t, j - last)
last = j
- We iterate through all possible values from 1 to 100
- For each value
j
, we check if it exists in the range using:pre_sum[right][j] - pre_sum[left][j] > 0
- If the value exists:
- If we've seen a previous value (
last != -1
), we calculate the differencej - last
and update our minimum - We update
last
to the current valuej
- If we've seen a previous value (
4. Handling Edge Cases:
if t == inf: t = -1 ans.append(t)
If t
remains as inf
, it means we found only one distinct value (or none), so we return -1
as specified.
The algorithm efficiently handles range queries in O(100)
time per query after O(m × 100)
preprocessing, making it suitable for multiple queries on the same array.
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 solution with a concrete example:
Input: nums = [4, 2, 3, 2, 4]
, queries = [[0, 2], [2, 4], [0, 4]]
Step 1: Build Prefix Sum Array
We create a prefix sum array to track occurrences of each value (1-100) up to each position:
Position: 0 1 2 3 4 5 nums: - [4] [2] [3] [2] [4]
Key prefix sum values (showing only values 2, 3, 4 for brevity):
pre_sum[0][2] = 0, pre_sum[0][3] = 0, pre_sum[0][4] = 0 pre_sum[1][2] = 0, pre_sum[1][3] = 0, pre_sum[1][4] = 1 pre_sum[2][2] = 1, pre_sum[2][3] = 0, pre_sum[2][4] = 1 pre_sum[3][2] = 1, pre_sum[3][3] = 1, pre_sum[3][4] = 1 pre_sum[4][2] = 2, pre_sum[4][3] = 1, pre_sum[4][4] = 1 pre_sum[5][2] = 2, pre_sum[5][3] = 1, pre_sum[5][4] = 2
Step 2: Process Query [0, 2] (subarray: [4, 2, 3])
Check which values exist in range [0, 2]:
- Value 2:
pre_sum[3][2] - pre_sum[0][2] = 1 - 0 = 1
✓ (exists) - Value 3:
pre_sum[3][3] - pre_sum[0][3] = 1 - 0 = 1
✓ (exists) - Value 4:
pre_sum[3][4] - pre_sum[0][4] = 1 - 0 = 1
✓ (exists)
Find minimum difference between consecutive present values:
- First present value: 2 (set
last = 2
) - Next present value: 3 → difference =
3 - 2 = 1
- Next present value: 4 → difference =
4 - 3 = 1
- Minimum difference = 1
Step 3: Process Query [2, 4] (subarray: [3, 2, 4])
Check which values exist in range [2, 4]:
- Value 2:
pre_sum[5][2] - pre_sum[2][2] = 2 - 1 = 1
✓ (exists) - Value 3:
pre_sum[5][3] - pre_sum[2][3] = 1 - 0 = 1
✓ (exists) - Value 4:
pre_sum[5][4] - pre_sum[2][4] = 2 - 1 = 1
✓ (exists)
Find minimum difference:
- Present values in order: 2, 3, 4
- Differences:
3 - 2 = 1
,4 - 3 = 1
- Minimum difference = 1
Step 4: Process Query [0, 4] (subarray: [4, 2, 3, 2, 4])
Check which values exist in range [0, 4]:
- Value 2:
pre_sum[5][2] - pre_sum[0][2] = 2 - 0 = 2
✓ (exists) - Value 3:
pre_sum[5][3] - pre_sum[0][3] = 1 - 0 = 1
✓ (exists) - Value 4:
pre_sum[5][4] - pre_sum[0][4] = 2 - 0 = 2
✓ (exists)
Find minimum difference:
- Present values in order: 2, 3, 4
- Differences:
3 - 2 = 1
,4 - 3 = 1
- Minimum difference = 1
Final Answer: [1, 1, 1]
The algorithm efficiently finds that all three queries have a minimum absolute difference of 1, using the prefix sum array to quickly identify which values are present in each subarray range.
Solution Implementation
1class Solution:
2 def minDifference(self, nums: List[int], queries: List[List[int]]) -> List[int]:
3 """
4 Find the minimum absolute difference between any two distinct elements
5 in the subarray for each query.
6
7 Args:
8 nums: List of integers (values range from 1 to 100)
9 queries: List of [left, right] index pairs representing subarrays
10
11 Returns:
12 List of minimum differences for each query, -1 if no valid difference exists
13 """
14 num_elements = len(nums)
15 num_queries = len(queries)
16
17 # Build prefix sum array for each value from 1 to 100
18 # prefix_count[i][val] = count of 'val' in nums[0:i]
19 MAX_VALUE = 100
20 prefix_count = [[0] * (MAX_VALUE + 1) for _ in range(num_elements + 1)]
21
22 # Calculate prefix sums for each possible value
23 for i in range(1, num_elements + 1):
24 for value in range(1, MAX_VALUE + 1):
25 # Copy previous counts
26 prefix_count[i][value] = prefix_count[i - 1][value]
27 # Increment if current element equals this value
28 if nums[i - 1] == value:
29 prefix_count[i][value] += 1
30
31 # Process each query
32 result = []
33 for query_idx in range(num_queries):
34 left_idx = queries[query_idx][0]
35 right_idx = queries[query_idx][1] + 1 # Convert to exclusive right bound
36
37 # Find minimum difference between distinct values in the subarray
38 min_diff = float('inf')
39 prev_value = -1
40
41 # Check which values appear in the subarray
42 for value in range(1, MAX_VALUE + 1):
43 count_in_range = prefix_count[right_idx][value] - prefix_count[left_idx][value]
44
45 if count_in_range > 0: # This value exists in the subarray
46 if prev_value != -1:
47 # Calculate difference with previous existing value
48 min_diff = min(min_diff, value - prev_value)
49 prev_value = value
50
51 # If no valid difference found (less than 2 distinct values), return -1
52 if min_diff == float('inf'):
53 min_diff = -1
54
55 result.append(min_diff)
56
57 return result
58
1class Solution {
2 public int[] minDifference(int[] nums, int[][] queries) {
3 int arrayLength = nums.length;
4 int queryCount = queries.length;
5
6 // Build prefix sum array for each value from 1 to 100
7 // prefixSum[i][j] represents count of value j in nums[0...i-1]
8 int[][] prefixSum = new int[arrayLength + 1][101];
9
10 for (int i = 1; i <= arrayLength; i++) {
11 for (int value = 1; value <= 100; value++) {
12 // Check if current element equals the value
13 int increment = (nums[i - 1] == value) ? 1 : 0;
14 // Accumulate count from previous position
15 prefixSum[i][value] = prefixSum[i - 1][value] + increment;
16 }
17 }
18
19 // Process each query to find minimum difference
20 int[] result = new int[queryCount];
21
22 for (int queryIndex = 0; queryIndex < queryCount; queryIndex++) {
23 int leftBound = queries[queryIndex][0];
24 int rightBound = queries[queryIndex][1] + 1; // Convert to exclusive right bound
25
26 int minDiff = Integer.MAX_VALUE;
27 int previousValue = -1;
28
29 // Check each possible value from 1 to 100
30 for (int value = 1; value <= 100; value++) {
31 // Check if this value exists in the query range
32 if (prefixSum[rightBound][value] > prefixSum[leftBound][value]) {
33 // If we have a previous value, calculate difference
34 if (previousValue != -1) {
35 minDiff = Math.min(minDiff, value - previousValue);
36 }
37 // Update previous value for next iteration
38 previousValue = value;
39 }
40 }
41
42 // If no valid difference found, return -1
43 if (minDiff == Integer.MAX_VALUE) {
44 minDiff = -1;
45 }
46
47 result[queryIndex] = minDiff;
48 }
49
50 return result;
51 }
52}
53
1class Solution {
2public:
3 vector<int> minDifference(vector<int>& nums, vector<vector<int>>& queries) {
4 int arraySize = nums.size();
5 int queryCount = queries.size();
6
7 // Create prefix sum array for each value from 1 to 100
8 // prefixSum[i][j] represents count of value j in nums[0...i-1]
9 int prefixSum[arraySize + 1][101];
10
11 // Initialize prefix sum array with zeros (implicit in stack allocation)
12 // Build prefix sum for each possible value (1 to 100)
13 for (int i = 1; i <= arraySize; ++i) {
14 for (int value = 1; value <= 100; ++value) {
15 // Check if current element equals this value
16 int increment = (nums[i - 1] == value) ? 1 : 0;
17 // Update prefix sum
18 prefixSum[i][value] = prefixSum[i - 1][value] + increment;
19 }
20 }
21
22 // Process each query to find minimum difference
23 vector<int> result(queryCount);
24
25 for (int queryIdx = 0; queryIdx < queryCount; ++queryIdx) {
26 // Get query range [left, right], converting to 1-indexed
27 int leftIdx = queries[queryIdx][0];
28 int rightIdx = queries[queryIdx][1] + 1;
29
30 // Initialize minimum difference to maximum possible value
31 int minDiff = 101;
32 int previousValue = -1;
33
34 // Check each possible value from 1 to 100
35 for (int value = 1; value <= 100; ++value) {
36 // Check if this value exists in the query range
37 if (prefixSum[rightIdx][value] > prefixSum[leftIdx][value]) {
38 // If we have a previous value, calculate difference
39 if (previousValue != -1) {
40 minDiff = min(minDiff, value - previousValue);
41 }
42 // Update previous value for next iteration
43 previousValue = value;
44 }
45 }
46
47 // If no valid difference found, return -1
48 if (minDiff == 101) {
49 minDiff = -1;
50 }
51
52 result[queryIdx] = minDiff;
53 }
54
55 return result;
56 }
57};
58
1function minDifference(nums: number[], queries: number[][]): number[] {
2 const arrayLength: number = nums.length;
3 const queriesCount: number = queries.length;
4 const maxValue: number = 100; // Maximum possible value in nums array
5
6 // Build prefix sum array for each distinct value (1 to 100)
7 // prefixCount[i][j] represents count of value j in nums[0...i-1]
8 const prefixCount: number[][] = [];
9 prefixCount.push(new Array(maxValue + 1).fill(0)); // Initialize first row with zeros
10
11 // Populate prefix count array
12 for (let i = 0; i < arrayLength; i++) {
13 const currentNum: number = nums[i];
14 // Copy previous row's counts
15 prefixCount.push([...prefixCount[i]]);
16 // Increment count for current number
17 prefixCount[i + 1][currentNum]++;
18 }
19
20 const result: number[] = [];
21
22 // Process each query
23 for (const [leftIndex, rightIndex] of queries) {
24 let previousValue: number = -1; // Track the last seen value in range
25 let minDiff: number = Infinity; // Track minimum difference between consecutive values
26
27 // Check each possible value from 1 to maxValue
28 for (let value = 1; value <= maxValue; value++) {
29 // Check if this value exists in the query range [leftIndex, rightIndex]
30 const countInRange: number = prefixCount[rightIndex + 1][value] - prefixCount[leftIndex][value];
31
32 if (countInRange > 0) {
33 // If we have a previous value, calculate difference
34 if (previousValue !== -1) {
35 minDiff = Math.min(minDiff, value - previousValue);
36 }
37 previousValue = value;
38 }
39 }
40
41 // Add result: -1 if no valid difference found, otherwise the minimum difference
42 result.push(minDiff === Infinity ? -1 : minDiff);
43 }
44
45 return result;
46}
47
Time and Space Complexity
Time Complexity: O(m * 101 + n * 101)
which simplifies to O(m + n)
- Building the prefix sum array requires iterating through all
m
elements ofnums
, and for each element, we update 101 values (constant), resulting inO(m * 101) = O(m)
time. - Processing queries requires iterating through all
n
queries. For each query, we check all 101 possible values (constant) to find which values exist in the subarray and calculate the minimum difference, takingO(n * 101) = O(n)
time. - The overall time complexity is
O(m + n)
wherem
is the length ofnums
andn
is the number of queries.
Space Complexity: O(m * 101)
which simplifies to O(m)
- The prefix sum array
pre_sum
has dimensions(m + 1) × 101
, requiringO((m + 1) * 101) = O(m)
space. - The answer array
ans
storesn
results, requiringO(n)
space. - Since typically
m ≥ n
in practical scenarios and the prefix sum dominates, the overall space complexity isO(m)
. - Additional variables like
left
,right
,t
, andlast
use constantO(1)
space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Same Elements
A critical pitfall is misunderstanding that we need the minimum difference between distinct values, not between different positions of the same value.
Wrong Approach:
# Incorrect: Finding minimum difference between any two elements
min_diff = float('inf')
for i in range(left, right + 1):
for j in range(i + 1, right + 1):
min_diff = min(min_diff, abs(nums[i] - nums[j]))
This would incorrectly return 0
for [2, 5, 2]
by comparing the two 2's.
Correct Approach: Only compare elements with different values, which the prefix sum solution handles by tracking distinct values present in the range.
2. Off-by-One Errors in Index Handling
The queries use 0-based indexing, but the prefix sum array needs careful index management.
Wrong:
# Forgetting to adjust indices properly left, right = queries[i][0], queries[i][1] # Missing +1 for right count = prefix_count[right][value] - prefix_count[left][value] # Wrong!
Correct:
left_idx = queries[query_idx][0] right_idx = queries[query_idx][1] + 1 # Convert to exclusive right bound count = prefix_count[right_idx][value] - prefix_count[left_idx][value]
3. Memory Inefficiency with Large Value Ranges
If the value range wasn't constrained to [1, 100], using a fixed-size array would be wasteful or impossible.
Problematic for large ranges:
# If values could be up to 10^9, this would use excessive memory
prefix_count = [[0] * (10**9 + 1) for _ in range(n + 1)] # Memory error!
Solution for unconstrained values:
# Use dictionary-based approach for sparse values
from collections import defaultdict
def minDifference(nums, queries):
result = []
for left, right in queries:
# Collect unique values in subarray
unique_vals = sorted(set(nums[left:right+1]))
if len(unique_vals) < 2:
result.append(-1)
else:
min_diff = min(unique_vals[i+1] - unique_vals[i]
for i in range(len(unique_vals)-1))
result.append(min_diff)
return result
4. Not Handling Edge Cases Properly
Failing to return -1
when all elements in the subarray are identical.
Wrong:
# Forgetting to check if we found at least 2 distinct values
min_diff = float('inf')
for value in range(1, 101):
# ... find differences ...
result.append(min_diff) # Would append inf instead of -1
Correct:
if min_diff == float('inf'):
min_diff = -1 # Properly handle single distinct value case
result.append(min_diff)
5. Inefficient Query Processing Without Preprocessing
Processing each query independently without leveraging preprocessing leads to poor performance.
Inefficient O(n² × q) approach:
def minDifference(nums, queries):
result = []
for left, right in queries:
min_diff = float('inf')
for i in range(left, right + 1):
for j in range(i + 1, right + 1):
if nums[i] != nums[j]:
min_diff = min(min_diff, abs(nums[i] - nums[j]))
result.append(-1 if min_diff == float('inf') else min_diff)
return result
The prefix sum approach reduces this to O(100) per query after O(n × 100) preprocessing, which is much more efficient for multiple queries.
Which of the following is a min heap?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!