Facebook Pixel

3727. Maximum Alternating Sum of Squares

MediumGreedyArraySorting
LeetCode ↗

Problem Description

You are given an integer array nums, and you are allowed to rearrange its elements in any order you like.

The alternating score of an array arr is calculated by squaring each element and then alternating between adding and subtracting these squares, starting with addition:

  • score = arr[0]² - arr[1]² + arr[2]² - arr[3]² + ...

In other words:

  • Elements at even indices (0, 2, 4, ...) have their squares added to the score.
  • Elements at odd indices (1, 3, 5, ...) have their squares subtracted from the score.

Your task is to rearrange the elements of nums so that this alternating score is as large as possible, and return that maximum possible alternating score.

For example, if nums = [3, -1, 2, 4], one optimal arrangement is [4, -1, 3, 2], giving a score of 4² - (-1)² + 3² - 2² = 16 - 1 + 9 - 4 = 20.

Key observations about the problem:

  1. Since every element is squared, the sign of the original number does not matter — only the magnitude of its square does.
  2. Rearranging freely means you get to choose which elements are added and which are subtracted. With n elements, ⌈n / 2⌉ of them land on even indices (added) and ⌊n / 2⌋ land on odd indices (subtracted).
  3. To maximize the score, you want the elements with the largest squares to be added and the elements with the smallest squares to be subtracted.

