Facebook Pixel

3849. Maximum Bitwise XOR After Rearrangement

MediumGreedyBit ManipulationString
LeetCode ↗

Problem Description

You are given two binary strings s and t, each of length n (a binary string contains only the characters '0' and '1').

You are allowed to rearrange the characters of t in any order you like. However, the string s must stay the same and cannot be changed.

Your goal is to perform a bitwise XOR operation between s and the rearranged version of t. The XOR operation compares the bits at each position: it produces '1' when the two bits are different, and '0' when the two bits are the same.

Because you can rearrange t freely, different arrangements will produce different XOR results. You need to choose the arrangement of t that makes the resulting binary string represent the largest possible integer value.

Return a binary string of length n that represents this maximum integer value obtainable from the bitwise XOR of s and the rearranged t.

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.

Greedychoice formax/min?yesLinkedlist?noGreedyAlgorithms

Greedy left-to-right scan picks opposite bits to maximize the XOR result string.

Open in Flowchart

Intuition

To make a binary string represent the largest integer value, we want as many '1' bits as possible, and we want those '1' bits to appear at the leftmost (highest) positions, since higher positions carry more weight.

Looking at how XOR works: a position in the result becomes '1' only when the bit from s and the bit from t are different. So for each position in s, if we can pair it with a character from t that is the opposite value, we get a '1' at that position.

Since we are free to rearrange t, the order of t doesn't matter by itself — what truly matters is how many '0' characters and how many '1' characters t contains. As long as we have the right kind of character available, we can place it at any position we want.

This naturally leads to a greedy strategy. We scan s from left to right (from the highest bit to the lowest bit). At each position, we first try to use an opposite character from t so that the XOR gives '1'. Because we process positions from left to right, we always spend our valuable "opposite" characters on the higher-weight positions first, which guarantees the maximum value.

If no opposite character is left in t, then we have no choice but to use a character equal to s[i], which makes that position '0'. Either way, we use up exactly one character from t for each position in s, since both strings have the same length n.

By keeping just a simple count of how many '0' and '1' characters remain in t, we can quickly decide what to do at each step, building the maximum result one bit at a time.

Pattern Learn more about Greedy patterns.

Solution Approach

Solution 1: Greedy

We use an array cnt of length 2 to count the number of character '0' and character '1' in string t. Here cnt[0] stores how many '0' characters are available, and cnt[1] stores how many '1' characters are available.

cnt = [0, 0]
for c in t:
    cnt[int(c)] += 1

Next, we prepare an answer array ans of length n, initially filled with '0':

ans = ['0'] * len(s)

Then we iterate through string s. For each character s[i], let x = int(s[i]). We want to find a character in t that is different from s[i] to perform the XOR operation, in order to get a '1' at this position. The opposite of x is x ^ 1:

  • If cnt[x ^ 1] is greater than 0, an opposite character is available. We set the i-th bit of the answer to '1' and decrement cnt[x ^ 1] by one.
  • Otherwise, we can only use a character that is the same as s[i]. In this case the XOR result is 0, so the i-th bit of the answer stays '0', and we decrement cnt[x] by one.
for i, c in enumerate(s):
    x = int(c)
    if cnt[x ^ 1]:
        cnt[x ^ 1] -= 1
        ans[i] = '1'
    else:
        cnt[x] -= 1

Because we scan s from left to right, we always assign the available opposite characters to the highest-weight positions first, which guarantees the maximum integer value.

Finally, we join the answer array into a string and return it:

return ''.join(ans)

The time complexity is O(n), where n is the length of the strings, since we make a constant number of passes over the input. The space complexity is O(n) for storing the answer array (ignoring the output, the extra space for cnt is O(1)).

Example Walkthrough

Let's trace through a small example to see the greedy approach in action.

Input:

  • s = "1010"
  • t = "0011"
  • n = 4

Step 1: Count the characters in t.

We scan t = "0011" and tally how many '0' and '1' characters it contains:

  • '0' appears twice → cnt[0] = 2
  • '1' appears twice → cnt[1] = 2

So cnt = [2, 2].

We also initialize the answer array: ans = ['0', '0', '0', '0'].

Step 2: Scan s from left to right (highest bit to lowest bit).

At each position we try to grab an opposite character from t to produce a '1'.

is[i]xneed x^1cnt[x^1] available?actionans[i]cnt after
0'1'10cnt[0] = 2 �→ yesuse a '0', XOR = 1'1'[1, 2]
1'0'01cnt[1] = 2 �→ yesuse a '1', XOR = 1'1'[1, 1]
2'1'10cnt[0] = 1 �→ yesuse a '0', XOR = 1'1'[0, 1]
3'0'01cnt[1] = 1 �→ yesuse a '1', XOR = 1'1'[0, 0]

In this case every position found an opposite character, so the answer is "1111".

