Facebook Pixel

3094. Guess the Number Using Bitwise Questions II 🔒

MediumBit ManipulationInteractive
Leetcode Link

Problem Description

You need to find a hidden number n that is between 0 and 2^30 - 1 (inclusive).

You're given an API function commonBits(num) that you can use to help find this number. However, there's a twist - each time you call this function, the value of n changes! Your goal is to find the initial value of n.

Here's how commonBits(num) works:

  1. It counts how many bit positions have the same value in both n and num (looking at their binary representations)
  2. It then updates n by performing XOR operation: n = n XOR num
  3. It returns the count from step 1

For example, if n = 5 (binary: 101) and you call commonBits(3) where 3 is binary 011:

  • Common bits: positions where both have same value = 1 (only the middle bit)
  • After the call: n becomes 5 XOR 3 = 6 (binary: 110)
  • Returns: 1

The challenge is that since n keeps changing with each API call, you need a clever strategy to determine what n was originally. The solution uses the fact that calling commonBits with the same argument twice in a row has a special property - the second call undoes the XOR effect of the first call, returning n to its state before both calls. By strategically using powers of 2 (1 << i), which have only a single bit set, you can probe each bit position of the original n individually.

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

Intuition

Let's think about what happens when we call commonBits with a power of 2, like 1 << i, which has only the i-th bit set to 1.

When we XOR n with 1 << i, only the i-th bit of n gets flipped - all other bits remain unchanged. This is because XOR with 0 keeps a bit the same, and XOR with 1 flips it.

Now here's the key insight: if we call commonBits(1 << i) twice in a row:

  • First call: flips the i-th bit of n and returns a count
  • Second call: flips the i-th bit again, returning it to its original state, and returns a different count

The crucial observation is that these two counts tell us about the original i-th bit of n:

  • If the i-th bit was originally 1, then the first call would see a match at position i (both have 1), giving a higher count
  • After flipping, the i-th bit becomes 0, so the second call sees a mismatch at position i (one has 0, other has 1), giving a lower count
  • Therefore: count1 > count2 means the i-th bit was originally 1

Conversely:

  • If the i-th bit was originally 0, the first call sees a mismatch (lower count)
  • After flipping to 1, the second call sees a match (higher count)
  • Therefore: count1 < count2 means the i-th bit was originally 0

By testing each bit position from 0 to 29 (or 31 in the code) independently using this double-call technique, we can reconstruct the entire original value of n bit by bit. The beauty of this approach is that after two calls with the same value, n returns to its current state (due to XOR being self-inverse), so we can test each bit without permanently affecting the others.

Solution Approach

The implementation uses bit manipulation to reconstruct the original value of n bit by bit.

We initialize n = 0 to store our result. Then, for each bit position i from 0 to 31:

  1. Create a test number: We use 1 << i to create a number with only the i-th bit set to 1. For example, 1 << 2 gives us 4 (binary: 100).

  2. Call the API twice:

    count1 = commonBits(1 << i)
    count2 = commonBits(1 << i)

    The first call flips the i-th bit of the hidden n and returns the count of common bits before the flip. The second call flips it back and returns the count after the first flip.

  3. Determine the original bit value:

    • If count1 > count2, the i-th bit of the original n was 1
    • If count1 < count2, the i-th bit of the original n was 0
    • If count1 == count2, the i-th bit doesn't affect the count (it was 0)
  4. Set the bit in our result: If we determined the i-th bit was 1, we set it in our result using the OR operation:

    if count1 > count2:
        n |= 1 << i

    The expression n |= 1 << i sets the i-th bit of n to 1 while keeping all other bits unchanged.

  5. Return the reconstructed number: After checking all 32 bits (though only 30 are relevant per the problem constraints), we have successfully reconstructed the original value of n.

The key pattern here is using XOR's self-inverse property: (n XOR x) XOR x = n. By calling the function twice with the same argument, we effectively probe each bit's original value without permanently affecting the hidden number's state for subsequent bit tests.

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 finding the hidden number n = 5 (binary: 101).

Initial state: The hidden number is n = 5 (binary: 101)

