Facebook Pixel

1674. Minimum Moves to Make Array Complementary

Problem Description

You have an integer array nums with an even length n and an integer limit. You can perform moves where each move allows you to replace any integer in nums with another integer between 1 and limit (inclusive).

The goal is to make the array complementary. An array is complementary when for every index i (0-indexed), the sum nums[i] + nums[n - 1 - i] equals the same value across all pairs.

In other words, you need to make it so that:

  • nums[0] + nums[n-1] equals some value s
  • nums[1] + nums[n-2] equals the same value s
  • nums[2] + nums[n-3] equals the same value s
  • And so on for all pairs

For example, the array [1,2,3,4] is complementary because:

  • nums[0] + nums[3] = 1 + 4 = 5
  • nums[1] + nums[2] = 2 + 3 = 5

All pairs sum to 5, so the array is complementary.

Your task is to find the minimum number of moves (replacements) required to make the array complementary.

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

Intuition

The key insight is that we need to choose a target sum s that all pairs must equal to, and then count how many replacements are needed to achieve this sum for each pair.

For each pair (nums[i], nums[n-1-i]), let's call them x and y where x ≤ y. We need to understand how many replacements are required for different possible target sums:

  1. Current sum (x + y): If we choose s = x + y, no replacements are needed for this pair.

  2. One replacement possible: If we replace just one number in the pair:

    • Replacing x with something between 1 and limit gives us sums in range [1 + y, limit + y]
    • Replacing y with something between 1 and limit gives us sums in range [x + 1, x + limit]
    • The overlap of these ranges where exactly one replacement works is [x + 1, y + limit]
  3. Two replacements needed: For sums outside the one-replacement range, we need to replace both numbers. This happens when:

    • s < x + 1 (sum too small, even replacing the smaller number with 1 isn't enough)
    • s > y + limit (sum too large, even replacing the larger number with limit isn't enough)

Instead of trying every possible sum s from 2 to 2 * limit and counting replacements for each, we can use a difference array to efficiently track how many replacements each pair needs across all possible sums.

For each pair, we mark ranges of sums with their replacement costs:

  • [2, x]: needs 2 replacements
  • [x + 1, x + y - 1]: needs 1 replacement
  • [x + y, x + y]: needs 0 replacements
  • [x + y + 1, y + limit]: needs 1 replacement
  • [y + limit + 1, 2 * limit]: needs 2 replacements

By using a difference array to accumulate these costs for all pairs simultaneously, we can then find the sum s that minimizes the total number of replacements across all pairs.

Learn more about Prefix Sum patterns.

Solution Approach

We use a difference array to efficiently track the number of replacements needed for each possible target sum across all pairs.

Step 1: Initialize the difference array

  • Create a difference array d of size 2 * limit + 2 to cover all possible sums from 2 to 2 * limit.
  • This array will accumulate the replacement costs for all pairs.

Step 2: Process each pair For each pair (nums[i], nums[n-1-i]) where i goes from 0 to n//2 - 1:

  • Let x = min(nums[i], nums[n-1-i]) and y = max(nums[i], nums[n-1-i])
  • We need to update the difference array to reflect the replacement costs for this pair across different sum ranges.

Step 3: Update difference array for each range Using the difference array technique, we mark the start and end of each cost range:

  • Range [2, x]: Needs 2 replacements

    • d[2] += 2 (start adding 2 at index 2)
    • d[x + 1] -= 2 (stop adding 2 after index x)
  • Range [x + 1, x + y - 1]: Needs 1 replacement

    • d[x + 1] += 1 (start adding 1 at index x+1)
    • d[x + y] -= 1 (stop adding 1 at index x+y-1)
  • Point [x + y]: Needs 0 replacements

    • No additional update needed (the previous -1 at d[x + y] handles this)
  • Range [x + y + 1, y + limit]: Needs 1 replacement

    • d[x + y + 1] += 1 (start adding 1 at index x+y+1)
    • d[y + limit + 1] -= 1 (stop adding 1 after index y+limit)
  • Range [y + limit + 1, 2 * limit]: Needs 2 replacements

    • d[y + limit + 1] += 2 (start adding 2 at index y+limit+1)
    • No end marker needed as it goes until the array end

Step 4: Find the minimum cost

  • Use accumulate(d[2:]) to compute the prefix sum, which gives us the actual number of replacements needed for each possible sum.
  • Return the minimum value from these prefix sums, which represents the minimum number of moves required.

The time complexity is O(n) where n is the length of the array, as we process each pair once and the difference array operations are constant time. The space complexity is O(limit) for the difference array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [1, 5, 3, 3] and limit = 6.

Step 1: Identify pairs

  • Pair 1: (nums[0], nums[3]) = (1, 3)x = 1, y = 3
  • Pair 2: (nums[1], nums[2]) = (5, 3)x = 3, y = 5

Step 2: Analyze replacement costs for each pair

For Pair 1 (x=1, y=3):

  • Current sum: 1 + 3 = 4 (needs 0 replacements)
  • One replacement ranges:
    • Can achieve sums [x+1, y+limit] = [2, 9] with 1 replacement
    • But sum 4 needs 0, so we split: [2,3] needs 1, [4,4] needs 0, [5,9] needs 1
  • Two replacements: sums outside [2, 9] would need 2 (but max sum is 2*6=12)

For Pair 2 (x=3, y=5):

  • Current sum: 3 + 5 = 8 (needs 0 replacements)
  • One replacement ranges:
    • Can achieve sums [x+1, y+limit] = [4, 11] with 1 replacement
    • But sum 8 needs 0, so we split: [4,7] needs 1, [8,8] needs 0, [9,11] needs 1
  • Two replacements: sums [2,3] and [12,12] need 2

Step 3: Build difference array

Initialize d = [0] * 14 (size 2*limit + 2)

For Pair 1 (x=1, y=3):

  • [2, 1]: needs 2 → d[2] += 2 (but 1 < 2, so this range is empty)
  • [2, 3]: needs 1 → d[2] += 1, d[4] -= 1
  • [4, 4]: needs 0 → already handled by the -1 at d[4]
  • [5, 9]: needs 1 → d[5] += 1, d[10] -= 1
  • [10, 12]: needs 2 → d[10] += 2

For Pair 2 (x=3, y=5):

  • [2, 3]: needs 2 → d[2] += 2, d[4] -= 2
  • [4, 7]: needs 1 → d[4] += 1, d[8] -= 1
  • [8, 8]: needs 0 → already handled by the -1 at d[8]
  • [9, 11]: needs 1 → d[9] += 1, d[12] -= 1
  • [12, 12]: needs 2 → d[12] += 2

Step 4: Compute prefix sum

After all updates:

d = [0, 0, 3, 0, -2, 1, 0, 0, -1, 1, 1, 0, 1, 0]

Computing prefix sum from index 2:

Sum  | 2  3  4  5  6  7  8  9  10 11 12
Cost | 3  3  1  2  2  2  1  2  3  3  4

Step 5: Find minimum

The minimum cost is 1 at sum 4 and sum 8.

This means we need only 1 move to make the array complementary:

  • If we choose target sum 4: Replace nums[1]=5 with 1 to get [1,1,3,3]
  • If we choose target sum 8: Replace nums[0]=1 with 5 to get [5,5,3,3]

Both options require exactly 1 replacement to make all pairs sum to the same value.

Solution Implementation

1from typing import List
2from itertools import accumulate
3
4
5class Solution:
6    def minMoves(self, nums: List[int], limit: int) -> int:
7        # Initialize difference array for tracking move costs
8        # Size is 2*limit+2 to handle all possible sums from 2 to 2*limit
9        diff_array = [0] * (2 * limit + 2)
10        n = len(nums)
11      
12        # Process each pair (nums[i], nums[n-1-i])
13        for i in range(n // 2):
14            left_val = nums[i]
15            right_val = nums[n - 1 - i]
16          
17            # Ensure left_val <= right_val for consistent processing
18            if left_val > right_val:
19                left_val, right_val = right_val, left_val
20          
21            # Build difference array using range update technique:
22            # - Start with 2 moves needed for all sums (worst case)
23            diff_array[2] += 2
24          
25            # - From sum (left_val + 1) onwards, only 1 move needed
26            diff_array[left_val + 1] -= 2
27            diff_array[left_val + 1] += 1
28          
29            # - At exact sum (left_val + right_val), 0 moves needed
30            diff_array[left_val + right_val] -= 1
31          
32            # - After exact sum, back to 1 move needed
33            diff_array[left_val + right_val + 1] += 1
34          
35            # - Beyond (right_val + limit), back to 2 moves needed
36            diff_array[right_val + limit + 1] -= 1
37            diff_array[right_val + limit + 1] += 2
38      
39        # Apply prefix sum to get actual move counts for each target sum
40        # Return minimum moves needed (skip indices 0 and 1 as sums start from 2)
41        return min(accumulate(diff_array[2:]))
42
1class Solution {
2    public int minMoves(int[] nums, int limit) {
3        // Create a difference array to track move costs for each possible sum
4        // Size is 2*limit+2 to handle all possible sums from 2 to 2*limit
5        int[] differenceArray = new int[2 * limit + 2];
6        int arrayLength = nums.length;
7      
8        // Process each pair (nums[i], nums[n-i-1])
9        for (int i = 0; i < arrayLength / 2; ++i) {
10            // Get the smaller and larger values of the current pair
11            int minValue = Math.min(nums[i], nums[arrayLength - i - 1]);
12            int maxValue = Math.max(nums[i], nums[arrayLength - i - 1]);
13          
14            // For sum in range [2, minValue]: need 2 moves (change both elements)
15            differenceArray[2] += 2;
16            differenceArray[minValue + 1] -= 2;
17          
18            // For sum in range [minValue+1, minValue+maxValue-1]: need 1 move
19            differenceArray[minValue + 1] += 1;
20            differenceArray[minValue + maxValue] -= 1;
21          
22            // For sum = minValue+maxValue: need 0 moves (already equals this sum)
23            differenceArray[minValue + maxValue] += 1;
24            differenceArray[minValue + maxValue + 1] -= 1;
25          
26            // For sum in range [minValue+maxValue+1, maxValue+limit]: need 1 move
27            // (Already handled by the previous difference array updates)
28          
29            // For sum in range [maxValue+limit+1, 2*limit]: need 2 moves
30            differenceArray[maxValue + limit + 1] += 2;
31        }
32      
33        // Calculate the minimum moves by computing prefix sum
34        int minimumMoves = arrayLength;
35        int prefixSum = 0;
36      
37        // Check each possible target sum from 2 onwards
38        for (int targetSum = 2; targetSum < differenceArray.length; ++targetSum) {
39            prefixSum += differenceArray[targetSum];
40            minimumMoves = Math.min(minimumMoves, prefixSum);
41        }
42      
43        return minimumMoves;
44    }
45}
46
1class Solution {
2public:
3    int minMoves(vector<int>& nums, int limit) {
4        int n = nums.size();
5      
6        // Difference array to track cost changes across different target sums
7        // Size is limit*2 + 2 to cover all possible sums from 2 to limit*2
8        int costDifference[limit * 2 + 2];
9        memset(costDifference, 0, sizeof(costDifference));
10      
11        // Process each pair (nums[i], nums[n-i-1])
12        for (int i = 0; i < n / 2; ++i) {
13            int minVal = nums[i];
14            int maxVal = nums[n - i - 1];
15          
16            // Ensure minVal <= maxVal for consistent processing
17            if (minVal > maxVal) {
18                swap(minVal, maxVal);
19            }
20          
21            // Build difference array based on cost regions:
22            // [2, minVal]: cost = 2 (need to change both elements)
23            costDifference[2] += 2;
24            costDifference[minVal + 1] -= 2;
25          
26            // [minVal + 1, minVal + maxVal - 1]: cost = 1 (change one element)
27            costDifference[minVal + 1] += 1;
28            costDifference[minVal + maxVal] -= 1;
29          
30            // [minVal + maxVal]: cost = 0 (no change needed)
31            // No explicit update needed as the difference is 0
32          
33            // [minVal + maxVal + 1, maxVal + limit]: cost = 1 (change one element)
34            costDifference[minVal + maxVal + 1] += 1;
35            costDifference[maxVal + limit + 1] -= 1;
36          
37            // [maxVal + limit + 1, 2 * limit]: cost = 2 (need to change both elements)
38            costDifference[maxVal + limit + 1] += 2;
39        }
40      
41        // Find the minimum cost by calculating prefix sum of the difference array
42        int minTotalMoves = n;  // Initialize with maximum possible moves
43        int currentCost = 0;
44      
45        for (int targetSum = 2; targetSum <= limit * 2; ++targetSum) {
46            currentCost += costDifference[targetSum];
47            minTotalMoves = min(minTotalMoves, currentCost);
48        }
49      
50        return minTotalMoves;
51    }
52};
53
1/**
2 * Finds the minimum number of moves to make all pairs sum to the same value
3 * @param nums - Array of integers
4 * @param limit - Maximum value any element can be changed to (1 to limit)
5 * @returns Minimum number of moves required
6 */
7function minMoves(nums: number[], limit: number): number {
8    const arrayLength: number = nums.length;
9  
10    // Difference array to track cost changes at different sum values
11    // Size is 2*limit+2 to handle all possible sums (2 to 2*limit)
12    const differenceArray: number[] = Array(limit * 2 + 2).fill(0);
13  
14    // Process each pair from both ends of the array
15    for (let i = 0; i < arrayLength >> 1; ++i) {
16        // Get the pair elements from opposite ends
17        const minValue: number = Math.min(nums[i], nums[arrayLength - 1 - i]);
18        const maxValue: number = Math.max(nums[i], nums[arrayLength - 1 - i]);
19      
20        // Build difference array using range updates:
21        // [2, minValue]: requires 2 moves (both elements need to change)
22        differenceArray[2] += 2;
23        differenceArray[minValue + 1] -= 2;
24      
25        // [minValue+1, minValue+maxValue-1]: requires 1 move (change larger element)
26        differenceArray[minValue + 1] += 1;
27        differenceArray[minValue + maxValue] -= 1;
28      
29        // [minValue+maxValue, minValue+maxValue]: requires 0 moves (already sum to this)
30        // No update needed as the net change is 0
31      
32        // [minValue+maxValue+1, maxValue+limit]: requires 1 move (change smaller element)
33        differenceArray[minValue + maxValue + 1] += 1;
34        differenceArray[maxValue + limit + 1] -= 1;
35      
36        // [maxValue+limit+1, 2*limit]: requires 2 moves (both elements need to change)
37        differenceArray[maxValue + limit + 1] += 2;
38    }
39  
40    // Find the minimum cost by computing prefix sum
41    let minMoves: number = arrayLength;
42    let currentCost: number = 0;
43  
44    // Calculate actual costs from difference array and find minimum
45    for (let targetSum = 2; targetSum < differenceArray.length; ++targetSum) {
46        currentCost += differenceArray[targetSum];
47        minMoves = Math.min(minMoves, currentCost);
48    }
49  
50    return minMoves;
51}
52

Time and Space Complexity

Time Complexity: O(n + limit)

The algorithm iterates through n/2 pairs of elements (where n is the length of the input array), performing constant-time operations for each pair. This gives us O(n) for the first loop. The accumulate function then processes the difference array d[2:] which has a length of approximately 2 * limit, taking O(limit) time. The min function also takes O(limit) time to find the minimum value from the accumulated results. Therefore, the overall time complexity is O(n + limit).

Space Complexity: O(limit)

The algorithm creates a difference array d of size 2 * limit + 2, which requires O(limit) space. The accumulate function creates an iterator that generates values on-the-fly, and min processes these values without storing them all at once, so no additional space proportional to the array size is needed. The only significant space usage is the difference array d, resulting in a space complexity of O(limit).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Difference Array Updates Leading to Overlapping Ranges

One of the most common mistakes is incorrectly updating the difference array boundaries, which causes overlapping or gaps in the cost ranges. The code shown has this exact issue.

The Problem: In the provided code, the updates are:

diff_array[2] += 2
diff_array[left_val + 1] -= 2
diff_array[left_val + 1] += 1
diff_array[left_val + right_val] -= 1
diff_array[left_val + right_val + 1] += 1
diff_array[right_val + limit + 1] -= 1
diff_array[right_val + limit + 1] += 2

This creates overlapping updates at left_val + 1 and right_val + limit + 1, making the logic confusing and error-prone.

The Solution: Simplify by updating each range boundary only once:

# Range [2, left_val]: 2 moves
diff_array[2] += 2
diff_array[left_val + 1] -= 1  # Reduce by 1 (from 2 to 1)

# Range [left_val + 1, left_val + right_val - 1]: 1 move (already set)

# Point [left_val + right_val]: 0 moves
diff_array[left_val + right_val] -= 1  # Reduce by 1 (from 1 to 0)
diff_array[left_val + right_val + 1] += 1  # Back to 1

# Range [left_val + right_val + 1, right_val + limit]: 1 move (already set)

# Range [right_val + limit + 1, 2*limit]: 2 moves
diff_array[right_val + limit + 1] += 1  # Increase by 1 (from 1 to 2)

2. Array Index Out of Bounds

The Problem: When right_val + limit + 1 exceeds the difference array size, it causes an index out of bounds error. This happens when processing pairs where one element is already at the limit.

The Solution: Add boundary checks before updating:

if right_val + limit + 1 < len(diff_array):
    diff_array[right_val + limit + 1] += 1

3. Not Handling Edge Cases for Small Limits

The Problem: When limit is very small (e.g., 1 or 2), the ranges might overlap or become invalid, leading to incorrect calculations.

The Solution: Ensure all range boundaries are valid:

# Ensure we don't go out of bounds
max_sum = min(right_val + limit, 2 * limit)
if max_sum + 1 < len(diff_array):
    diff_array[max_sum + 1] += 1

Corrected Code:

from typing import List
from itertools import accumulate

class Solution:
    def minMoves(self, nums: List[int], limit: int) -> int:
        diff_array = [0] * (2 * limit + 2)
        n = len(nums)
      
        for i in range(n // 2):
            left_val = min(nums[i], nums[n - 1 - i])
            right_val = max(nums[i], nums[n - 1 - i])
          
            # Start with 2 moves for all sums
            diff_array[2] += 2
          
            # From left_val + 1, only 1 move needed
            diff_array[left_val + 1] -= 1
          
            # At exact sum, 0 moves needed
            diff_array[left_val + right_val] -= 1
          
            # After exact sum, back to 1 move
            diff_array[left_val + right_val + 1] += 1
          
            # After right_val + limit, back to 2 moves
            if right_val + limit + 1 < len(diff_array):
                diff_array[right_val + limit + 1] += 1
      
        # Calculate prefix sum and find minimum
        return min(accumulate(diff_array[2:2 * limit + 1]))
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

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

Load More