Facebook Pixel

2546. Apply Bitwise Operations to Make Strings Equal

MediumBit ManipulationString
Leetcode Link

Problem Description

You are given two binary strings s and target of the same length n, both containing only '0's and '1's. Your goal is to transform string s into string target using a specific operation that can be applied any number of times.

The allowed operation works as follows:

  • Select two different indices i and j (where 0 <= i, j < n and i ≠ j)
  • Simultaneously update both positions:
    • Replace s[i] with the result of s[i] OR s[j]
    • Replace s[j] with the result of s[i] XOR s[j]

For example, starting with s = "0110":

  • If you choose i = 0 and j = 2
  • s[0] becomes 0 OR 1 = 1
  • s[2] becomes 0 XOR 1 = 1
  • The resulting string is s = "1110"

The operation behavior for all possible bit combinations:

  • (0, 0)(0, 0) - no change when both bits are 0
  • (0, 1)(1, 1) - transforms to both 1s
  • (1, 0)(1, 1) - transforms to both 1s
  • (1, 1)(1, 0) - first stays 1, second becomes 0

The key insight is that having at least one '1' in the string acts as a "tool" that enables transformations. Once a string contains a '1', it will always contain at least one '1' after any operation. Conversely, if a string contains only '0's, no operation can introduce a '1'.

Therefore, transformation from s to target is possible if and only if both strings either contain at least one '1', or both contain only '0's.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's carefully analyze what happens during each operation by examining all possible bit combinations:

  • When we apply the operation on bits (0, 0): They become (0 OR 0, 0 XOR 0) = (0, 0) - no change
  • When we apply the operation on bits (0, 1): They become (0 OR 1, 0 XOR 1) = (1, 1)
  • When we apply the operation on bits (1, 0): They become (1 OR 0, 1 XOR 0) = (1, 1)
  • When we apply the operation on bits (1, 1): They become (1 OR 1, 1 XOR 1) = (1, 0)

The crucial observation is that once you have at least one '1' in your string, you can never eliminate all '1's. Why? Because:

  • Operating on (0, 1) or (1, 0) produces two '1's
  • Operating on (1, 1) keeps one '1' and creates one '0'
  • Operating on (0, 0) doesn't create any '1's

This means '1' acts like a catalyst or tool - once present, it persists in the string (though it may move around or multiply).

Conversely, if your string contains only '0's, you can never create a '1' since operating on (0, 0) yields (0, 0).

This leads to a fundamental invariant: the presence or absence of '1' in a string is preserved through all operations. A string with at least one '1' will always have at least one '1' after any sequence of operations, and a string with no '1's will never gain a '1'.

Therefore, we can only transform string s into string target if they belong to the same "class":

  • Both contain at least one '1', OR
  • Both contain only '0's

This explains why the solution is simply checking whether ("1" in s) == ("1" in target).

Solution Approach

Based on our understanding that the presence of '1' in a string is an invariant property, the solution becomes remarkably simple. We need to check if both strings belong to the same category - either both have at least one '1', or both have only '0's.

The implementation uses a direct comparison:

class Solution:
    def makeStringsEqual(self, s: str, target: str) -> bool:
        return ("1" in s) == ("1" in target)

Here's how this works:

  1. Check for '1' in s: The expression "1" in s returns True if string s contains at least one '1', and False if it contains only '0's.

  2. Check for '1' in target: Similarly, "1" in target returns True if string target contains at least one '1', and False otherwise.

  3. Compare the results: Using the equality operator ==, we check if both expressions yield the same boolean value:

    • If both are True (both strings have '1's), the function returns True
    • If both are False (both strings have only '0's), the function returns True
    • If one is True and the other is False, the function returns False

This elegant one-liner solution leverages Python's in operator for substring checking, which internally performs a linear scan of the string. The time complexity is O(n) where n is the length of the strings, and the space complexity is O(1) as we only use constant extra space for the boolean comparisons.

The beauty of this solution lies in recognizing that we don't need to simulate the actual transformations or find the specific sequence of operations. We only need to verify that both strings share the same fundamental property - the presence or absence of '1'.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate why checking for the presence of '1' determines if transformation is possible.

Example 1: s = "0011", target = "1100"

First, let's check our condition:

  • "1" in s = True (s contains '1' at positions 2 and 3)
  • "1" in target = True (target contains '1' at positions 0 and 1)
  • Since both are True, transformation is possible ✓

Let's verify by actually transforming s to target:

