3094. Guess the Number Using Bitwise Questions II 🔒
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:
- It counts how many bit positions have the same value in both
n
andnum
(looking at their binary representations) - It then updates
n
by performing XOR operation:n = n XOR num
- 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
becomes5 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.
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:
-
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 us4
(binary:100
). -
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. -
Determine the original bit value:
- If
count1 > count2
, the i-th bit of the originaln
was 1 - If
count1 < count2
, the i-th bit of the originaln
was 0 - If
count1 == count2
, the i-th bit doesn't affect the count (it was 0)
- If
-
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 ofn
to 1 while keeping all other bits unchanged. -
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 EvaluatorExample 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 iscommonBits(1)
where1
is binary001
- 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 withn = 100
- Before XOR:
n = 100
,num = 001
- Common bits: only position 1 matches → count2 = 1
- Before XOR:
- 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 iscommonBits(2)
where2
is binary010
- 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 withn = 111
- Before XOR:
n = 111
,num = 010
- Common bits: all positions match → count2 = 3
- Before XOR:
- 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 iscommonBits(4)
where4
is binary100
- 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 withn = 001
- Before XOR:
n = 001
,num = 100
- Common bits: only position 1 matches → count2 = 1
- Before XOR:
- 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
andcount_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.
Which technique can we use to find the middle of a linked list?
Recommended Readings
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 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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!