3781. Maximum Score After Binary Swaps
Problem Description
You are given an integer array nums of length n and a binary string s of the same length.
Initially, your score is 0. Each index i where s[i] = '1' contributes nums[i] to the score.
You may perform any number of operations (including zero). In one operation, you may choose an index i such that 0 <= i < n - 1, where s[i] = '0' and s[i + 1] = '1', and swap these two characters.
Return an integer denoting the maximum possible score you can achieve.
In other words, each '1' in the string can be moved to the left through any number of swaps (each swap moves a '1' one position left, past a '0'). After rearranging the '1's by moving them leftward, the score is the sum of nums[i] over all positions i where s[i] = '1'. The goal is to choose how to perform the swaps so that this total sum is as large as possible.
The key observation is that because a '1' can only move left and cannot move right, each '1' ends up occupying some position at or before its original location. Since '1's can be relocated to any earlier available spots, each '1' effectively gets to select one of the numbers at indices to its left (including its own index), without two '1's sharing the same index. To maximize the score, every '1' should claim the largest still-unclaimed value among the indices available to it.
How We Pick the Algorithm
Why Heap / Sortings?
This problem maps to Heap / Sortings through a short path in the full flowchart.
Using a max-heap to greedily assign each '1' the largest available value it can reach by moving left maximizes the total score.
Open in FlowchartIntuition
Let's think about what the swap operation really lets us do. The only allowed swap moves a '1' one step to the left (past a '0'). A '1' can never move to the right. This means every '1' can end up at its original position or at any position before it, as long as no two '1's land on the same spot.
So the real question becomes: for each '1' in the string, which index's value should it collect? Since a '1' at position i can slide left to any earlier empty slot, it is free to "pick up" the value nums[j] for any index j <= i that isn't already taken by another '1'.
If we process the array from left to right, then by the time we reach the k-th '1', all indices up to its current position have already been seen. Together with the values from earlier '1's positions, this forms a pool of candidate numbers. Each '1' wants the biggest value it can grab, and earlier choices should never block later ones from doing better.
This naturally leads to a greedy idea: keep a running collection of all values seen so far, and whenever we hit a '1', take the largest value currently available. Because every value added stays a valid candidate for all '1's that come afterward, always grabbing the current maximum for each '1' guarantees the overall sum is maximized.
To efficiently fetch and remove the largest available value at each '1', a max-heap is the perfect tool. We push each nums[i] into the heap as we scan, and for every '1' we pop the heap's maximum and add it to the answer. Since Python's heapq is a min-heap, we push negated values -x so that popping gives us the largest original number.
Pattern Learn more about Greedy and Heap (Priority Queue) patterns.
Solution Approach
Solution 1: Max-Heap
According to the problem statement, each '1' can be swapped left any number of times, so each '1' can choose the largest unpicked number among the indices to its left (including its own). We maintain these candidate numbers with a max-heap.
We traverse the string s together with the array nums from left to right. For each position i:
- Push the current value
nums[i]into the max-heap. This makes it a candidate that can be picked by the current'1'or any later'1'. - If
s[i] = '1', pop the maximum value from the heap and add it to the answer. This represents the current'1'grabbing the best still-available value.
After the traversal finishes, the accumulated sum is the maximum score.
Since Python's heapq only provides a min-heap, we store the negated values -x. Pushing -x and popping the smallest element gives us the largest original number, so we subtract the popped value (ans -= heappop(pq)) to add its true positive amount.
Walking through the code:
pq = []initializes an empty heap, andans = 0initializes the score.heappush(pq, -x)adds every number as a candidate as we scan.- When
c == "1",ans -= heappop(pq)removes the current maximum and adds it to the answer. - Finally,
return ansgives the maximum achievable score.
The time complexity is O(n × log n), where n is the length of nums, because each element is pushed and possibly popped from the heap once, and each heap operation costs O(log n). The space complexity is O(n) for the heap.
Example Walkthrough
Let's trace through a small example to see the max-heap approach in action.
Input:
nums = [3, 1, 5, 2]s = "0110"
Our goal is to move each '1' leftward (or leave it in place) so that the sum of values at the '1' positions is maximized. There are two '1's (at indices 1 and 2), so we will end up picking two values.
We scan from left to right, maintaining a max-heap (pq) of candidate values seen so far and a running ans = 0.
i | nums[i] | s[i] | Action | Heap after action (as values) | ans |
|---|---|---|---|---|---|
| 0 | 3 | '0' | Push 3 | [3] | 0 |
| 1 | 1 | '1' | Push 1 → heap [3, 1]; pop max (3), add to ans | [1] | 3 |
| 2 | 5 | '1' | Push 5 → heap [5, 1]; pop max (5), add to ans | [1] | 8 |
| 3 | 2 | '0' | Push 2 | [2, 1] | 8 |
Step-by-step reasoning:
i = 0—s[0] = '0', so no'1'to satisfy yet. We still pushnums[0] = 3into the heap because it remains a valid candidate for any future'1'(a later'1'could slide left onto index 0).i = 1— first pushnums[1] = 1. The heap now holds{3, 1}. Sinces[1] = '1', this'1'grabs the largest available value. It picks3(which lives at index 0, reachable by sliding left).ans = 3. The value1stays in the heap.i = 2— pushnums[2] = 5. The heap now holds{5, 1}. Sinces[2] = '1', this'1'grabs the maximum,5(its own position).ans = 8. The value1remains unclaimed.i = 3—s[3] = '0', so we only pushnums[3] = 2. No'1'to satisfy.
Result: ans = 8.
Verification against the problem semantics:
The first '1' (index 1) slides left into index 0 to collect nums[0] = 3. The second '1' (index 2) stays put to collect nums[2] = 5. Final placement corresponds to '1's at indices 0 and 2, giving 3 + 5 = 8 — the maximum possible. Any other assignment (e.g., keeping the first '1' at index 1 for value 1) would yield a smaller sum, confirming the greedy "always take the current max" strategy is optimal.
Note how the implementation uses negated values internally: pushing -3, -1, -5 into Python's min-heap and popping the smallest (-3, then -5) recovers the largest originals via ans -= heappop(pq).
Solution Implementation
1from heapq import heappush, heappop
2from typing import List
3
4
5class Solution:
6 def maximumScore(self, nums: List[int], s: str) -> int:
7 # Running total of selected values
8 total_score = 0
9
10 # Max-heap simulated with a min-heap by storing negated values
11 max_heap = []
12
13 # Traverse each number together with its corresponding flag character
14 for num, flag in zip(nums, s):
15 # Push the negated number so the smallest negative (largest original) is on top
16 heappush(max_heap, -num)
17
18 # When the flag is "1", we are allowed to take one number:
19 # greedily pick the largest seen so far
20 if flag == "1":
21 total_score -= heappop(max_heap) # subtract negative => add original
22
23 return total_score
241class Solution {
2 public long maximumScore(int[] nums, String s) {
3 // Accumulator for the final maximum score
4 long totalScore = 0;
5
6 // Max-heap to keep the largest available numbers at the top.
7 // Collections.reverseOrder() turns the default min-heap into a max-heap.
8 PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
9
10 // Iterate through every number alongside its corresponding flag in the string.
11 for (int i = 0; i < nums.length; i++) {
12 int currentNum = nums[i];
13 char flag = s.charAt(i);
14
15 // Add the current number into the pool of candidates.
16 maxHeap.offer(currentNum);
17
18 // When the flag is '1', we are allowed to "collect" one number.
19 // Greedily take the largest available number to maximize the score.
20 if (flag == '1') {
21 totalScore += maxHeap.poll();
22 }
23 }
24
25 return totalScore;
26 }
27}
281class Solution {
2public:
3 long long maximumScore(vector<int>& nums, string s) {
4 // Accumulated maximum score
5 long long totalScore = 0;
6
7 // Max-heap to keep track of the largest available numbers seen so far
8 priority_queue<int> maxHeap;
9
10 // Iterate through each number paired with its corresponding character in s
11 for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
12 int currentValue = nums[i];
13 char currentFlag = s[i];
14
15 // Add the current number into the pool of candidates
16 maxHeap.push(currentValue);
17
18 // When the flag is '1', we are allowed to "collect" one number.
19 // Greedily pick the largest available value to maximize the score.
20 if (currentFlag == '1') {
21 totalScore += maxHeap.top();
22 maxHeap.pop();
23 }
24 }
25
26 return totalScore;
27 }
28};
291/**
2 * Computes the maximum score by greedily selecting the largest available numbers.
3 * At each position where the character in `s` is '1', the current maximum value
4 * from all numbers seen so far (that haven't been used) is added to the answer.
5 *
6 * @param nums - The array of numbers to choose from.
7 * @param s - A binary string where '1' indicates a position to take the max value.
8 * @returns The accumulated maximum score.
9 */
10function maximumScore(nums: number[], s: string): number {
11 // Accumulated result score.
12 let answer = 0;
13
14 // Max-heap holding all numbers encountered so far that are still available.
15 const maxHeap = new MaxPriorityQueue<number>();
16
17 for (let i = 0; i < nums.length; i++) {
18 const value = nums[i];
19 const flag = s[i];
20
21 // Make the current number available for selection.
22 maxHeap.enqueue(value);
23
24 // When the flag is '1', take the largest available number.
25 if (flag === '1') {
26 answer += maxHeap.dequeue()!;
27 }
28 }
29
30 return answer;
31}
32Time and Space Complexity
-
Time Complexity:
O(n log n), wherenis the length of the arraynums. The code iterates throughnumsandstogether with a single loop runningntimes. In each iteration, aheappushoperation is performed, costingO(log n). Additionally, when the charactercequals"1", aheappopoperation is also performed, also costingO(log n). Therefore, the overall time complexity isO(n log n). -
Space Complexity:
O(n), wherenis the length of the arraynums. The priority queuepqcan hold up tonelements in the worst case, so the additional space required isO(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
A frequent mistake is forgetting that Python's heapq is a min-heap and mishandling the sign conversion, which breaks the greedy "pick the largest available value" logic.
Pitfall 1: Treating the heap as a max-heap directly
Python only provides a min-heap. If you push the raw values and pop, you will always remove the smallest candidate instead of the largest:
# WRONG: this pops the smallest value, minimizing the score heappush(max_heap, num) if flag == "1": total_score += heappop(max_heap) # picks the worst candidate
This produces the minimum score, not the maximum. The fix is to negate values on the way in and on the way out:
# CORRECT: store negated values to simulate a max-heap heappush(max_heap, -num) if flag == "1": total_score -= heappop(max_heap) # subtract negative => add original positive
Pitfall 2: Inconsistent sign handling
Another variant of the same bug is negating only on push but forgetting to negate on pop (or vice versa):
# WRONG: pushes -num but adds the negative value directly heappush(max_heap, -num) if flag == "1": total_score += heappop(max_heap) # adds a negative number, shrinking the score
Here the largest original value is correctly retrieved (the most negative entry pops first), but it is added back as a negative number. Always keep the negation symmetric: negate when pushing and negate again when popping.
# CORRECT heappush(max_heap, -num) total_score -= heappop(max_heap) # the two negations cancel out
Pitfall 3: Pushing only when the character is '1'
It is tempting to push nums[i] into the heap only at positions where s[i] == '1', but every index to the left of a '1' is a valid candidate—including positions holding '0'. A '1' can be swapped leftward into a '0' slot, so those values must be available too:
# WRONG: skips '0' positions, losing valid candidates
for num, flag in zip(nums, s):
if flag == "1":
heappush(max_heap, -num)
total_score -= heappop(max_heap)
The correct approach pushes every value as you scan, and only pops when you encounter a '1':
# CORRECT
for num, flag in zip(nums, s):
heappush(max_heap, -num) # every index is a candidate
if flag == "1":
total_score -= heappop(max_heap)
Key takeaway
The greedy correctness relies on two invariants: (1) the heap must always contain all values seen so far (not just '1' positions), and (2) each '1' must extract the current maximum. Both the sign-handling and the unconditional-push details are essential to preserve these invariants.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhat does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
141public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
271function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27Recommended 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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!