2817. Minimum Absolute Difference Between Elements With Constraint
Problem Description
In this problem, we're provided with an array of integers, nums
, and a separate integer x
. The objective is to find the smallest possible absolute difference between two elements of the array, given that these elements' indices are x
or more positions apart. More formally, we need to find indices i
and j
(where i
can be before or after j
) in such a way that abs(i - j) >= x
, while simultaneously minimizing the value of abs(nums[i] - nums[j])
.
The problem defines an absolute difference function abs
, which calculates the non-negative difference between two numbers. In this context, it's applied twice: first to ensure that the indices i
and j
are sufficiently separated by at least x
indices; second, to measure the difference in value between the array entries at those indices.
Intuition
The main intuition behind the solution is that to find the minimum absolute difference between elements that are at least x
indices apart, we don't need to compare each element with every other element that meets the index distance requirement. That would be inefficient, especially for large arrays. Instead, we can maintain a subset of the array elements that allows us to quickly find the closest elements in value to any given element in the array. We use a data structure that maintains the elements in sorted order and allows for efficient lookups - an ordered set.
The ordered set is implemented using SortedList
, which comes from the sortedcontainers
Python library. This data structure supports logarithmic time complexity for insertion, deletion, and lookup operations—a key benefit when dealing with a potentially large number of elements.
To implement the solution, we iterate over the array starting from index x
, for each element, adding the element that is exactly x
indices before it to the ordered set. Now, for every nums[i]
(where i >= x
), we only need to compare it with the closest values that are already in the ordered set. These closest values are the ones that could potentially yield the minimum absolute difference.
The bisect_left
function from the Python's bisect
module is used to find the position where nums[i]
would fit in the sorted list. This gives us two candidates: the element right before and right after the position found by bisect_left
. Compare nums[i]
with these two candidates, and the smaller difference is a potential answer. Update the answer if this difference is smaller than all previously seen differences.
This way, we efficiently narrow our search for the minimum absolute difference to just two comparisons per array element considered, using the ordered set to quickly find the best candidates for comparison.
Learn more about Binary Search patterns.
Solution Approach
The solution is implemented in Python and involves the use of the SortedList
data structure to maintain a dynamically ordered subset of the array elements for quick comparisons. This ordered set allows us to find the minimum difference efficiently.
Here's a step-by-step breakdown of how the algorithm works:
-
We initialize our ordered set
sl
by importingSortedList
. The set is initially empty because we populate it as we iterate through the array. -
We start with a variable
ans
initialized toinf
, representing the smallest absolute difference found so far.inf
is a placeholder for infinity, ensuring that any real absolute difference found will be smaller and thus will replaceinf
. -
We enumerate the elements of
nums
starting from indexx
(i.e., the first element that can have another element in the array which isx
indices apart is at indexx
). -
As we visit each element
nums[i]
wherei >= x
, we add the elementnums[i - x]
to our ordered setsl
. This is becausenums[i - x]
is exactlyx
indices beforenums[i]
, satisfying the distance constraint. -
We then use the
bisect_left
function from thebisect
module to find the position in the ordered set wherenums[i]
would fit based on its value. -
Two adjacent positions in the ordered set are considered:
- The position found by
bisect_left
(sl[j]
), which is the first element in the ordered set that is not less thannums[i]
. - The position immediately before (
sl[j - 1]
), which is the largest element in the ordered set that is less thannums[i]
(if it exists).
- The position found by
-
If
j
is less than the length of the ordered set, it means there's an element on its right; we calculate the difference between this element andnums[i]
, and if it's smaller thanans
, we updateans
. -
If
j
is not 0 (which means that there is an element in the set smaller thannums[i]
), we find the difference with the left neighborsl[j - 1]
and updateans
if this new difference is smaller. -
After iterating through all elements from index
x
to the end of the array, the variableans
holds the minimum absolute difference, and we return this value.
Through the use of SortedList
and the binary search function bisect_left
, this approach optimizes what could have been an O(n^2)
brute force comparison process into an O(n log n)
process, since each of the n - x
iterations takes O(log n)
due to operations on the ordered set.
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 walk through a small example to illustrate the solution approach.
Consider the array nums = [4, 2, 5, 1, 3]
and x = 2
. We want to find the smallest absolute difference between two elements where the indices are at least x
positions apart.
-
We begin by initializing
SortedList
assl
and settingans
toinf
. -
Now, we start iterating over the array from the index
x
, which is 2 in this case. So we look atnums[2]
which is5
. -
Before we consider
nums[2]
, we addnums[0]
(4
in this case) tosl
. Nowsl = [4]
. -
We then check for the closest elements in
sl
to5
. Sincesl
has only one value, we only need to check4
. The absolute differenceabs(5 - 4) = 1
, so we updateans
frominf
to1
. -
We move to the next index,
i = 3
. We addnums[1]
(2
) tosl
, so nowsl = [2, 4]
. -
nums[3]
is1
. Usingbisect_left
, we find that1
would be inserted at the beginning ofsl
, so we compare it to the first element (2
). The absolute differenceabs(1 - 2) = 1
. Ourans
is already1
, so it remains unchanged. -
Moving to
i = 4
, we addnums[2]
(5
) tosl
. Now,sl
becomes[2, 4, 5]
. -
nums[4]
is3
. Usingbisect_left
, we determine3
would fit between2
and4
insl
. Hence, we compare3
with both sides:abs(3 - 2) = 1
andabs(3 - 4) = 1
.ans
remains1
. -
Having checked all elements from index
x
to the end ofnums
, we conclude that the smallest absolute difference we found is1
, so we returnans
which is1
.
Thus, the minimum absolute difference between elements at least x
indices apart is 1
. This example demonstrates the efficacy of the algorithm in finding the solution with fewer comparisons, thanks to the ordered set sl
and binary search techniques.
Solution Implementation
1from sortedcontainers import SortedList
2from bisect import bisect_left
3
4class Solution:
5 def minAbsoluteDifference(self, nums, target):
6 # Create a SortedList to store the current window of numbers
7 sorted_list = SortedList()
8 # Initialize the answer with infinity, representing a large number
9 min_diff = float('inf')
10
11 # Start iterating from the target index to the end of the list
12 for i in range(target, len(nums)):
13 # Add the (current index - target)th element to maintain the window
14 sorted_list.add(nums[i - target])
15 # Find the index in the sorted list where nums[i] would fit
16 insert_pos = bisect_left(sorted_list, nums[i])
17
18 # If there is an element on the right, update min_diff
19 if insert_pos < len(sorted_list):
20 min_diff = min(min_diff, sorted_list[insert_pos] - nums[i])
21 # If there is an element on the left, update min_diff
22 if insert_pos > 0:
23 min_diff = min(min_diff, nums[i] - sorted_list[insert_pos - 1])
24
25 # Return the minimum absolute difference found
26 return min_diff
27
1class Solution {
2 public int minAbsoluteDifference(List<Integer> nums, int windowSize) {
3 // TreeMap to store the occurrences of the numbers in the current window
4 TreeMap<Integer, Integer> frequencyMap = new TreeMap<>();
5 // Initialize the answer with the maximum possible positive integer value
6 int minimumDifference = Integer.MAX_VALUE;
7
8 // Start from index windowSize, as we want to have a complete window
9 for (int i = windowSize; i < nums.size(); ++i) {
10 // Add or update the frequency of the starting element of the current window
11 frequencyMap.merge(nums.get(i - windowSize), 1, Integer::sum);
12 // Find the smallest number greater than or equal to the current number
13 Integer higherOrEqual = frequencyMap.ceilingKey(nums.get(i));
14 if (higherOrEqual != null) {
15 // If found, update the minimum difference
16 minimumDifference = Math.min(minimumDifference, higherOrEqual - nums.get(i));
17 }
18 // Find the greatest number less than or equal to the current number
19 Integer lowerOrEqual = frequencyMap.floorKey(nums.get(i));
20 if (lowerOrEqual != null) {
21 // If found, update the minimum difference
22 minimumDifference = Math.min(minimumDifference, nums.get(i) - lowerOrEqual);
23 }
24 }
25 // Return the found minimum absolute difference
26 return minimumDifference;
27 }
28}
29
1#include <vector>
2#include <set>
3#include <algorithm> // For std::min and std::max
4#include <climits> // For INT_MAX
5
6class Solution {
7public:
8 // Function to find the minimum absolute difference
9 int minAbsoluteDifference(vector<int>& nums, int x) {
10 // Initialize the answer with the maximum possible int value
11 int minAbsDiff = INT_MAX;
12
13 // Use a multiset to maintain a sorted window of the last 'x' elements
14 multiset<int> windowElements;
15
16 // Loop to calculate the minimum absolute difference
17 for (int i = x; i < nums.size(); ++i) {
18 // Insert elements into the window so it always contains 'x' elements
19 windowElements.insert(nums[i - x]);
20
21 // Find the lower_bound of the current number in our window
22 auto it = windowElements.lower_bound(nums[i]);
23
24 // If the iterator is not at the end, update the minimum difference
25 if (it != windowElements.end()) {
26 minAbsDiff = min(minAbsDiff, abs(*it - nums[i]));
27 }
28
29 // Check the predecessor of the current element if it exists
30 if (it != windowElements.begin()) {
31 --it; // Move the iterator to the left element
32 minAbsDiff = min(minAbsDiff, abs(nums[i] - *it));
33 }
34 }
35 // Return the minimum absolute difference found
36 return minAbsDiff;
37 }
38};
39
1module TreeMultiSetModule {
2 // Define a comparator type for comparing elements.
3 type Compare<T> = (lhs: T, rhs: T) => number;
4
5 // Red-Black Tree node class with generics support.
6 class RBTreeNode<T = number> {
7 data: T;
8 count: number;
9 left: RBTreeNode<T> | null;
10 right: RBTreeNode<T> | null;
11 parent: RBTreeNode<T> | null;
12 color: number;
13
14 constructor(data: T) {
15 this.data = data;
16 this.left = this.right = this.parent = null;
17 this.color = 0; // Use 0 for red, 1 for black.
18 this.count = 1; // Counter for duplicates.
19 }
20
21 // Helper function to get the sibling of the node.
22 sibling(): RBTreeNode<T> | null {
23 if (!this.parent) {
24 return null; // No sibling if no parent.
25 }
26 return this.isOnLeft() ? this.parent.right : this.parent.left;
27 }
28
29 // Check if the node is the left child of its parent.
30 isOnLeft(): boolean {
31 return this === this.parent!.left;
32 }
33
34 // Check if the node has at least one red child.
35 hasRedChild(): boolean {
36 return (
37 (this.left !== null && this.left.color === 0) ||
38 (this.right !== null && this.right.color === 0)
39 );
40 }
41 }
42
43 // Red-Black Tree class definition.
44 class RBTree<T> {
45 root: RBTreeNode<T> | null;
46 lt: (l: T, r: T) => boolean; // Function to compare two nodes.
47
48 constructor(compare: Compare<T> = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) {
49 this.root = null; // Root initially set to null.
50 this.lt = (l: T, r: T) => compare(l, r) < 0;
51 }
52
53 // Rotate the subtree to the left at the given node.
54 rotateLeft(pt: RBTreeNode<T>): void {
55 const right = pt.right!;
56 pt.right = right.left;
57
58 if (pt.right) {
59 pt.right.parent = pt;
60 }
61 right.parent = pt.parent;
62
63 if (!pt.parent) {
64 this.root = right;
65 } else if (pt === pt.parent.left) {
66 pt.parent.left = right;
67 } else {
68 pt.parent.right = right;
69 }
70
71 right.left = pt;
72 pt.parent = right;
73 }
74
75 // Rotate the subtree to the right at the given node.
76 rotateRight(pt: RBTreeNode<T>): void {
77 const left = pt.left!;
78 pt.left = left.right;
79
80 if (pt.left) {
81 pt.left.parent = pt;
82 }
83 left.parent = pt.parent;
84
85 if (!pt.parent) {
86 this.root = left;
87 } else if (pt === pt.parent.left) {
88 pt.parent.left = left;
89 } else {
90 pt.parent.right = left;
91 }
92
93 left.right = pt;
94 pt.parent = left;
95 }
96
97 // Swap colors between two nodes.
98 swapColor(p1: RBTreeNode<T>, p2: RBTreeNode<T>): void {
99 const tmp = p1.color;
100 p1.color = p2.color;
101 p2.color = tmp;
102 }
103
104 // Swap data between two nodes.
105 swapData(p1: RBTreeNode<T>, p2: RBTreeNode<T>): void {
106 const tmp = p1.data;
107 p1.data = p2.data;
108 p2.data = tmp;
109 }
110
111 // Additional RBTree methods would go here...
112 }
113
114 // TreeMultiSet class definition with generics support, using a Red-Black Tree.
115 class TreeMultiSet<T = number> {
116 private _size: number; // The number of elements in the multiset.
117 private tree: RBTree<T>; // The underlying Red-Black tree.
118 private compare: Compare<T>; // Comparator function for the elements.
119
120 constructor(
121 collection: T[] | Compare<T> = [],
122 compare: Compare<T> = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)
123 ) {
124 if (typeof collection === 'function') {
125 compare = collection;
126 collection = [];
127 }
128 this._size = 0;
129 this.compare = compare;
130 this.tree = new RBTree(compare);
131 for (const val of collection) {
132 this.add(val);
133 }
134 }
135
136 // Returns the size of the multiset.
137 size(): number {
138 return this._size;
139 }
140
141 // Checks if the multiset contains a value.
142 has(val: T): boolean {
143 return !!this.tree.find(val);
144 }
145
146 // Adds a value to the multiset.
147 add(val: T): boolean {
148 const successful = this.tree.insert(val);
149 if (successful) {
150 this._size++;
151 }
152 return successful;
153 }
154
155 // Deletes one occurrence of the value from the multiset.
156 delete(val: T): boolean {
157 const successful = this.tree.delete(val);
158 if (successful) {
159 this._size--;
160 }
161 return successful;
162 }
163
164 // Additional TreeMultiSet methods would go here...
165 }
166
167 // Export the TreeMultiSet class from the module.
168 export { TreeMultiSet };
169
170 // The rest of the TreeMultiSet class methods... (for brevity, not shown here)
171}
172
173// Export the module for use elsewhere.
174export = TreeMultiSetModule;
175
Time and Space Complexity
The time complexity of the provided code is O(n * log(x))
where n
is the length of the array nums
and x
is the window size given as a parameter. This is due to the fact that we are iterating through n - x
elements, and for each element, we perform operations like add
and bisect_left
which are O(log(x))
. Since x
is less than or equal to n
, in the worst case, the time complexity could be mistaken as O(n * log(n))
, but it is crucial to note that x
dictates the actual size of the sorted list, not n
.
The space complexity of the code is O(x)
as the space is used only for maintaining the sorted list with a maximum of x
elements at any time, rather than O(n)
as inferred in the reference. If x
were to be considered constant or a value that does not scale with n
, the space complexity could be considered as O(1)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which type of traversal does breadth first search do?
Recommended Readings
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!