Starting with s = "0011"

  1. Apply operation on indices i=2, j=0:

    • s[2] = 1, s[0] = 0
    • s[0] becomes 0 OR 1 = 1
    • s[2] becomes 0 XOR 1 = 1
    • Result: s = "1011"
  2. Apply operation on indices i=3, j=1:

    • s[3] = 1, s[1] = 0
    • s[1] becomes 0 OR 1 = 1
    • s[3] becomes 0 XOR 1 = 1
    • Result: s = "1111"
  3. Apply operation on indices i=2, j=3:

    • s[2] = 1, s[3] = 1
    • s[2] becomes 1 OR 1 = 1
    • s[3] becomes 1 XOR 1 = 0
    • Result: s = "1110"
  4. Apply operation on indices i=2, j=3:

    • s[2] = 1, s[3] = 0
    • s[2] becomes 1 OR 0 = 1
    • s[3] becomes 1 XOR 0 = 1
    • Result: s = "1111"
  5. Apply operation on indices i=3, j=2:

    • s[3] = 1, s[2] = 1
    • s[2] becomes 1 OR 1 = 1
    • s[3] becomes 1 XOR 1 = 0
    • Result: s = "1110"
  6. Apply operation on indices i=2, j=3:

    • s[2] = 1, s[3] = 0
    • s[2] becomes 1 OR 0 = 1
    • s[3] becomes 1 XOR 0 = 1
    • Result: s = "1111"
  7. Apply operation on indices i=3, j=2:

    • s[3] = 1, s[2] = 1
    • s[2] becomes 1 OR 1 = 1
    • s[3] becomes 1 XOR 1 = 0
    • Result: s = "1110"
  8. Apply operation on indices i=1, j=2:

    • s[1] = 1, s[2] = 1
    • s[1] becomes 1 OR 1 = 1
    • s[2] becomes 1 XOR 1 = 0
    • Result: s = "1100" ✓

We successfully transformed s into target!

Example 2: s = "0000", target = "0101"

Check our condition:

  • "1" in s = False (s contains no '1's)
  • "1" in target = True (target contains '1' at positions 1 and 3)
  • Since one is False and the other is True, transformation is impossible ✗

Let's verify why it's impossible:

  • Starting with s = "0000", any operation on indices i and j will be on bits (0,0)
  • (0,0) transforms to (0 OR 0, 0 XOR 0) = (0,0)
  • No matter how many operations we perform, we can never create a '1'
  • Therefore, we can never reach target = "0101" which contains '1's

The key insight: once we know whether each string contains at least one '1', we immediately know if transformation is possible without needing to find the actual sequence of operations.

Solution Implementation

1class Solution:
2    def makeStringsEqual(self, s: str, target: str) -> bool:
3        """
4        Determines if string s can be transformed into target string.
5      
6        The key insight: transformation is possible if and only if both strings
7        either contain at least one '1' or both contain no '1's.
8      
9        Args:
10            s: The source string containing only '0' and '1'
11            target: The target string containing only '0' and '1'
12          
13        Returns:
14            bool: True if s can be transformed into target, False otherwise
15        """
16        # Check if the presence of '1' is the same in both strings
17        # Both must have at least one '1', or both must have no '1's
18        has_one_in_s = "1" in s
19        has_one_in_target = "1" in target
20      
21        return has_one_in_s == has_one_in_target
22
1class Solution {
2    /**
3     * Determines if string s can be transformed into target string through valid operations.
4     * 
5     * The key insight: A string containing at least one '1' can be transformed into 
6     * any other string that also contains at least one '1'. A string with all '0's 
7     * can only match another string with all '0's.
8     * 
9     * @param s      The source binary string to transform
10     * @param target The target binary string to achieve
11     * @return true if s can be transformed into target, false otherwise
12     */
13    public boolean makeStringsEqual(String s, String target) {
14        // Check if both strings have the same "1" presence status
15        // If both have at least one '1', transformation is possible
16        // If both have no '1's (all zeros), they can match
17        // If one has '1' and other doesn't, transformation is impossible
18        boolean sourceHasOne = s.contains("1");
19        boolean targetHasOne = target.contains("1");
20      
21        return sourceHasOne == targetHasOne;
22    }
23}
24
1class Solution {
2public:
3    bool makeStringsEqual(string s, string target) {
4        // Check if string s contains at least one '1'
5        bool sHasOne = count(s.begin(), s.end(), '1') > 0;
6      
7        // Check if target string contains at least one '1'
8        bool targetHasOne = count(target.begin(), target.end(), '1') > 0;
9      
10        // Two strings can be made equal if and only if:
11        // - Both have at least one '1', OR
12        // - Both have no '1's (all '0's)
13        // This is because operations require at least one '1' to transform bits
14        return sHasOne == targetHasOne;
15    }
16};
17
1/**
2 * Determines if string s can be transformed into target string through operations.
3 * The key insight is that any string containing '1' can be transformed into any other string containing '1',
4 * and a string with all '0's can only remain as all '0's.
5 * 
6 * @param s - The source binary string
7 * @param target - The target binary string to transform into
8 * @returns true if transformation is possible, false otherwise
9 */
10function makeStringsEqual(s: string, target: string): boolean {
11    // Check if both strings either contain '1' or both don't contain '1'
12    // If both have at least one '1', transformation is possible
13    // If both have no '1's (all zeros), they must already be equal
14    return s.includes('1') === target.includes('1');
15}
16

