LeetCode 2587 - Rearrange Array to Maximize Prefix Score
The problem gives us a 0-indexed array of integers, nums, which can include negative numbers, zero, and positive numbers. We are allowed to reorder the elements in any way we choose.
Difficulty: 🟡 Medium
Topics: Array, Greedy, Sorting, Prefix Sum
Solution
Problem Understanding
The problem gives us a 0-indexed array of integers, nums, which can include negative numbers, zero, and positive numbers. We are allowed to reorder the elements in any way we choose. After reordering, we compute the prefix sum array, prefix, where prefix[i] is the sum of all elements from index 0 to i. The score of this array is the count of positive numbers in the prefix array. Our goal is to maximize the score by choosing an optimal order for nums.
Key points are:
- Positive prefix sums contribute to the score; negative or zero values do not.
- Reordering is allowed, so the placement of numbers affects the sum accumulation.
- Constraints indicate
numscan be as large as10^5elements and values up to10^6in magnitude. Therefore, an algorithm must be linearithmic at most, O(n log n), to run efficiently. - Edge cases include arrays of all negatives, all positives, zeros, or a mix. For arrays with all negative numbers, the maximum score is zero. For all positive numbers, the maximum score is the length of the array.
Understanding these constraints is essential because naive approaches like trying all permutations would be computationally infeasible.
Approaches
Brute Force
The brute-force approach would involve generating all possible permutations of the input array, computing the prefix sums for each, and counting the positive prefix sums. Then, we would take the maximum score among all permutations. This guarantees correctness, as it explores all options, but it is exponentially slow, O(n!), which is impossible for n up to 10^5.
Optimal Approach
The key observation is that to maximize the number of positive prefix sums, we should accumulate positive sums as early as possible. This means we should place the largest positive numbers first. Negative numbers or zeros should come later because they might reduce the running prefix sum and decrease the number of positive prefixes.
Formally, the steps are:
- Sort the array in descending order. Positive numbers will appear first.
- Iterate through the sorted array, maintaining a running sum.
- Count each prefix that remains positive. Once the running sum becomes non-positive, we stop counting since any further negative numbers would not contribute positively to the prefix.
This approach works because the sum of the first k largest numbers is maximized at every prefix, ensuring the largest number of positive prefix sums.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n!) | O(n) | Try all permutations, impractical |
| Optimal | O(n log n) | O(1) | Sort descending, accumulate sum, count positive prefixes |
Algorithm Walkthrough
- Sort
numsin descending order. This ensures that the largest values are considered first, maximizing the chance of maintaining a positive prefix sum as we traverse. - Initialize a running sum
prefix_sumto 0 and ascorecounter to 0**. - Iterate over the sorted array:
- Add the current element to
prefix_sum. - If
prefix_sum > 0, incrementscoreby 1. - If
prefix_sum <= 0, we can still continue adding numbers but they may not increase the score.
- Return
scoreafter completing the iteration.
Why it works: By sorting in descending order, every prefix sum is as large as possible given the remaining numbers. This maximizes the number of positive prefix sums because adding smaller or negative numbers later cannot undo the positives accumulated from larger numbers upfront.
Python Solution
from typing import List
class Solution:
def maxScore(self, nums: List[int]) -> int:
nums.sort(reverse=True) # Step 1: Sort descending
prefix_sum = 0
score = 0
for num in nums: # Step 2-3: Iterate and accumulate
prefix_sum += num
if prefix_sum > 0:
score += 1
return score # Step 4: Return the total positive prefixes
Explanation: First, we sort the array in descending order. The running sum accumulates elements one by one. Each time the running sum is positive, we increment the score. This directly implements the optimal approach discussed above.
Go Solution
import "sort"
func maxScore(nums []int) int {
sort.Slice(nums, func(i, j int) bool {
return nums[i] > nums[j] // Sort descending
})
prefixSum := 0
score := 0
for _, num := range nums {
prefixSum += num
if prefixSum > 0 {
score++
}
}
return score
}
Go-specific notes: We use sort.Slice with a custom comparator to sort descending. Slices are dynamically sized, so no extra space is required. The integer operations are safe within the problem's constraints.
Worked Examples
Example 1: nums = [2,-1,0,1,-3,3,-3]
- Sort descending:
[3, 2, 1, 0, -1, -3, -3] - Initialize
prefix_sum = 0,score = 0. - Iterate:
- 3 → prefix_sum = 3 → score = 1
- 2 → prefix_sum = 5 → score = 2
- 1 → prefix_sum = 6 → score = 3
- 0 → prefix_sum = 6 → score = 4
- -1 → prefix_sum = 5 → score = 5
- -3 → prefix_sum = 2 → score = 6
- -3 → prefix_sum = -1 → score remains 6
- Return 6
Example 2: nums = [-2, -3, 0]
- Sort descending:
[0, -2, -3] - Initialize
prefix_sum = 0,score = 0. - Iterate:
- 0 → prefix_sum = 0 → score = 0
- -2 → prefix_sum = -2 → score = 0
- -3 → prefix_sum = -5 → score = 0
- Return 0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting the array dominates; iteration is O(n) |
| Space | O(1) | Only counters and running sum; sorting in-place |
Sorting dominates time complexity. Extra space is negligible because we can sort in-place and maintain only two counters.
Test Cases
# Provided examples
assert Solution().maxScore([2,-1,0,1,-3,3,-3]) == 6 # Mixed numbers
assert Solution().maxScore([-2,-3,0]) == 0 # All non-positive
# Edge cases
assert Solution().maxScore([1,2,3,4,5]) == 5 # All positive
assert Solution().maxScore([-1,-2,-3]) == 0 # All negative
assert Solution().maxScore([0,0,0]) == 0 # All zeros
assert Solution().maxScore([1000000, -1000000]) == 1 # Large numbers
assert Solution().maxScore([1]) == 1 # Single positive
assert Solution().maxScore([-1]) == 0 # Single negative
| Test | Why |
|---|---|
[2,-1,0,1,-3,3,-3] |
Mixed positive, negative, zero numbers |
[-2,-3,0] |
All non-positive numbers |
[1,2,3,4,5] |
All positive numbers |
[-1,-2,-3] |
All negative numbers |
[0,0,0] |
All zeros |
[1000000, -1000000] |
Large magnitude numbers |
[1] |
Single element positive |
[-1] |
Single element negative |
Edge Cases
- All negative numbers: Any order yields a running sum that is never positive. Our implementation correctly returns 0 as the maximum score.
- Zeros mixed with negatives: Adding zeros does not increase the prefix sum, so they do not contribute to the score. The algorithm naturally counts only positive sums.
- Single element arrays: Both positive and negative single-element arrays are handled, returning 1 or 0 as appropriate.
- Large arrays with large numbers: Sorting in-place and iterating ensures that integer accumulation does not overflow within the problem constraints, making the solution robust to the largest allowed inputs.
This solution is both efficient and safe for all specified constraints.