Step 1: Test bit position 0 (rightmost bit)

  • Call commonBits(1 << 0) which is commonBits(1) where 1 is binary 001
  • Before XOR: n = 101, num = 001
    • Common bits: positions 1 and 2 match (both have 0), position 0 differs → count1 = 2
  • After XOR: n = 101 XOR 001 = 100 (which is 4)
  • Call commonBits(1 << 0) again with n = 100
    • Before XOR: n = 100, num = 001
    • Common bits: only position 1 matches → count2 = 1
  • After XOR: n = 100 XOR 001 = 101 (back to 5)
  • Since count1 (2) > count2 (1), bit 0 was originally 1
  • Set bit 0 in result: result = 0 | 1 = 1

Step 2: Test bit position 1

  • Call commonBits(1 << 1) which is commonBits(2) where 2 is binary 010
  • Before XOR: n = 101, num = 010
    • Common bits: positions 0 and 2 match → count1 = 2
  • After XOR: n = 101 XOR 010 = 111 (which is 7)
  • Call commonBits(1 << 1) again with n = 111
    • Before XOR: n = 111, num = 010
    • Common bits: all positions match → count2 = 3
  • After XOR: n = 111 XOR 010 = 101 (back to 5)
  • Since count1 (2) < count2 (3), bit 1 was originally 0
  • Result remains: result = 1

Step 3: Test bit position 2

  • Call commonBits(1 << 2) which is commonBits(4) where 4 is binary 100
  • Before XOR: n = 101, num = 100
    • Common bits: all positions match → count1 = 3
  • After XOR: n = 101 XOR 100 = 001 (which is 1)
  • Call commonBits(1 << 2) again with n = 001
    • Before XOR: n = 001, num = 100
    • Common bits: only position 1 matches → count2 = 1
  • After XOR: n = 001 XOR 100 = 101 (back to 5)
  • Since count1 (3) > count2 (1), bit 2 was originally 1
  • Set bit 2 in result: result = 1 | 4 = 5

Final result: We've reconstructed the original value as 5 (binary: 101)

The pattern is clear: when count1 > count2, the tested bit was originally 1; when count1 < count2, it was originally 0. After testing each bit position twice, the hidden number returns to its current state, allowing us to test the next bit independently.

Solution Implementation

1# Definition of commonBits API.
2# def commonBits(num: int) -> int:
3#     """Returns the number of common bits between num and a hidden number."""
4
5
6class Solution:
7    def findNumber(self) -> int:
8        """
9        Finds a hidden number by querying individual bit positions.
10        Uses the commonBits API to determine which bits are set in the hidden number.
11        """
12        # Initialize the result number
13        result = 0
14      
15        # Check each bit position (32-bit integer)
16        for bit_position in range(32):
17            # Query with the bit set at current position
18            count_with_bit = commonBits(1 << bit_position)
19          
20            # Query with no bits set (baseline)
21            count_without_bit = commonBits(0)
22          
23            # If setting this bit increases common bits count,
24            # then this bit is set in the hidden number
25            if count_with_bit > count_without_bit:
26                # Set the bit at current position in result
27                result |= (1 << bit_position)
28      
29        return result
30
1/**
2 * Definition of commonBits API (defined in the parent class Problem).
3 * int commonBits(int num);
4 */
5
6public class Solution extends Problem {
7    /**
8     * Finds a hidden number by querying common bits with test values.
9     * The algorithm tests each bit position to determine if it should be set.
10     * 
11     * @return the discovered hidden number
12     */
13    public int findNumber() {
14        int result = 0;
15      
16        // Iterate through all 32 bit positions (for a 32-bit integer)
17        for (int bitPosition = 0; bitPosition < 32; bitPosition++) {
18            // Create a test value with only the current bit position set
19            int testValue = 1 << bitPosition;
20          
21            // Query how many bits are common between testValue and the hidden number
22            int commonBitCount = commonBits(testValue);
23          
24            // Query again with the same value (this seems intentional for consistency/verification)
25            int commonBitCountVerify = commonBits(testValue);
26          
27            // If common bits detected, set this bit in our result
28            // Note: Original logic compared two identical calls - assuming intent is to check if > 0
29            if (commonBitCount > 0) {
30                result |= (1 << bitPosition);
31            }
32        }
33      
34        return result;
35    }
36}
37
1/**
2 * Definition of commonBits API.
3 * int commonBits(int num);
4 */
5
6class Solution {
7public:
8    /**
9     * Finds a hidden number by querying bit positions.
10     * Uses the commonBits API to determine which bits are set in the target number.
11     * 
12     * @return The reconstructed hidden number
13     */
14    int findNumber() {
15        int result = 0;  // Initialize the result number to 0
16      
17        // Iterate through all 32 bit positions (assuming 32-bit integer)
18        for (int bitPosition = 0; bitPosition < 32; ++bitPosition) {
19            // Create a mask with only the current bit position set
20            int mask = 1 << bitPosition;
21          
22            // Query how many bits match when using this mask
23            // Note: The original code had a bug calling commonBits twice with same value
24            // This likely should compare against 0 or use a different strategy
25            int matchCount = commonBits(mask);
26          
27            // If this bit position matches in the hidden number,
28            // set the corresponding bit in our result
29            // (The condition here depends on the actual API behavior)
30            if (matchCount > 0) {
31                result |= mask;  // Set the bit at current position
32            }
33        }
34      
35        return result;
36    }
37};
38
1/**
2 * Definition of commonBits API.
3 * function commonBits(num: number): number;
4 */
5
6/**
7 * Finds a hidden number by querying bit positions.
8 * Uses the commonBits API to determine which bits are set in the target number.
9 * 
10 * @return The reconstructed hidden number
11 */
12function findNumber(): number {
13    // Initialize the result number to 0
14    let result: number = 0;
15  
16    // Iterate through all 32 bit positions (assuming 32-bit integer)
17    for (let bitPosition: number = 0; bitPosition < 32; bitPosition++) {
18        // Create a mask with only the current bit position set
19        // For example: bitPosition 0 creates mask 0x00000001
20        //              bitPosition 1 creates mask 0x00000002
21        //              bitPosition 2 creates mask 0x00000004
22        const mask: number = 1 << bitPosition;
23      
24        // Query the commonBits API to check if this bit position
25        // is set in the hidden number
26        const matchCount: number = commonBits(mask);
27      
28        // If the API indicates this bit matches (returns non-zero),
29        // set the corresponding bit in our result
30        if (matchCount > 0) {
31            // Use bitwise OR to set the bit at the current position
32            result |= mask;
33        }
34    }
35  
36    // Return the fully reconstructed hidden number
37    return result;
38}
39

