Skip to main content

Command Palette

Search for a command to run...

LeetCode 3499: Maximise Active Section with Trade

O(n) Time | O(1) Space | C++

Updated
6 min readView as Markdown
LeetCode 3499: Maximise Active Section with Trade
E
Software Engineer with 7+ years of experience building distributed systems, high-throughput data application, and scalable backend infrastructure across finance and sports technology. Experienced designing event-driven systems, data pipelines, and production services supporting analytical workloads at scale.

You're given a binary string where 1 represents an active section and 0 represents an inactive one. You get exactly one trade:

  1. Erase a block of 1s that has 0s on both sides.

  2. Fill a block of 0s that has 1s on both sides.

The goal is to maximise the final number of active sections.

At first glance, the problem encourages you to think about flipping individual bits. That quickly becomes messy. The trick is to change the unit of thinking and instead look at runs of consecutive 1s and 0s.

The first solution explicitly uses Run Length Encoding (RLE) to represent those runs. The second builds on the same idea but processes the runs as they are encountered, reducing the extra space to O(1).

The problem also pads the string as:

t = "1" + s + "1";

This handles the boundaries for free. Any 1 block touching either end of the original string becomes connected to the padded 1, so it only has a 0 on one side and can never be erased. Every remaining 1 run is therefore a valid candidate.

Observation

Suppose we erase a valid 1 block.

The zero runs on its left and right immediately merge into a larger zero run. Since that merged run is now surrounded by 1s, it becomes exactly the block we fill in the second step.

Although the erased block briefly becomes 0, it is immediately restored to 1 when the merged zero run is filled, so its contribution never changes.

The only real gain comes from converting the two neighbouring zero runs into 1s.

That means every candidate block has a score of:

totalOnes + leftZeroRun + rightZeroRun

So the answer is simply:

answer = totalOnes + max(leftZeroRun + rightZeroRun)

taken over every valid 1 block.

If no valid block exists, the answer is simply totalOnes.

O(n) Time, O(n) Space Solution

class Solution {
public:
    int maxActiveSectionsAfterTrade(string s) {

        string t = "1" + s + "1";

        vector<pair<char,int>> runs;

        for (int i = 0; i < t.size();) {

            int j = i;

            while (j < t.size() && t[j] == t[i])
                j++;

            runs.push_back({t[i], j - i});

            i = j;
        }

        int totalOnes = count(s.begin(), s.end(), '1');

        int maxGain = 0;

        for (int i = 1; i + 1 < runs.size(); i++) {

            if (runs[i].first == '1') {

                maxGain = max(maxGain,
                              runs[i - 1].second +
                              runs[i + 1].second);
            }
        }

        return totalOnes + maxGain;
    }
};

This solution explicitly builds the Run Length Encoding by storing every run as a (character, length) pair.

Since the first and last runs always contain the padded 1s, scanning from 1 to runs.size() - 2 automatically skips the boundary cases.

Every remaining 1 run already has a zero run on both sides, so scoring it is simply a matter of adding the lengths of its neighbouring zero runs.

Building the runs and scanning them both take linear time.

Complexity:

  • Time Complexity: O(n)

  • Space Complexity: O(n)

O(n) Time, O(1) Space Solution

The previous solution only needs the current run and its immediate neighbours, so storing every run is unnecessary.

Instead, process the string one run at a time.

Every zero run is the right neighbour of one 1 block and the left neighbour of the next, so once a zero run ends we already have everything needed to score the previous block.

class Solution {
public:
    int maxActiveSectionsAfterTrade(string s) {

        int totalOnes = 0;

        int prevZero = 0;
        int curZeros = 0;

        bool havePending = false;

        int maxGain = 0;

        for (int i = 0; i < s.size(); i++) {

            if (s[i] == '1') {

                totalOnes++;

                if (i == 0 || s[i - 1] == '0') {

                    if (i > 0) {

                        if (havePending) {

                            maxGain = max(maxGain,
                                          prevZero + curZeros);
                        }

                        prevZero = curZeros;
                        havePending = true;

                    } else {

                        havePending = false;
                    }

                    curZeros = 0;
                }

            } else {

                curZeros++;
            }
        }

        if (!s.empty() && s.back() == '0' && havePending) {

            maxGain = max(maxGain,
                          prevZero + curZeros);
        }

        return totalOnes + maxGain;
    }
};

curZeros tracks the zero run currently being traversed.

When a new 1 block begins, that zero run has just ended, which means we now know the right neighbour of the previous block.

After scoring it, that same zero run immediately becomes the left neighbour of the next block.

If the string ends with 0s, the final pending block never gets scored inside the loop, so we handle that single case after the scan finishes.

Complexity:

  • Time Complexity: O(n)

  • Space Complexity: O(1)

Edge Cases

One subtle detail is easy to miss.

The problem lets us erase one block and fill another, but it never says those two blocks have to be related.

Could erasing one block and filling a completely different zero run ever produce a better answer?

No.

If a zero run can already be filled independently, then it was already surrounded by 1s.

Filling it gains its length, but the erased block is permanently lost, so the net gain is:

size(zero run) - size(erased block)

Now erase one of that zero run's neighbouring 1 blocks instead.

The erased block is rebuilt as part of the fill operation, and you gain the original zero run plus the zero run on the other side.

That is always at least as good because nothing is permanently lost.

The only exception is when the string has exactly three runs:

111...000...111

Both 1 blocks touch the boundary, so neither can be erased.

There is no valid trade, which means the answer is simply the original number of 1s.

Conclusion

The key to this problem is changing how you look at the string.

Once you stop thinking about individual characters and start thinking in terms of runs, the trade becomes easy to score.

From there, the explicit Run Length Encoding solution is straightforward, and the O(1) solution naturally follows by processing those same runs on the fly.