Facebook Pixel

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 to k1 times total
  • You can modify elements in nums2 by adding or subtracting 1, up to k2 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 Evaluator

Example 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. Update right = 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. Update left = 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. Update left = 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) where n is the length of nums1/nums2
  • Finding the sum of d: O(n)
  • Binary search with left and right 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)))
  • First loop to update d with left 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 (not mid_target - 1) when the target is achievable
  • min_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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More