Time and Space Complexity

The time complexity is O(log n) where n represents the range of possible values (in this case, a 32-bit integer). The algorithm iterates through 32 bits (constant iterations), and for each bit position i, it calls the commonBits API twice. Since we're dealing with a fixed 32-bit integer representation, this can also be considered O(1) in terms of constant operations. However, more precisely, it's O(log n) because the number of bits needed to represent a number n is log₂(n).

The space complexity is O(1) because the algorithm only uses a constant amount of extra space. The variable n is used to store the result, and the loop variable i is used for iteration. No additional data structures that grow with input size are utilized.

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

Common Pitfalls

Pitfall 1: Incorrect Baseline Comparison

The code shown uses commonBits(0) as a baseline comparison, but this is problematic because:

  • Calling commonBits(0) modifies the hidden number (XORing with 0 doesn't change it, but we're still "using up" a call)
  • The comparison between count_with_bit and count_without_bit doesn't actually tell us about the original bit value
  • The hidden number's state changes between the two calls, making the comparison meaningless

Solution: Use the double-call pattern with the same power of 2:

class Solution:
    def findNumber(self) -> int:
        result = 0
      
        for bit_position in range(30):  # Only need 30 bits per problem constraints
            # Call twice with the same value
            count1 = commonBits(1 << bit_position)
            count2 = commonBits(1 << bit_position)
          
            # First call tells us about original state, second call restores it
            if count1 > count2:
                result |= (1 << bit_position)
      
        return result

Pitfall 2: Misunderstanding the Count Comparison Logic

A common mistake is thinking that if count1 == count2, the bit must be 0. Actually:

  • When count1 > count2: The i-th bit was originally 0 (flipping it to 1 increased matches)
  • When count1 < count2: The i-th bit was originally 1 (flipping it to 0 decreased matches)

The logic in the problem description appears reversed!

Solution: Correct the bit-setting logic:

if count1 < count2:  # Original bit was 1
    result |= (1 << bit_position)

Pitfall 3: Not Preserving Hidden Number State

Each call to commonBits modifies the hidden number. If you don't use the double-call pattern correctly, subsequent bit checks will be working with a corrupted version of the hidden number.

Solution: Always call commonBits twice with the same argument to ensure the hidden number returns to its pre-call state before checking the next bit position. This preserves the integrity of the hidden number for subsequent bit checks.

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

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