This leads directly to a sorting strategy: sort the array by squared value, subtract the sum of squares of the smaller half (nums[: n // 2]), and add the sum of squares of the larger half (nums[n // 2 :]). The answer is s2 - s1, where s2 is the sum of squares of the larger half and s1 is the sum of squares of the smaller half.

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

How We Pick the Algorithm

Why Greedy Algorithms?

This problem maps to Greedy Algorithms through a short path in the full flowchart.

Tree orgraph?noMax/minwithgreedyyesGreedyAlgorithms

Sorting by square magnitude and assigning the largest squares to addition and smallest to subtraction maximizes the alternating score.

Open in Flowchart

Intuition

The first thing to notice is that the score formula only ever uses squared values. Once we square an element, its original sign disappears — (-3)² and are both 9. So the problem really boils down to working with the squares of the numbers, not the numbers themselves.

The second key insight comes from the freedom to rearrange. The alternating pattern + - + - ... looks complicated at first, but since we can place elements anywhere, the positions themselves stop mattering. All that matters is a simple partition:

  • Some elements end up at even indices, and their squares are added.
  • The rest end up at odd indices, and their squares are subtracted.

So the question transforms into: which elements should be added, and which should be subtracted?

With n elements, exactly ⌈n / 2⌉ slots are "plus" slots and ⌊n / 2⌋ are "minus" slots. To maximize (sum of added squares) - (sum of subtracted squares), a greedy choice is obvious:

  • Put the elements with the largest squares into the plus slots.
  • Put the elements with the smallest squares into the minus slots.

Swapping any large-square element out of a plus slot for a smaller one can only decrease the total, so this greedy assignment is optimal.

This naturally suggests sorting by squared value. After sorting:

  • The first half nums[: n // 2] holds the smallest squares — these get subtracted (s1).
  • The second half nums[n // 2 :] holds the largest squares — these get added (s2). Note that when n is odd, this half is one element bigger, which correctly matches the extra plus slot at index 0.

The answer is simply s2 - s1.

Pattern Learn more about Greedy and Sorting patterns.

Solution Approach

The implementation follows directly from the greedy insight: sort by squared value, subtract the small half, add the large half. Let's walk through the code step by step.

class Solution:
    def maxAlternatingSum(self, nums: List[int]) -> int:
        nums.sort(key=lambda x: x * x)
        n = len(nums)
        s1 = sum(x * x for x in nums[: n // 2])
        s2 = sum(x * x for x in nums[n // 2 :])
        return s2 - s1

Step 1: Sort by squared value

nums.sort(key=lambda x: x * x)

We sort using the key x * x rather than x itself. This is important because the array may contain negative numberssorting by raw value would place -5 before 2, but in terms of squares, (-5)² = 25 is larger than 2² = 4. Sorting by x * x guarantees the elements are ordered by their actual contribution to the score, from smallest square to largest square.

Step 2: Split the array into two halves

With n = len(nums), the sorted array is divided at index n // 2:

  • nums[: n // 2] — the ⌊n / 2⌋ elements with the smallest squares. These are conceptually placed at odd indices, so their squares form the subtracted sum s1.
  • nums[n // 2 :] — the ⌈n / 2⌉ elements with the largest squares. These go to even indices, and their squares form the added sum s2.

The split point n // 2 handles both parities of n correctly:

  • If n is even, both halves have exactly n / 2 elements, matching the equal number of plus and minus slots.
  • If n is odd, the second half gets one extra element — matching the fact that even indices (0, 2, 4, ...) outnumber odd indices by one.

Step 3: Compute the two sums and return the difference

s1 = sum(x * x for x in nums[: n // 2])
s2 = sum(x * x for x in nums[n // 2 :])
return s2 - s1

Each generator expression squares the elements of its half and sums them up. The maximum alternating score is then simply s2 - s1.

Walkthrough with an example

Take nums = [3, -1, 2, 4]:

  1. Sorting by square: [-1, 2, 3, 4] (squares: 1, 4, 9, 16).
  2. n = 4, split point is 2:
    • First half [-1, 2]s1 = 1 + 4 = 5
    • Second half [3, 4]s2 = 9 + 16 = 25
  3. Answer: s2 - s1 = 25 - 5 = 20.

This corresponds to the arrangement [4, -1, 3, 2] (or [3, 2, 4, -1], etc. — any arrangement where 3 and 4 occupy even indices).

Complexity Analysis

  • Time complexity: O(n log n), dominated by the sort. The two summation passes are O(n) each.
  • Space complexity: O(log n) for the sorting routine's internal stack (or O(n) if counting the temporary slices nums[: n // 2] and nums[n // 2 :]; this can be reduced to O(1) extra space by summing with index ranges instead of slicing).

Alternative implementations

  • Single-pass after sorting: Instead of slicing, iterate once and add x * x when the index is ≥ n // 2, subtract otherwise — avoiding the extra slice copies.
  • Quickselect: Since we only need to partition the squares around the median rather than fully sort them, a quickselect-based approach could achieve O(n) average time. For typical input sizes, however, the simplicity of sorting is usually preferable.

Example Walkthrough

Let's trace the solution with nums = [-2, 5, 1, -4, 3] — an odd-length array with negative numbers, which exercises both subtle points of the approach (sorting by square, and the uneven split).

Step 1: Sort by squared value

Compute each element's square to see its true "weight":

Element-251-43
Square4251169

Sorting by x * x (not by x!) gives:

nums = [1, -2, 3, -4, 5]      # squares: 1, 4, 9, 16, 25

Notice why the key matters: sorting by raw value would put -4 near the front, even though its square 16 is one of the largest contributions. Sorting by square places it correctly toward the end.

Step 2: Split at n // 2

Here n = 5, so the split point is 5 // 2 = 2:

  • Small half nums[:2] = [1, -2] → these will be subtracted.
  • Large half nums[2:] = [3, -4, 5] → these will be added.

The large half gets the extra element — exactly matching the fact that a length-5 array has three even indices (0, 2, 4) but only two odd indices (1, 3).

Step 3: Compute the sums and the answer

s1 = 1² + (-2)²          = 1 + 4        = 5
s2 = 3² + (-4)² + 5²     = 9 + 16 + 25  = 50
answer = s2 - s1 = 50 - 5 = 45

Verification with an actual arrangement

The split corresponds to a concrete rearrangement: place the large-square elements at even indices and the small-square elements at odd indices, e.g. [5, 1, -4, -2, 3]:

score = 5² - 1² + (-4)² - (-2)² + 3²
      = 25 - 1 + 16  - 4   + 9
      = 45

Any other assignment does worse. For instance, if -4 were stuck at an odd index and -2 promoted to an even one, the score would change by -16 - 16 + 4 + 4 = -24, dropping to 21. This confirms the greedy choice: the largest squares must be added, the smallest subtracted, and s2 - s1 = 45 is the maximum alternating score.

Solution Implementation

1class Solution:
2    def maxAlternatingSum(self, nums: List[int]) -> int:
3        # Sort the numbers in ascending order based on their squared values
4        nums.sort(key=lambda x: x * x)
5
6        # Total count of elements
7        n = len(nums)
8
9        # Sum of squares of the smaller half (first half after sorting)
10        smaller_half_sum = sum(x * x for x in nums[: n // 2])
11
12        # Sum of squares of the larger half (second half after sorting)
13        larger_half_sum = sum(x * x for x in nums[n // 2:])
14
15        # The result is the difference between the larger and smaller halves
16        return larger_half_sum - smaller_half_sum
17
1class Solution {
2    public long maxAlternatingSum(int[] nums) {
3        int length = nums.length;
4
5        // Square each element in the array
6        for (int i = 0; i < length; ++i) {
7            nums[i] *= nums[i];
8        }
9
10        // Sort the array in ascending order,
11        // so smaller squared values come first
12        Arrays.sort(nums);
13
14        long answer = 0;
15        int half = length / 2;
16
17        // Subtract the smaller half of the squared values
18        for (int i = 0; i < half; ++i) {
19            answer -= nums[i];
20        }
21
22        // Add the larger half of the squared values
23        for (int i = half; i < length; ++i) {
24            answer += nums[i];
25        }
26
27        return answer;
28    }
29}
30
1class Solution {
2public:
3    long long maxAlternatingSum(vector<int>& nums) {
4        // Step 1: Square every element in the array.
5        // Each value x is replaced with x * x.
6        for (int& num : nums) {
7            num = num * num;
8        }
9
10        // Step 2: Sort the squared values in ascending order.
11        // After sorting, the smaller squares come first and
12        // the larger squares come last.
13        ranges::sort(nums);
14
15        long long answer = 0;
16
17        // The first half (smaller values) will be subtracted,
18        // and the second half (larger values) will be added.
19        long long half = nums.size() / 2;
20
21        // Step 3: Subtract the smaller half of the squared values.
22        for (int i = 0; i < half; ++i) {
23            answer -= nums[i];
24        }
25
26        // Step 4: Add the larger half of the squared values.
27        // Note: if the array length is odd, the middle element
28        // is included in this (added) half.
29        for (int i = half; i < nums.size(); ++i) {
30            answer += nums[i];
31        }
32
33        // Step 5: Return the maximum alternating sum obtained.
34        return answer;
35    }
36};
37
1/**
2 * Calculates the maximum alternating sum by:
3 * 1. Squaring every element of the input array.
4 * 2. Sorting the squared values in ascending order.
5 * 3. Subtracting the smaller half and adding the larger half.
6 *
7 * @param nums - The input array of numbers (modified in place).
8 * @returns The maximum alternating sum after the transformation.
9 */
10function maxAlternatingSum(nums: number[]): number {
11    // Total number of elements in the array
12    const length: number = nums.length;
13
14    // Step 1: Replace each element with its square
15    for (let i = 0; i < length; i++) {
16        nums[i] = nums[i] ** 2;
17    }
18
19    // Step 2: Sort the squared values in ascending order
20    nums.sort((a: number, b: number) => a - b);
21
22    // Index that splits the array into a smaller half and a larger half
23    const half: number = Math.floor(length / 2);
24
25    // Accumulator for the final result
26    let result: number = 0;
27
28    // Step 3a: Subtract every element in the smaller half
29    for (let i = 0; i < half; i++) {
30        result -= nums[i];
31    }
32
33    // Step 3b: Add every element in the larger half
34    for (let i = half; i < length; i++) {
35        result += nums[i];
36    }
37
38    return result;
39}
40

Time and Space Complexity

  • Time Complexity: O(n log n), where n is the length of the array nums.

    • The dominant operation is nums.sort(key=lambda x: x * x), which uses Timsort and takes O(n log n) time.
    • The two summations sum(x * x for x in nums[: n // 2]) and sum(x * x for x in nums[n // 2 :]) each traverse roughly half of the array, contributing O(n) time in total.
    • Overall: O(n log n) + O(n) = O(n log n).
  • Space Complexity: O(log n), where n is the length of the array nums.

    • The sorting algorithm (Timsort) requires O(log n) auxiliary space for its recursion/stack in the typical analysis used here.
    • The generator expressions inside sum(...) compute values lazily and only need O(1) extra space.
    • Overall: O(log n).

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

Common Pitfalls

Pitfall 1: Sorting by raw value instead of squared value

The most frequent mistake is writing nums.sort() instead of nums.sort(key=lambda x: x * x). Sorting by raw value silently breaks the algorithm whenever the array contains negative numbers.

Why it fails: A plain ascending sort places large-magnitude negatives at the front of the array — exactly where the "small squares to subtract" are supposed to be. But (-5)² = 25 is one of the largest contributions, not one of the smallest.

Buggy example:

class Solution:
    def maxAlternatingSum(self, nums: List[int]) -> int:
        nums.sort()  # BUG: orders by value, not by square
        n = len(nums)
        s1 = sum(x * x for x in nums[: n // 2])
        s2 = sum(x * x for x in nums[n // 2:])
        return s2 - s1

For nums = [-5, 1, 2, 3]:

  • Buggy sort: [-5, 1, 2, 3] → subtract 25 + 1 = 26, add 4 + 9 = 13 → returns -13.
  • Correct sort by square: [1, 2, 3, -5] → subtract 1 + 4 = 5, add 9 + 25 = 34 → returns 29.

The bug is especially dangerous because it passes all test cases with non-negative inputs, so it can survive casual testing.

Fix: Always sort by the quantity that actually determines an element's contribution — its square:

nums.sort(key=lambda x: x * x)   # or: key=abs

Note that key=abs works equally well, since |x| and induce the same ordering.

Pitfall 2: Giving the extra element to the wrong half when n is odd

When n is odd, the array has one more even index than odd indices, so the added group must contain ⌈n / 2⌉ elements and the subtracted group ⌊n / 2⌋. A subtle off-by-one such as splitting at (n + 1) // 2:

s1 = sum(x * x for x in nums[: (n + 1) // 2])  # BUG: small half too big
s2 = sum(x * x for x in nums[(n + 1) // 2:])

assigns the extra (median-square) element to the subtracted side, reducing the score by twice its square. For nums = [1, 2, 3] this returns 9 - (1 + 4) = 4 instead of the correct (4 + 9) - 1 = 12.

Fix: Split at exactly n // 2, so the larger (added) half automatically receives the extra element when n is odd.

Pitfall 3: Unintentionally mutating the caller's input

nums.sort(...) sorts in place, permanently reordering the caller's list. On LeetCode this is harmless, but in real codebases a function that quietly rearranges its argument can corrupt logic elsewhere that depends on the original order.

Fix: Use a non-mutating sort if the input must be preserved:

ordered = sorted(nums, key=lambda x: x * x)

Pitfall 4: Integer overflow when porting to other languages

Python integers are arbitrary-precision, so sum(x * x for ...) can never overflow. Translating the same code to Java, C++, or Go is a different story: with values up to 10⁵ and 10⁵ elements, the sum of squares can reach 10¹⁵, far beyond a 32-bit int.

Fix (Java example): Accumulate in a 64-bit type and cast before multiplying:

long s1 = 0, s2 = 0;
for (int i = 0; i < n; i++) {
    long sq = (long) nums[i] * nums[i];  // cast BEFORE multiplying
    if (i < n / 2) s1 += sq; else s2 += sq;
}
return s2 - s1;

Casting after the multiplication ((long)(nums[i] * nums[i])) still overflows, because the product is computed in 32-bit arithmetic first.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

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