Step 3: Return the result.

Joining the array gives "1111", which is the maximum value 15 — the best possible outcome.


A second example where we run out of opposite characters.

  • s = "1100"
  • t = "1110"

Counting t: cnt = [1, 3] (one '0', three '1's). Start ans = ['0','0','0','0'].

is[i]xneed x^1cnt[x^1]?actionans[i]cnt after
0'1'10cnt[0] = 1 → yesuse '0', XOR = 1'1'[0, 3]
1'1'10cnt[0] = 0 → nouse '1', XOR = 0'0'[0, 2]
2'0'01cnt[1] = 2 → yesuse '1', XOR = 1'1'[0, 1]
3'0'01cnt[1] = 1 → yesuse '1', XOR = 1'1'[0, 0]

The result is "1011".

Notice the key greedy insight at position 1: we had no '0' left to flip s[1] = '1', so that bit became '0'. Because we scanned left to right, we had already spent our single '0' on the higher-weight position 0, where it contributes more to the integer value. Sacrificing the lower position rather than the higher one is exactly what guarantees the maximum result.

Solution Implementation

1class Solution:
2    def maximumXor(self, s: str, t: str) -> str:
3        # Count how many '0's and '1's are available in t.
4        # bit_count[0] -> number of '0', bit_count[1] -> number of '1'
5        bit_count = [0, 0]
6        for ch in t:
7            bit_count[int(ch)] += 1
8
9        # Initialize the result with all '0's, matching the length of s.
10        result = ['0'] * len(s)
11
12        # Greedily process each bit of s.
13        for i, ch in enumerate(s):
14            bit = int(ch)
15            # To get a XOR result of '1', we need the opposite bit (bit ^ 1) from t.
16            if bit_count[bit ^ 1]:
17                # Consume one opposite bit and set this position to '1'.
18                bit_count[bit ^ 1] -= 1
19                result[i] = '1'
20            else:
21                # No opposite bit left, consume the same bit; XOR yields '0'.
22                bit_count[bit] -= 1
23
24        # Combine the list of characters into the final binary string.
25        return ''.join(result)
26
1class Solution {
2    public String maximumXor(String s, String t) {
3        // Count occurrences of '0' and '1' in string t.
4        // digitCount[0] = number of '0' characters, digitCount[1] = number of '1' characters.
5        int[] digitCount = new int[2];
6        for (char c : t.toCharArray()) {
7            digitCount[c - '0']++;
8        }
9
10        // Build the result string that maximizes the XOR with s,
11        // using a greedy strategy: prefer producing a '1' bit whenever possible.
12        char[] result = new char[s.length()];
13        for (int i = 0; i < s.length(); i++) {
14            // Current bit from s at position i.
15            int currentBit = s.charAt(i) - '0';
16
17            // To get a XOR result of '1', we need the opposite bit (currentBit ^ 1).
18            if (digitCount[currentBit ^ 1] > 0) {
19                // Consume one opposite bit, producing a '1' in the XOR result.
20                digitCount[currentBit ^ 1]--;
21                result[i] = '1';
22            } else {
23                // No opposite bit available, so use the same bit, producing a '0'.
24                digitCount[currentBit]--;
25                result[i] = '0';
26            }
27        }
28
29        return new String(result);
30    }
31}
32
1class Solution {
2public:
3    string maximumXor(string s, string t) {
4        // Count the occurrences of '0' and '1' in string t.
5        // count[0] holds the number of '0' characters, count[1] holds the number of '1' characters.
6        int count[2]{};
7        for (char c : t) {
8            count[c - '0']++;
9        }
10
11        // Initialize the answer with all '0' characters, matching the length of s.
12        string ans(s.size(), '0');
13
14        // Greedily try to maximize each XOR bit from left to right.
15        for (int i = 0; i < static_cast<int>(s.size()); i++) {
16            int bit = s[i] - '0';
17
18            // To produce a '1' in the XOR result, we need a character from t
19            // that is the complement of the current bit (bit ^ 1).
20            if (count[bit ^ 1] > 0) {
21                // Consume one complementary character and set this position to '1'.
22                count[bit ^ 1]--;
23                ans[i] = '1';
24            } else {
25                // No complement available, so we must use the same bit,
26                // which yields '0' in the XOR result (already set by default).
27                count[bit]--;
28            }
29        }
30
31        return ans;
32    }
33};
34
1/**
2 * Maximizes the XOR result string by greedily pairing each bit of `s`
3 * with an opposite bit from `t` whenever one is available.
4 *
5 * @param s - The base binary string whose length defines the result length.
6 * @param t - The binary string providing the pool of bits to XOR against.
7 * @returns The lexicographically maximized XOR result as a binary string.
8 */
9function maximumXor(s: string, t: string): string {
10    // bitCount[0] tracks available '0' bits in t, bitCount[1] tracks '1' bits.
11    const bitCount: [number, number] = [0, 0];
12
13    // Tally how many of each bit value exist in t.
14    for (const char of t) {
15        bitCount[Number(char)]++;
16    }
17
18    // Initialize the result with all '0's; each position is the length of s.
19    const answer: string[] = new Array<string>(s.length).fill('0');
20
21    // For each bit in s, attempt to produce a '1' by using an opposite bit from t.
22    for (let i = 0; i < s.length; i++) {
23        const currentBit = Number(s[i]);
24        const oppositeBit = currentBit ^ 1;
25
26        if (bitCount[oppositeBit] > 0) {
27            // An opposite bit is available: XOR yields 1, so consume it.
28            bitCount[oppositeBit]--;
29            answer[i] = '1';
30        } else {
31            // No opposite bit left: must use the same bit, producing 0.
32            bitCount[currentBit]--;
33        }
34    }
35
36    // Combine individual bit characters into the final result string.
37    return answer.join('');
38}
39

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of two sequential loops:

  1. The first loop iterates over string t to count the occurrences of '0' and '1'. This takes O(m) time, where m is the length of t.
  2. The second loop iterates over string s (of length n), performing constant-time operations (integer conversion, XOR, array access, and decrement) at each step. This takes O(n) time.

