3849. Maximum Bitwise XOR After Rearrangement
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.
How We Pick the Algorithm
Why Greedy Algorithms?
This problem maps to Greedy Algorithms through a short path in the full flowchart.
Greedy left-to-right scan picks opposite bits to maximize the XOR result string.
Open in FlowchartIntuition
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 than0, an opposite character is available. We set thei-th bit of the answer to'1'and decrementcnt[x ^ 1]by one. - Otherwise, we can only use a character that is the same as
s[i]. In this case the XOR result is0, so thei-th bit of the answer stays'0', and we decrementcnt[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'.
i | s[i] | x | need x^1 | cnt[x^1] available? | action | ans[i] | cnt after |
|---|---|---|---|---|---|---|---|
| 0 | '1' | 1 | 0 | cnt[0] = 2 �→ yes | use a '0', XOR = 1 | '1' | [1, 2] |
| 1 | '0' | 0 | 1 | cnt[1] = 2 �→ yes | use a '1', XOR = 1 | '1' | [1, 1] |
| 2 | '1' | 1 | 0 | cnt[0] = 1 �→ yes | use a '0', XOR = 1 | '1' | [0, 1] |
| 3 | '0' | 0 | 1 | cnt[1] = 1 �→ yes | use 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'].
i | s[i] | x | need x^1 | cnt[x^1]? | action | ans[i] | cnt after |
|---|---|---|---|---|---|---|---|
| 0 | '1' | 1 | 0 | cnt[0] = 1 → yes | use '0', XOR = 1 | '1' | [0, 3] |
| 1 | '1' | 1 | 0 | cnt[0] = 0 → no | use '1', XOR = 0 | '0' | [0, 2] |
| 2 | '0' | 0 | 1 | cnt[1] = 2 → yes | use '1', XOR = 1 | '1' | [0, 1] |
| 3 | '0' | 0 | 1 | cnt[1] = 1 → yes | use '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)
261class 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}
321class 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};
341/**
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}
39Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two sequential loops:
- The first loop iterates over string
tto count the occurrences of'0'and'1'. This takesO(m)time, wheremis the length oft. - The second loop iterates over string
s(of lengthn), performing constant-time operations (integer conversion, XOR, array access, and decrement) at each step. This takesO(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
cntarray uses constant spaceO(1), as it always holds exactly two elements. - The
anslist storesncharacters, requiringO(n)space. - The final string produced by
''.join(ans)also occupiesO(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 RoadmapWhich technique can we use to find the middle of a linked list?
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
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!