2587. Rearrange Array to Maximize Prefix Score
Problem Description
You have an integer array nums
with 0-based indexing. You can rearrange the elements of this array in any order you want.
After rearranging the array, you need to calculate its prefix sum array. The prefix sum array prefix
is defined such that prefix[i]
equals the sum of all elements from index 0 to index i in the rearranged array.
Your goal is to find a rearrangement that maximizes the score, where the score is defined as the count of positive integers in the prefix sum array.
For example, if after rearranging you get nums = [2, -1, 3]
, then:
prefix[0] = 2
(positive)prefix[1] = 2 + (-1) = 1
(positive)prefix[2] = 2 + (-1) + 3 = 4
(positive)
This would give a score of 3 since all three prefix sums are positive.
The task is to return the maximum possible score you can achieve by optimally rearranging the array elements.
Intuition
To maximize the number of positive prefix sums, we need to think about what makes a prefix sum positive. Each prefix sum is the cumulative total of elements up to that position. If we want as many positive prefix sums as possible, we should try to keep the running sum positive for as long as possible.
The key insight is that we should place larger positive numbers first. Why? Because when we add a large positive number early, it creates a "buffer" that can absorb negative numbers that come later while still keeping the prefix sum positive.
Consider this: if we have numbers like [5, -2, 3, -1]
, arranging them as [5, 3, -1, -2]
(largest to smallest) gives us:
- First prefix:
5
(positive) - Second prefix:
5 + 3 = 8
(positive) - Third prefix:
8 + (-1) = 7
(positive) - Fourth prefix:
7 + (-2) = 5
(positive)
All prefix sums remain positive! But if we had started with negative numbers or smaller positive numbers, we would lose the ability to maintain positive sums early on.
This leads us to a greedy strategy: sort the array in descending order. Start with the largest values to build up a strong positive sum, then add smaller values (including negatives) later. The accumulated positive sum from the beginning can help offset the negative values that come later.
We keep adding elements and calculating the running sum. Once the running sum becomes zero or negative, we know that from this point onward, all subsequent prefix sums will be non-positive (since we're adding smaller or more negative values). At that point, we can stop and return how many positive prefix sums we've counted so far.
Learn more about Greedy, Prefix Sum and Sorting patterns.
Solution Approach
The implementation follows a greedy approach with sorting:
-
Sort the array in descending order: We use
nums.sort(reverse=True)
to arrange elements from largest to smallest. This ensures we process the most positive (or least negative) elements first. -
Iterate through the sorted array while maintaining a running sum: We initialize a variable
s = 0
to track the prefix sum as we process each element. -
For each element, update the prefix sum and check if it remains positive:
for i, x in enumerate(nums): s += x if s <= 0: return i
- We add the current element
x
to our running sums
- If at any point
s
becomes zero or negative, we immediately return the current indexi
- This index represents how many positive prefix sums we were able to achieve before the sum turned non-positive
- We add the current element
-
Return the result:
- If we complete the entire loop without the sum becoming non-positive, it means all prefix sums are positive, so we return
len(nums)
- Otherwise, we return the index where the sum first became non-positive
- If we complete the entire loop without the sum becoming non-positive, it means all prefix sums are positive, so we return
The algorithm has a time complexity of O(n log n)
due to sorting, where n
is the length of the array. The space complexity is O(1)
if we don't count the space used by the sorting algorithm.
The key insight is that by processing elements in descending order, we maximize our chances of keeping the prefix sum positive for as many positions as possible. Once the sum drops to zero or below, we know that adding any remaining elements (which are smaller or more negative) won't help us achieve more positive prefix sums.
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 the solution with a concrete example: nums = [2, -1, 0, 1, -3, 3, -3]
Step 1: Sort in descending order
After sorting: nums = [3, 2, 1, 0, -1, -3, -3]
Step 2: Process elements and track prefix sums
Let's trace through each iteration:
Index (i) | Element (x) | Running Sum (s) | Is Positive? | Count So Far |
---|---|---|---|---|
0 | 3 | 0 + 3 = 3 | Yes ✓ | 1 |
1 | 2 | 3 + 2 = 5 | Yes ✓ | 2 |
2 | 1 | 5 + 1 = 6 | Yes ✓ | 3 |
3 | 0 | 6 + 0 = 6 | Yes ✓ | 4 |
4 | -1 | 6 + (-1) = 5 | Yes ✓ | 5 |
5 | -3 | 5 + (-3) = 2 | Yes ✓ | 6 |
6 | -3 | 2 + (-3) = -1 | No ✗ | Stop! |
Step 3: Return the result When we reach index 6, adding -3 makes our running sum negative (-1). At this point, we stop and return the current index, which is 6.
This means we can achieve a maximum score of 6 positive prefix sums by arranging the array as [3, 2, 1, 0, -1, -3, -3]
.
Notice how starting with the largest positive values (3, 2, 1) built up a "cushion" that allowed us to absorb the negative values (-1, -3) while keeping the prefix sum positive for as long as possible. The greedy strategy of sorting in descending order maximized our score.
Solution Implementation
1class Solution:
2 def maxScore(self, nums: List[int]) -> int:
3 # Sort the array in descending order to process larger values first
4 nums.sort(reverse=True)
5
6 # Initialize prefix sum
7 prefix_sum = 0
8
9 # Iterate through sorted array with index
10 for index, value in enumerate(nums):
11 # Add current value to prefix sum
12 prefix_sum += value
13
14 # If prefix sum becomes non-positive, return current count
15 # This means we can only take 'index' elements while keeping sum positive
16 if prefix_sum <= 0:
17 return index
18
19 # If we processed all elements and sum is still positive,
20 # return the total count of elements
21 return len(nums)
22
1class Solution {
2 public int maxScore(int[] nums) {
3 // Sort the array in ascending order
4 Arrays.sort(nums);
5
6 int arrayLength = nums.length;
7 long cumulativeSum = 0;
8
9 // Iterate through the sorted array from largest to smallest
10 for (int i = 0; i < arrayLength; ++i) {
11 // Add elements starting from the largest (last element after sorting)
12 cumulativeSum += nums[arrayLength - i - 1];
13
14 // If cumulative sum becomes non-positive, return the count of elements added so far
15 if (cumulativeSum <= 0) {
16 return i;
17 }
18 }
19
20 // If all elements can be added while maintaining positive sum, return total count
21 return arrayLength;
22 }
23}
24
1class Solution {
2public:
3 int maxScore(vector<int>& nums) {
4 // Sort the array in descending order to greedily pick largest elements first
5 sort(nums.rbegin(), nums.rend());
6
7 // Initialize prefix sum to track cumulative sum
8 long long prefixSum = 0;
9
10 // Get the size of the input array
11 int arraySize = nums.size();
12
13 // Iterate through sorted array and calculate prefix sum
14 for (int i = 0; i < arraySize; ++i) {
15 // Add current element to prefix sum
16 prefixSum += nums[i];
17
18 // If prefix sum becomes non-positive, we can't include this element
19 // Return the count of elements before this point
20 if (prefixSum <= 0) {
21 return i;
22 }
23 }
24
25 // If all elements maintain positive prefix sum, return total count
26 return arraySize;
27 }
28};
29
1/**
2 * Calculates the maximum score by selecting elements from a sorted array
3 * where each selected element contributes to a running sum that must remain positive
4 * @param nums - Array of numbers to process
5 * @returns Maximum number of elements that can be selected while maintaining positive sum
6 */
7function maxScore(nums: number[]): number {
8 // Sort the array in ascending order
9 nums.sort((a, b) => a - b);
10
11 // Get the length of the array
12 const arrayLength = nums.length;
13
14 // Initialize running sum
15 let runningSum = 0;
16
17 // Iterate through the array from largest to smallest elements
18 for (let i = 0; i < arrayLength; ++i) {
19 // Add the current largest unprocessed element to the sum
20 runningSum += nums[arrayLength - i - 1];
21
22 // If sum becomes non-positive, return the count of elements processed so far
23 if (runningSum <= 0) {
24 return i;
25 }
26 }
27
28 // If all elements maintain positive sum, return the total count
29 return arrayLength;
30}
31
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the array nums
. This is dominated by the sorting operation nums.sort(reverse=True)
, which uses Python's Timsort algorithm with O(n × log n)
complexity in the average and worst cases. The subsequent loop through the array takes O(n)
time, but this is overshadowed by the sorting step.
The space complexity is O(log n)
. While Python's Timsort is an in-place sorting algorithm that modifies the original list, it still requires O(log n)
auxiliary space for the recursion stack during the sorting process. The rest of the algorithm uses only O(1)
additional space for variables s
and i
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Termination Condition
The Issue: A common mistake is thinking that we should continue processing elements even after the prefix sum becomes zero or negative, hoping that future positive elements might make the sum positive again. This leads to incorrect implementations like:
def maxScore(self, nums: List[int]) -> int:
nums.sort(reverse=True)
prefix_sum = 0
count = 0
for value in nums:
prefix_sum += value
if prefix_sum > 0: # Wrong: counting individual positive sums
count += 1
return count
Why It's Wrong:
Once the prefix sum becomes zero or negative at position i
, all subsequent prefix sums will also be non-positive because we've already used our largest elements. The remaining elements are smaller or more negative, so they cannot recover the sum to positive.
The Fix: Return immediately when the prefix sum becomes non-positive:
if prefix_sum <= 0: return index # Return the count of positive prefix sums achieved
Pitfall 2: Incorrect Handling of All-Negative Arrays
The Issue: Forgetting to handle the edge case where all elements are negative. Some might incorrectly assume the answer should always be at least 1:
def maxScore(self, nums: List[int]) -> int:
if all(x < 0 for x in nums):
return 1 # Wrong assumption
# ... rest of the code
Why It's Wrong:
Even with all negative numbers, if we sort them in descending order (least negative first), the first element's prefix sum might still be positive if it's a negative number close to zero (e.g., -0.5
would still count as negative, but in integer arrays, the least negative would be -1
).
The Fix:
The original solution handles this correctly by checking if prefix_sum <= 0
which will return 0 if even the first (least negative) element makes the sum non-positive.
Pitfall 3: Off-by-One Error in Return Value
The Issue: Confusing the index with the count of positive prefix sums:
def maxScore(self, nums: List[int]) -> int:
nums.sort(reverse=True)
prefix_sum = 0
for index, value in enumerate(nums):
prefix_sum += value
if prefix_sum <= 0:
return index + 1 # Wrong: adds 1 unnecessarily
return len(nums)
Why It's Wrong:
When we encounter a non-positive sum at index i
, we've successfully maintained positive sums for indices 0
through i-1
, which is exactly i
elements. Adding 1 would overcount.
The Fix: Return the index directly, as it represents the count of elements we successfully included while maintaining positive prefix sums:
if prefix_sum <= 0: return index # Correct: index equals the count of positive prefix sums
Which type of traversal does breadth first search do?
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!