Since the problem treats n as the dominant length, and the final ''.join(ans) operation also runs in O(n), the overall time complexity is O(n), where n is the length of string s.

Space Complexity: O(n)

The auxiliary space breakdown is as follows:

  • The cnt array uses constant space O(1), as it always holds exactly two elements.
  • The ans list stores n characters, requiring O(n) space.
  • The final string produced by ''.join(ans) also occupies O(n) space.

Therefore, the overall space complexity is O(n), where n is the length of string s.

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

Common Pitfalls

Pitfall 1: Sorting t Instead of Greedily Matching Against s

A very common mistake is to assume that producing the largest XOR result is just a matter of arranging t to be "as large as possible" on its own (e.g., placing all '1's of t at the front). The value we want to maximize is the XOR result s ⊕ t, not t itself.

# WRONG: sorting t in descending order ignores s entirely
t = ''.join(sorted(t, reverse=True))
# then XOR with s... this does NOT maximize s ^ t

This fails because what makes a high-weight bit of the result equal to '1' depends on the corresponding bit of s. At a position where s[i] == '1', you need a '0' from t to get a '1' in the result; at a position where s[i] == '0', you need a '1' from t. Blindly stacking t's '1's at the front ignores this dependency.

Solution: Decide the choice per position of s, from the most significant bit to the least, always trying to place the bit of t that is opposite to s[i] (using bit ^ 1). This is exactly what the greedy loop does, and it is why scanning s left-to-right (high weight first) is essential.


Pitfall 2: Forgetting to Consume the Same Bit When No Opposite Bit Remains

When cnt[bit ^ 1] is exhausted, you still must place some character of t at this position — there is no option to "skip." Forgetting the else branch (decrementing cnt[bit]) leaves stale counts that corrupt later decisions.

# WRONG: missing the else branch
if bit_count[bit ^ 1]:
    bit_count[bit ^ 1] -= 1
    result[i] = '1'
# (no else) -> counts no longer reflect remaining characters

If you omit the else, you may later "spend" a character that should already have been consumed, producing an arrangement that uses more of a character than t actually contains — an invalid result.

Solution: Always handle both cases. When no opposite bit is available, consume a same-valued bit and leave the result position as '0':

else:
    bit_count[bit] -= 1   # result[i] stays '0'

Pitfall 3: Comparing Results as Integers and Losing Leading Zeros

The output must be a binary string of length n. A tempting shortcut is to compute the XOR via int(s, 2) ^ int(t, 2) and convert back, but bin() drops leading zeros and the length may shrink.

# WRONG: leading zeros are lost, length != n
val = int(s, 2) ^ int(t_rearranged, 2)
return bin(val)[2:]          # e.g. '0011' -> '11'

Solution: Build the answer as a fixed-length list/array of characters (['0'] * len(s)) and only set positions to '1'. This guarantees the returned string is always exactly length n, with leading zeros preserved.


Pitfall 4: Greedy Order Confusion (Least vs. Most Significant Bit)

Greedy algorithms are only correct with the right ordering. Here, the leftmost character is the most significant bit. Iterating from the right (or sorting positions incorrectly) would spend the limited opposite bits on low-weight positions, yielding a smaller integer.

# WRONG: processing low-significance bits first wastes opposite bits
for i in range(len(s) - 1, -1, -1):
    ...

Solution: Process s from index 0 upward (for i, ch in enumerate(s)), so each scarce "opposite" character is invested in the highest-value position still available. The exchange argument confirms this: moving a '1' from a lower position to a higher available position never decreases the value.

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:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More