Time and Space Complexity

The time complexity is O(n), where n is the length of the string. This is because the in operator needs to traverse through the string to check if the character "1" exists. In the worst case, it may need to scan the entire string (when "1" is at the end or doesn't exist at all). Since we perform this operation on both s and target, the total time complexity is O(n) + O(m), where n and m are the lengths of strings s and target respectively. However, if we assume both strings have the same or similar length n, the overall time complexity simplifies to O(n).

The space complexity is O(1). The algorithm only uses a constant amount of extra space regardless of the input size. The in operator performs the search without creating any additional data structures, and the comparison operation == also uses constant space. No auxiliary arrays, lists, or other data structures that scale with input size are created during execution.

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

Common Pitfalls

1. Attempting to Simulate the Operations

A natural but inefficient approach is to try simulating the actual transformation operations to see if you can reach the target string. This leads to unnecessary complexity:

# WRONG APPROACH - Overly Complex
class Solution:
    def makeStringsEqual(self, s: str, target: str) -> bool:
        if s == target:
            return True
      
        s_list = list(s)
        visited = {s}
        queue = [s_list]
      
        # BFS to try all possible operations
        while queue:
            current = queue.pop(0)
            for i in range(len(current)):
                for j in range(i + 1, len(current)):
                    new_state = current[:]
                    # Apply OR and XOR operations
                    or_result = str(int(current[i]) | int(current[j]))
                    xor_result = str(int(current[i]) ^ int(current[j]))
                    new_state[i] = or_result
                    new_state[j] = xor_result
                  
                    new_string = ''.join(new_state)
                    if new_string == target:
                        return True
                    if new_string not in visited:
                        visited.add(new_string)
                        queue.append(new_state)
      
        return False

Why it's wrong: This approach has exponential time complexity and will time out for larger inputs. It also misses the fundamental insight that makes the problem simple.

2. Misunderstanding the Operation's Effect on Bit Count

Some might think that the total number of '1's in the string is preserved or follows a specific pattern:

# WRONG APPROACH - Incorrect Assumption
class Solution:
    def makeStringsEqual(self, s: str, target: str) -> bool:
        # Incorrectly assuming the count of '1's matters
        return s.count('1') == target.count('1')

Why it's wrong: The operation can change the count of '1's. For example:

  • "10" can become "11" (increasing '1' count)
  • "11" can become "10" (decreasing '1' count)

3. Overthinking Edge Cases

Some might add unnecessary checks for edge cases that are already handled by the main logic:

# UNNECESSARILY COMPLEX
class Solution:
    def makeStringsEqual(self, s: str, target: str) -> bool:
        # These checks are redundant
        if s == target:
            return True
        if len(s) != len(target):
            return False
        if s == "0" * len(s) and target != "0" * len(s):
            return False
        if s != "0" * len(s) and target == "0" * len(s):
            return False
          
        return ("1" in s) == ("1" in target)

Why it's wrong: The simple check ("1" in s) == ("1" in target) already handles all these cases correctly. Adding extra conditions only makes the code harder to read and maintain.

Correct Solution

The key is recognizing the invariant: once a string has at least one '1', it will always have at least one '1' after any operation. The solution is elegantly simple:

class Solution:
    def makeStringsEqual(self, s: str, target: str) -> bool:
        return ("1" in s) == ("1" in target)

This solution is both correct and optimal with O(n) time complexity and O(1) space complexity.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More