2333. Minimum Sum of Squared Difference
Problem Description
You have two arrays nums1
and nums2
, both containing positive integers and having the same length n
. The sum of squared difference between these arrays is calculated by taking the difference between corresponding elements at each position, squaring that difference, and then summing all these squared values. Mathematically, this is: (nums1[0] - nums2[0])² + (nums1[1] - nums2[1])² + ... + (nums1[n-1] - nums2[n-1])²
.
You're given two additional positive integers k1
and k2
that represent modification budgets:
- You can modify elements in
nums1
by adding or subtracting 1, up tok1
times total - You can modify elements in
nums2
by adding or subtracting 1, up tok2
times total
Each modification operation changes a single element by exactly 1 (either +1 or -1). You can apply multiple modifications to the same element, and elements can become negative after modifications.
Your task is to minimize the sum of squared differences by strategically using your available modifications. Return the minimum possible sum of squared differences after using at most k1
modifications on nums1
and at most k2
modifications on nums2
.
For example, if nums1 = [1, 2, 3]
and nums2 = [4, 2, 1]
, the initial differences are [-3, 0, 2]
and the sum of squared differences is 9 + 0 + 4 = 13
. With modifications, you could reduce these differences to minimize the final result.
Intuition
The key insight is that modifying nums1[i]
by +1 or -1, or modifying nums2[i]
by +1 or -1, both have the same effect: they change the absolute difference |nums1[i] - nums2[i]|
by 1. This means we can combine both modification budgets into a single budget k = k1 + k2
and think about reducing the absolute differences directly.
Since we want to minimize the sum of squared differences, and squaring amplifies larger values more than smaller ones, we should prioritize reducing the largest differences first. For example, reducing a difference from 5 to 4 saves us 25 - 16 = 9
in the squared sum, while reducing from 2 to 1 only saves us 4 - 1 = 3
.
The problem then becomes: given an array of absolute differences and k
operations, how can we minimize the sum of squares? The greedy approach would be to repeatedly reduce the largest difference by 1, but this could be inefficient for large k
.
A more efficient approach uses binary search. We can search for the smallest possible maximum value in the final difference array. The idea is: "What's the smallest value x
such that we can make all differences at most x
using at most k
operations?"
For any candidate value mid
, we can calculate how many operations are needed to bring all differences down to at most mid
. If this requires more than k
operations, we need a larger target value. If it requires at most k
operations, we might be able to go even smaller.
Once we find this optimal maximum value, we first bring all differences down to this value. If we still have operations left, we distribute them evenly among the elements at this maximum value, reducing them by 1 each, until we run out of operations. This ensures we're always reducing the largest remaining values, which gives us the optimal result.
Learn more about Greedy, Binary Search, Sorting and Heap (Priority Queue) patterns.
Solution Approach
First, we calculate the absolute differences between corresponding elements and store them in array d
. Since modifying either array has the same effect on the difference, we combine both budgets: k = k1 + k2
.
If the sum of all differences is less than or equal to k
, we can reduce all differences to 0, so we return 0 immediately.
Binary Search for Optimal Maximum:
We use binary search to find the smallest possible maximum value that all differences can be reduced to within our budget. The search range is from 0
to max(d)
.
For each candidate value mid
, we check if it's achievable by calculating: sum(max(v - mid, 0) for v in d)
. This gives us the total operations needed to bring all values down to at most mid
. If this sum is less than or equal to k
, we can achieve this target, so we search for a potentially smaller value (right = mid
). Otherwise, we need a larger target (left = mid + 1
).
Apply the Reductions:
After finding the optimal maximum value left
, we first reduce all differences to at most this value:
for i, v in enumerate(d):
d[i] = min(left, v)
k -= max(0, v - left)
This step uses up some of our operations, and k
now contains the remaining operations.
Distribute Remaining Operations:
If we still have operations left (k > 0
), we need to distribute them among the elements that are currently at the maximum value left
. We iterate through the array and reduce each element at value left
by 1, one at a time, until we run out of operations:
for i, v in enumerate(d):
if k == 0:
break
if v == left:
k -= 1
d[i] -= 1
This ensures we're always reducing the largest values first, which is optimal for minimizing the sum of squares.
Calculate Final Result:
Finally, we calculate and return the sum of squared differences: sum(v * v for v in d)
.
The time complexity is O(n log(max(d)))
for the binary search, where each check takes O(n)
time. The space complexity is O(n)
for storing the differences 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 a concrete example with nums1 = [10, 10, 10]
, nums2 = [3, 3, 3]
, k1 = 6
, and k2 = 6
.
Step 1: Calculate initial differences
- Differences:
d = [|10-3|, |10-3|, |10-3|] = [7, 7, 7]
- Combined budget:
k = k1 + k2 = 12
- Initial sum of squared differences:
7² + 7² + 7² = 147
Step 2: Check if we can reduce everything to 0
- Sum of differences:
7 + 7 + 7 = 21
- Since
21 > 12
, we cannot reduce all differences to 0
Step 3: Binary search for optimal maximum
- Search range:
left = 0
,right = 7
Iteration 1:
mid = (0 + 7) // 2 = 3
- Operations needed to reduce all values to at most 3:
(7-3) + (7-3) + (7-3) = 12
- Since
12 ≤ 12
, we can achieve this. Updateright = 3
Iteration 2:
mid = (0 + 3) // 2 = 1
- Operations needed to reduce all values to at most 1:
(7-1) + (7-1) + (7-1) = 18
- Since
18 > 12
, we cannot achieve this. Updateleft = 2
Iteration 3:
mid = (2 + 3) // 2 = 2
- Operations needed to reduce all values to at most 2:
(7-2) + (7-2) + (7-2) = 15
- Since
15 > 12
, we cannot achieve this. Updateleft = 3
Iteration 4:
left = 3
,right = 3
, search complete
Step 4: Apply reductions to reach maximum of 3
- Reduce each difference from 7 to 3:
d = [3, 3, 3]
- Operations used:
(7-3) + (7-3) + (7-3) = 12
- Remaining operations:
k = 12 - 12 = 0
Step 5: Distribute remaining operations
- Since
k = 0
, no additional operations to distribute
Step 6: Calculate final result
- Final differences:
[3, 3, 3]
- Sum of squared differences:
3² + 3² + 3² = 27
The algorithm successfully reduced the sum of squared differences from 147 to 27 by strategically using all 12 available operations to bring each difference down to 3.
Solution Implementation
1class Solution:
2 def minSumSquareDiff(
3 self, nums1: List[int], nums2: List[int], k1: int, k2: int
4 ) -> int:
5 # Calculate absolute differences between corresponding elements
6 differences = [abs(a - b) for a, b in zip(nums1, nums2)]
7
8 # Total operations available (both k1 and k2 can be used interchangeably)
9 total_operations = k1 + k2
10
11 # If we have enough operations to reduce all differences to zero
12 if sum(differences) <= total_operations:
13 return 0
14
15 # Binary search to find the minimum maximum difference after operations
16 # We want to find the smallest value such that we can reduce all differences
17 # to at most this value using our available operations
18 min_target = 0
19 max_target = max(differences)
20
21 while min_target < max_target:
22 mid_target = (min_target + max_target) // 2
23
24 # Calculate operations needed to reduce all values to at most mid_target
25 operations_needed = sum(max(diff - mid_target, 0) for diff in differences)
26
27 if operations_needed <= total_operations:
28 # We can achieve mid_target with available operations
29 max_target = mid_target
30 else:
31 # We need more operations than available
32 min_target = mid_target + 1
33
34 # After binary search, min_target is the optimal threshold
35 optimal_threshold = min_target
36
37 # Reduce all differences to at most the optimal threshold
38 for i, diff in enumerate(differences):
39 operations_used = max(0, diff - optimal_threshold)
40 differences[i] = min(optimal_threshold, diff)
41 total_operations -= operations_used
42
43 # Distribute remaining operations to further reduce values at the threshold
44 # We can only reduce values that are currently at the threshold level
45 for i, diff in enumerate(differences):
46 if total_operations == 0:
47 break
48 if diff == optimal_threshold:
49 total_operations -= 1
50 differences[i] -= 1
51
52 # Calculate and return the sum of squared differences
53 return sum(diff * diff for diff in differences)
54
1class Solution {
2 public long minSumSquareDiff(int[] nums1, int[] nums2, int k1, int k2) {
3 int n = nums1.length;
4 int[] differences = new int[n];
5 long totalSum = 0;
6 int maxDifference = 0;
7 int totalOperations = k1 + k2;
8
9 // Calculate absolute differences and find the maximum difference
10 for (int i = 0; i < n; i++) {
11 differences[i] = Math.abs(nums1[i] - nums2[i]);
12 totalSum += differences[i];
13 maxDifference = Math.max(maxDifference, differences[i]);
14 }
15
16 // If we can reduce all differences to 0, return 0
17 if (totalSum <= totalOperations) {
18 return 0;
19 }
20
21 // Binary search to find the optimal threshold value
22 // All differences should be reduced to at most this threshold
23 int left = 0;
24 int right = maxDifference;
25 while (left < right) {
26 int mid = (left + right) >> 1;
27 long operationsNeeded = 0;
28
29 // Calculate operations needed to reduce all values to at most mid
30 for (int value : differences) {
31 operationsNeeded += Math.max(value - mid, 0);
32 }
33
34 if (operationsNeeded <= totalOperations) {
35 right = mid;
36 } else {
37 left = mid + 1;
38 }
39 }
40
41 // Reduce all differences greater than threshold to the threshold value
42 for (int i = 0; i < n; i++) {
43 totalOperations -= Math.max(0, differences[i] - left);
44 differences[i] = Math.min(differences[i], left);
45 }
46
47 // Use remaining operations to further reduce values at the threshold
48 // This distributes the remaining operations optimally
49 for (int i = 0; i < n && totalOperations > 0; i++) {
50 if (differences[i] == left) {
51 totalOperations--;
52 differences[i]--;
53 }
54 }
55
56 // Calculate the sum of squares of the final differences
57 long sumOfSquares = 0;
58 for (int value : differences) {
59 sumOfSquares += (long) value * value;
60 }
61
62 return sumOfSquares;
63 }
64}
65
1using ll = long long;
2
3class Solution {
4public:
5 long long minSumSquareDiff(vector<int>& nums1, vector<int>& nums2, int k1, int k2) {
6 int n = nums1.size();
7 vector<int> differences(n);
8 ll totalSum = 0;
9 int maxDiff = 0;
10 int totalOperations = k1 + k2;
11
12 // Calculate absolute differences and find maximum difference
13 for (int i = 0; i < n; ++i) {
14 differences[i] = abs(nums1[i] - nums2[i]);
15 totalSum += differences[i];
16 maxDiff = max(maxDiff, differences[i]);
17 }
18
19 // If we can reduce all differences to 0, return 0
20 if (totalSum <= totalOperations) {
21 return 0;
22 }
23
24 // Binary search to find the optimal threshold
25 // All differences should be reduced to at most this threshold
26 int left = 0, right = maxDiff;
27 while (left < right) {
28 int mid = (left + right) >> 1;
29 ll operationsNeeded = 0;
30
31 // Calculate operations needed to reduce all values to mid
32 for (int val : differences) {
33 operationsNeeded += max(val - mid, 0);
34 }
35
36 if (operationsNeeded <= totalOperations) {
37 right = mid;
38 } else {
39 left = mid + 1;
40 }
41 }
42
43 // Reduce all differences greater than threshold to threshold
44 for (int i = 0; i < n; ++i) {
45 int reduction = max(0, differences[i] - left);
46 totalOperations -= reduction;
47 differences[i] = min(differences[i], left);
48 }
49
50 // Use remaining operations to further reduce values at threshold
51 // This distributes remaining operations evenly
52 for (int i = 0; i < n && totalOperations > 0; ++i) {
53 if (differences[i] == left) {
54 --totalOperations;
55 --differences[i];
56 }
57 }
58
59 // Calculate the sum of squares of final differences
60 ll result = 0;
61 for (int val : differences) {
62 result += 1ll * val * val;
63 }
64
65 return result;
66 }
67};
68
1type ll = number;
2
3function minSumSquareDiff(nums1: number[], nums2: number[], k1: number, k2: number): number {
4 const n: number = nums1.length;
5 const differences: number[] = new Array(n);
6 let totalSum: ll = 0;
7 let maxDiff: number = 0;
8 let totalOperations: number = k1 + k2;
9
10 // Calculate absolute differences and find maximum difference
11 for (let i = 0; i < n; i++) {
12 differences[i] = Math.abs(nums1[i] - nums2[i]);
13 totalSum += differences[i];
14 maxDiff = Math.max(maxDiff, differences[i]);
15 }
16
17 // If we can reduce all differences to 0, return 0
18 if (totalSum <= totalOperations) {
19 return 0;
20 }
21
22 // Binary search to find the optimal threshold
23 // All differences should be reduced to at most this threshold
24 let left: number = 0;
25 let right: number = maxDiff;
26 while (left < right) {
27 const mid: number = Math.floor((left + right) / 2);
28 let operationsNeeded: ll = 0;
29
30 // Calculate operations needed to reduce all values to mid
31 for (const val of differences) {
32 operationsNeeded += Math.max(val - mid, 0);
33 }
34
35 if (operationsNeeded <= totalOperations) {
36 right = mid;
37 } else {
38 left = mid + 1;
39 }
40 }
41
42 // Reduce all differences greater than threshold to threshold
43 for (let i = 0; i < n; i++) {
44 const reduction: number = Math.max(0, differences[i] - left);
45 totalOperations -= reduction;
46 differences[i] = Math.min(differences[i], left);
47 }
48
49 // Use remaining operations to further reduce values at threshold
50 // This distributes remaining operations evenly
51 for (let i = 0; i < n && totalOperations > 0; i++) {
52 if (differences[i] === left) {
53 totalOperations--;
54 differences[i]--;
55 }
56 }
57
58 // Calculate the sum of squares of final differences
59 let result: ll = 0;
60 for (const val of differences) {
61 result += val * val;
62 }
63
64 return result;
65}
66
Time and Space Complexity
Time Complexity: O(n * log(max(d)))
The algorithm breaks down as follows:
- Creating the difference array
d
:O(n)
wheren
is the length of nums1/nums2 - Finding the sum of
d
:O(n)
- Binary search with
left
andright
bounds:O(log(max(d)))
iterations- Within each binary search iteration, calculating
sum(max(v - mid, 0) for v in d)
:O(n)
- Total for binary search:
O(n * log(max(d)))
- Within each binary search iteration, calculating
- First loop to update
d
withleft
value:O(n)
- Second loop to decrement values equal to
left
:O(n)
in worst case - Final sum calculation:
O(n)
The dominant term is O(n * log(max(d)))
from the binary search phase.
Space Complexity: O(n)
The space usage includes:
- Array
d
storing the absolute differences:O(n)
- Other variables (
k
,left
,right
,mid
):O(1)
The algorithm uses O(n)
extra space for storing the differences array, with no additional data structures that scale with input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Distribution of Remaining Operations
The Pitfall: After reducing all differences to the optimal threshold, there may be leftover operations. A common mistake is to arbitrarily distribute these operations or apply them all to a single element, which doesn't minimize the sum of squares optimally.
Why It's Wrong: Consider differences [3, 3, 3]
with 2 remaining operations. If you reduce only the first element to get [1, 3, 3]
, the sum of squares is 1 + 9 + 9 = 19
. But if you reduce two elements by 1 each to get [2, 2, 3]
, the sum is 4 + 4 + 9 = 17
, which is better.
The Solution: Always reduce the largest values first, one at a time. In the code, this is handled by:
for i, diff in enumerate(differences):
if total_operations == 0:
break
if diff == optimal_threshold:
total_operations -= 1
differences[i] -= 1
This ensures we're spreading the reductions across multiple elements at the maximum value, rather than concentrating them.
2. Off-by-One Error in Binary Search
The Pitfall: Using while min_target <= max_target:
instead of while min_target < max_target:
can cause an infinite loop or incorrect results when updating boundaries.
Why It's Wrong: With <=
, when min_target
equals max_target
, the loop continues unnecessarily. If you then set min_target = mid_target + 1
or max_target = mid_target - 1
, you might skip the optimal value or create inconsistent bounds.
The Solution: Use while min_target < max_target:
and update boundaries as:
max_target = mid_target
(notmid_target - 1
) when the target is achievablemin_target = mid_target + 1
when it's not achievable
This ensures convergence to the exact optimal threshold without missing values.
3. Not Handling the Case Where All Differences Can Be Reduced to Zero
The Pitfall: Forgetting to check if sum(differences) <= total_operations
at the beginning, leading to unnecessary computation and potentially incorrect results.
Why It's Wrong: If you have enough operations to eliminate all differences completely, the minimum sum of squares is always 0. Without this check, the binary search might find a non-zero threshold, and you'd still have unused operations after the algorithm completes.
The Solution: Always check this condition first:
if sum(differences) <= total_operations:
return 0
This provides an early exit for cases where complete elimination is possible, improving both correctness and efficiency.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!