1818. Minimum Absolute Sum Difference
Problem Description
You have two positive integer arrays nums1 and nums2, both with the same length n.
The absolute sum difference between these arrays is calculated by summing up |nums1[i] - nums2[i]| for all indices i from 0 to n-1.
Your task is to minimize this absolute sum difference by replacing at most one element in nums1. You can replace any element in nums1 with any other element that already exists in nums1.
For example, if nums1 = [1, 7, 5] and nums2 = [2, 3, 5]:
- The original absolute sum difference is
|1-2| + |7-3| + |5-5| = 1 + 4 + 0 = 5 - You could replace
nums1[1] = 7withnums1[0] = 1to get[1, 1, 5] - The new absolute sum difference would be
|1-2| + |1-3| + |5-5| = 1 + 2 + 0 = 3
Return the minimum possible absolute sum difference after performing at most one replacement. Since the answer might be very large, return it modulo 10^9 + 7.
The absolute value |x| is defined as:
xifx >= 0-xifx < 0
Intuition
The key insight is that we want to find the single replacement that gives us the maximum reduction in the total sum.
Let's think about what happens when we replace nums1[i] with some other value from nums1. The only term in the total sum that changes is |nums1[i] - nums2[i]|. If we replace nums1[i] with a different value x (where x must already exist in nums1), this term becomes |x - nums2[i]|.
The reduction we get from this replacement is:
original difference - new difference = |nums1[i] - nums2[i]| - |x - nums2[i]|
To maximize our reduction, we want to find the value x in nums1 that minimizes |x - nums2[i]|. In other words, we want to find the element in nums1 that is closest to nums2[i].
This naturally leads us to a binary search approach:
- First, calculate the total absolute sum difference without any replacements
- For each position
i, find the element innums1that is closest tonums2[i] - Calculate how much we can reduce the sum by making this replacement
- Track the maximum possible reduction across all positions
- Subtract this maximum reduction from the original sum
To efficiently find the closest element, we can sort a copy of nums1 and use binary search. For each nums2[i], we use bisect_left to find where nums2[i] would fit in the sorted array. The closest element must be either at this position or the position just before it.
The final answer is (original_sum - max_reduction) % mod.
Learn more about Binary Search and Sorting patterns.
Solution Implementation
1class Solution:
2 def minAbsoluteSumDiff(self, nums1: List[int], nums2: List[int]) -> int:
3 MOD = 10**9 + 7
4
5 # Create a sorted copy of nums1 for binary search
6 sorted_nums1 = sorted(nums1)
7 n = len(sorted_nums1)
8
9 # Calculate initial total absolute difference sum
10 total_diff = sum(abs(num1 - num2) for num1, num2 in zip(nums1, nums2)) % MOD
11
12 def find_first_ge(target: int) -> int:
13 """Find the first index where sorted_nums1[index] >= target using template."""
14 left, right = 0, n - 1
15 first_true_index = -1
16
17 while left <= right:
18 mid = (left + right) // 2
19 if sorted_nums1[mid] >= target:
20 first_true_index = mid
21 right = mid - 1
22 else:
23 left = mid + 1
24
25 return first_true_index
26
27 # Track maximum reduction possible by replacing one element
28 max_reduction = 0
29
30 for num1, num2 in zip(nums1, nums2):
31 current_diff = abs(num1 - num2)
32 min_possible_diff = float('inf')
33
34 # Find first element >= num2
35 idx = find_first_ge(num2)
36
37 # Check the value at or after num2
38 if idx != -1:
39 min_possible_diff = min(min_possible_diff, abs(sorted_nums1[idx] - num2))
40
41 # Check the value before num2
42 if idx == -1:
43 # All elements are < num2, check the last one
44 min_possible_diff = min(min_possible_diff, abs(sorted_nums1[n - 1] - num2))
45 elif idx > 0:
46 min_possible_diff = min(min_possible_diff, abs(sorted_nums1[idx - 1] - num2))
47
48 reduction = current_diff - min_possible_diff
49 max_reduction = max(max_reduction, reduction)
50
51 return (total_diff - max_reduction + MOD) % MOD
521class Solution {
2 public int minAbsoluteSumDiff(int[] nums1, int[] nums2) {
3 final int MOD = (int) 1e9 + 7;
4
5 int[] sortedNums1 = nums1.clone();
6 Arrays.sort(sortedNums1);
7 int n = nums1.length;
8
9 int totalSum = 0;
10 for (int i = 0; i < n; i++) {
11 totalSum = (totalSum + Math.abs(nums1[i] - nums2[i])) % MOD;
12 }
13
14 int maxReduction = 0;
15 for (int i = 0; i < n; i++) {
16 int originalDiff = Math.abs(nums1[i] - nums2[i]);
17 int minPossibleDiff = Integer.MAX_VALUE;
18
19 // Find first index where sortedNums1[index] >= nums2[i]
20 int idx = findFirstGe(sortedNums1, nums2[i]);
21
22 // Check the element at or after nums2[i]
23 if (idx != -1) {
24 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[idx] - nums2[i]));
25 }
26
27 // Check the element before nums2[i]
28 if (idx == -1) {
29 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[n - 1] - nums2[i]));
30 } else if (idx > 0) {
31 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[idx - 1] - nums2[i]));
32 }
33
34 maxReduction = Math.max(maxReduction, originalDiff - minPossibleDiff);
35 }
36
37 return (totalSum - maxReduction + MOD) % MOD;
38 }
39
40 private int findFirstGe(int[] nums, int target) {
41 int left = 0;
42 int right = nums.length - 1;
43 int firstTrueIndex = -1;
44
45 while (left <= right) {
46 int mid = left + (right - left) / 2;
47 if (nums[mid] >= target) {
48 firstTrueIndex = mid;
49 right = mid - 1;
50 } else {
51 left = mid + 1;
52 }
53 }
54
55 return firstTrueIndex;
56 }
57}
581class Solution {
2public:
3 int minAbsoluteSumDiff(vector<int>& nums1, vector<int>& nums2) {
4 const int MOD = 1e9 + 7;
5
6 vector<int> sortedNums1(nums1);
7 sort(sortedNums1.begin(), sortedNums1.end());
8 int n = nums1.size();
9
10 int totalSum = 0;
11 for (int i = 0; i < n; ++i) {
12 totalSum = (totalSum + abs(nums1[i] - nums2[i])) % MOD;
13 }
14
15 auto findFirstGe = [&](int target) -> int {
16 int left = 0;
17 int right = n - 1;
18 int firstTrueIndex = -1;
19
20 while (left <= right) {
21 int mid = left + (right - left) / 2;
22 if (sortedNums1[mid] >= target) {
23 firstTrueIndex = mid;
24 right = mid - 1;
25 } else {
26 left = mid + 1;
27 }
28 }
29
30 return firstTrueIndex;
31 };
32
33 int maxReduction = 0;
34 for (int i = 0; i < n; ++i) {
35 int currentDiff = abs(nums1[i] - nums2[i]);
36 int minPossibleDiff = INT_MAX;
37
38 int idx = findFirstGe(nums2[i]);
39
40 if (idx != -1) {
41 minPossibleDiff = min(minPossibleDiff, abs(sortedNums1[idx] - nums2[i]));
42 }
43
44 if (idx == -1) {
45 minPossibleDiff = min(minPossibleDiff, abs(sortedNums1[n - 1] - nums2[i]));
46 } else if (idx > 0) {
47 minPossibleDiff = min(minPossibleDiff, abs(sortedNums1[idx - 1] - nums2[i]));
48 }
49
50 maxReduction = max(maxReduction, currentDiff - minPossibleDiff);
51 }
52
53 return (totalSum - maxReduction + MOD) % MOD;
54 }
55};
561function minAbsoluteSumDiff(nums1: number[], nums2: number[]): number {
2 const MOD = 10 ** 9 + 7;
3
4 const sortedNums1 = [...nums1];
5 sortedNums1.sort((a, b) => a - b);
6 const n = nums1.length;
7
8 let totalSum = 0;
9 for (let i = 0; i < n; ++i) {
10 totalSum = (totalSum + Math.abs(nums1[i] - nums2[i])) % MOD;
11 }
12
13 const findFirstGe = (target: number): number => {
14 let left = 0;
15 let right = n - 1;
16 let firstTrueIndex = -1;
17
18 while (left <= right) {
19 const mid = Math.floor((left + right) / 2);
20 if (sortedNums1[mid] >= target) {
21 firstTrueIndex = mid;
22 right = mid - 1;
23 } else {
24 left = mid + 1;
25 }
26 }
27
28 return firstTrueIndex;
29 };
30
31 let maxReduction = 0;
32
33 for (let i = 0; i < n; ++i) {
34 const originalDiff = Math.abs(nums1[i] - nums2[i]);
35 let minPossibleDiff = 1 << 30;
36
37 const idx = findFirstGe(nums2[i]);
38
39 if (idx !== -1) {
40 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[idx] - nums2[i]));
41 }
42
43 if (idx === -1) {
44 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[n - 1] - nums2[i]));
45 } else if (idx > 0) {
46 minPossibleDiff = Math.min(minPossibleDiff, Math.abs(sortedNums1[idx - 1] - nums2[i]));
47 }
48
49 maxReduction = Math.max(maxReduction, originalDiff - minPossibleDiff);
50 }
51
52 return (totalSum - maxReduction + MOD) % MOD;
53}
54Solution Approach
The solution uses sorting and the binary search template to find the optimal replacement.
Step 1: Initial Setup
- Create a sorted copy of
nums1for binary searching - Calculate the initial absolute sum difference
Step 2: Binary Search Template for Finding Closest Element
We use the template to find the first element >= target:
def find_first_ge(target: int) -> int:
left, right = 0, n - 1
first_true_index = -1
while left <= right:
mid = (left + right) // 2
if sorted_nums1[mid] >= target:
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
The feasibility condition sorted_nums1[mid] >= target creates the pattern:
- Elements before
target: returnfalse - Elements at or after
target: returntrue
Step 3: Find Optimal Replacement for Each Position
For each pair (num1, num2):
- Calculate the current difference:
current_diff = abs(num1 - num2) - Use
find_first_ge(num2)to find the first element >= num2 - Check two candidates for the closest value:
- If
idx != -1: checksorted_nums1[idx](first element >= num2) - If
idx == -1: all elements < num2, check the last element - If
idx > 0: checksorted_nums1[idx-1](element just before)
- If
- Track the maximum possible reduction
Step 4: Calculate Final Answer
Return (total_diff - max_reduction + MOD) % MOD
Time Complexity: O(n log n) for sorting and n binary searches
Space Complexity: O(n) for storing the sorted copy of nums1
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 a concrete example to illustrate the solution approach.
Given:
nums1 = [5, 10, 3]nums2 = [7, 2, 8]
Step 1: Initial Setup
- Create sorted copy:
nums = [3, 5, 10] - Calculate initial sum:
|5-7| + |10-2| + |3-8| = 2 + 8 + 5 = 15
- Initialize
mx = 0(maximum reduction tracker)
Step 2: Find Optimal Replacement for Each Position
Position 0: (a=5, b=7)
- Current difference:
d1 = |5-7| = 2 - Find closest value to 7 in sorted
nums = [3, 5, 10]:bisect_left([3, 5, 10], 7)returns index 2- Check candidate at index 2:
nums[2] = 10, difference =|10-7| = 3 - Check candidate at index 1:
nums[1] = 5, difference =|5-7| = 2
- Best replacement gives
d2 = 2 - Reduction:
d1 - d2 = 2 - 2 = 0 - Update:
mx = max(0, 0) = 0
Position 1: (a=10, b=2)
- Current difference:
d1 = |10-2| = 8 - Find closest value to 2 in sorted
nums = [3, 5, 10]:bisect_left([3, 5, 10], 2)returns index 0- Check candidate at index 0:
nums[0] = 3, difference =|3-2| = 1 - No candidate before index 0
- Best replacement gives
d2 = 1 - Reduction:
d1 - d2 = 8 - 1 = 7 - Update:
mx = max(0, 7) = 7
Position 2: (a=3, b=8)
- Current difference:
d1 = |3-8| = 5 - Find closest value to 8 in sorted
nums = [3, 5, 10]:bisect_left([3, 5, 10], 8)returns index 2- Check candidate at index 2:
nums[2] = 10, difference =|10-8| = 2 - Check candidate at index 1:
nums[1] = 5, difference =|5-8| = 3
- Best replacement gives
d2 = 2 - Reduction:
d1 - d2 = 5 - 2 = 3 - Update:
mx = max(7, 3) = 7
Step 3: Calculate Final Answer
- Original sum: 15
- Maximum reduction: 7
- Final answer:
15 - 7 = 8
The optimal replacement is at position 1, where we replace nums1[1] = 10 with 3 (from nums1[2]), giving us the array [5, 3, 3] and reducing the total sum from 15 to 8.
Time and Space Complexity
The time complexity is O(n × log n), where n is the length of the array nums1.
- Sorting
nums1takesO(n × log n)time - Computing the initial sum takes
O(n)time - The main loop iterates
ntimes, and for each iteration:bisect_leftperforms binary search on the sorted array, takingO(log n)time- All other operations inside the loop take
O(1)time
- Therefore, the loop contributes
O(n × log n)time - Overall time complexity:
O(n × log n) + O(n) + O(n × log n) = O(n × log n)
The space complexity is O(n).
- Creating a sorted copy of
nums1requiresO(n)additional space - All other variables (
s,mx,d1,d2,i) useO(1)space - Overall space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
Pitfall: Using while left < right with right = mid instead of the standard template.
Wrong Implementation:
while left < right: mid = (left + right) >> 1 if nums[mid] >= target: right = mid else: left = mid + 1 return left
Correct Implementation (using template):
left, right = 0, n - 1 first_true_index = -1 while left <= right: mid = (left + right) // 2 if sorted_nums1[mid] >= target: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
2. Modulo Operation Handling Error
Pitfall: Simply doing (total_diff - max_reduction) % MOD can produce issues.
Solution: Add MOD before taking the modulo:
return (total_diff - max_reduction + MOD) % MOD
3. Not Checking Both Neighbors in Binary Search
Pitfall: Only checking one candidate value after binary search, missing the optimal replacement.
Example:
sorted_nums1 = [1, 5, 10]andnum2 = 6- Template returns
first_true_index = 2(element 10 >= 6) - Only checking index 2 gives difference 4
- But checking index 1 gives difference 1 (better!)
Solution: Check both first_true_index and first_true_index - 1 when they exist.
4. Handling first_true_index = -1
Pitfall: Not handling the case when all elements are less than the target.
Solution: When first_true_index == -1, the closest element must be the last one:
if idx == -1:
min_possible_diff = min(min_possible_diff, abs(sorted_nums1[n - 1] - num2))
Which two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Don’t Miss This!