1545. Find Kth Bit in Nth Binary String
Problem Description
You need to find the k-th bit in a special binary string S_n that is constructed using a recursive pattern.
The binary strings are built following these rules:
- Sβ = "0" (base case)
- For i > 1: S_i = S_{i-1} + "1" + reverse(invert(S_{i-1}))
Where:
+
means string concatenationreverse(x)
reverses the string xinvert(x)
flips all bits (0 becomes 1, 1 becomes 0)
For example, here's how the first four strings are constructed:
- Sβ = "0"
- Sβ = "0" + "1" + reverse(invert("0")) = "0" + "1" + reverse("1") = "0" + "1" + "1" = "011"
- Sβ = "011" + "1" + reverse(invert("011")) = "011" + "1" + reverse("100") = "011" + "1" + "001" = "0111001"
- Sβ = "0111001" + "1" + reverse(invert("0111001")) = "0111001" + "1" + "0110001" = "011100110110001"
Given two positive integers n
and k
, you need to return the k-th bit (1-indexed) in the string S_n. The problem guarantees that k is a valid position in S_n.
The solution uses a recursive approach by recognizing that:
- Each S_n has length
2^n - 1
- The middle bit is always "1" (at position
2^(n-1)
) - The first half (before the middle) is identical to S_{n-1}
- The second half (after the middle) is the reverse and inversion of S_{n-1}
This pattern allows us to recursively determine any bit's value without constructing the entire string.
Intuition
Let's first observe the pattern in how these strings are constructed. Each string S_n follows the pattern: S_{n-1} + "1" + reverse(invert(S_{n-1}))
. This creates a symmetric structure with a "1" in the middle.
Looking at the lengths:
- Sβ has length 1 =
2^1 - 1
- Sβ has length 3 =
2^2 - 1
- Sβ has length 7 =
2^3 - 1
- Sβ has length 15 =
2^4 - 1
So S_n has length 2^n - 1
. The middle position is always at index 2^(n-1)
, and this position always contains "1".
The key insight is that we don't need to build the entire string to find the k-th bit. Instead, we can use the recursive structure to navigate directly to the position we need:
-
If k = 1: The first bit is always "0" (since Sβ = "0" and every subsequent string starts with the previous string).
-
If k is a power of 2: These positions correspond to the middle "1" bits added at each level of construction. For example, position 2 in Sβ, position 4 in Sβ, position 8 in Sβ, etc.
-
If k is in the first half (k <
2^(n-1)
): The first half of S_n is exactly S_{n-1}, so we can recursively find the k-th bit in S_{n-1}. -
If k is in the second half (k >
2^(n-1)
): The second half isreverse(invert(S_{n-1}))
. To find the corresponding position in S_{n-1}, we need to:- Map k to its mirror position in S_{n-1}: position
2^n - k
- Since it's inverted, we flip the bit we find (XOR with 1)
- Map k to its mirror position in S_{n-1}: position
This recursive approach allows us to "zoom in" on the exact bit we need without constructing the entire string, making it very efficient. Each recursive call reduces the problem size by examining which half of the string contains our target position, similar to binary search.
Learn more about Recursion patterns.
Solution Approach
The solution implements a recursive function dfs(n, k)
that returns the k-th bit in string S_n without constructing the entire string.
Core Algorithm:
The function handles four distinct cases based on the position k:
-
Base Case (k = 1):
- Return
0
directly since every S_n starts with "0"
- Return
-
Power of 2 Check (
(k & (k - 1)) == 0
):- This bit manipulation trick checks if k is a power of 2
- Powers of 2 represent the middle "1" bits added at each construction level
- Return
1
for these positions
-
First Half Check (
k * 2 < m - 1
wherem = 1 << n
=2^n
):- Since S_n has length
2^n - 1
, the middle position is at2^(n-1)
- If
k * 2 < 2^n - 1
, then k is in the first half - The first half is identical to S_{n-1}, so recursively call
dfs(n - 1, k)
- Since S_n has length
-
Second Half (remaining cases):
- The second half is
reverse(invert(S_{n-1}))
- To find the corresponding position in S_{n-1}, calculate the mirror position:
2^n - k
- Since the bit is inverted in the second half, XOR the result with 1:
dfs(n - 1, m - k) ^ 1
- The second half is
Implementation Details:
m = 1 << n
efficiently calculates2^n
using bit shifting(k & (k - 1)) == 0
is a classic bit manipulation to check if k is a power of 2- For powers of 2, only one bit is set, so subtracting 1 flips all lower bits
- ANDing with the original number gives 0 only for powers of 2
- The XOR operation (
^ 1
) flips the bit: 0 becomes 1, and 1 becomes 0
Time Complexity: O(n) - At most n recursive calls since we reduce n by 1 in each call
Space Complexity: O(n) - Recursion stack depth is at most n
The final answer is converted to string format using str(dfs(n, k))
before returning.
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 find the 5th bit in Sβ = "0111001" using our recursive approach.
Step 1: Initial call - dfs(3, 5)
- Check if k = 1? No (k = 5)
- Check if k is power of 2? No (5 & 4 = 4, not 0)
- Calculate m = 1 << 3 = 8
- Check if in first half: 5 * 2 = 10, compare with 8 - 1 = 7
- Since 10 > 7, k = 5 is in the second half
Step 2: Map to mirror position in Sβ
- Since k is in the second half of Sβ, we need to find its mirror in Sβ
- Mirror position = m - k = 8 - 5 = 3
- Call
dfs(2, 3) ^ 1
(XOR with 1 because second half is inverted)
Step 3: Recursive call - dfs(2, 3)
- Check if k = 1? No (k = 3)
- Check if k is power of 2? No (3 & 2 = 2, not 0)
- Calculate m = 1 << 2 = 4
- Check if in first half: 3 * 2 = 6, compare with 4 - 1 = 3
- Since 6 > 3, k = 3 is in the second half
Step 4: Map to mirror position in Sβ
- Mirror position = m - k = 4 - 3 = 1
- Call
dfs(1, 1) ^ 1
Step 5: Base case - dfs(1, 1)
- k = 1, so return 0 (first bit is always 0)
Step 6: Backtrack with inversions
dfs(1, 1)
returns 0dfs(2, 3)
returns 0 ^ 1 = 1dfs(3, 5)
returns 1 ^ 1 = 0
Verification: Sβ = "0111001", and the 5th bit (1-indexed) is indeed '0'.
The algorithm correctly navigated through the recursive structure without building the entire string, using the symmetry and inversion properties to find the exact bit value.
Solution Implementation
1class Solution:
2 def findKthBit(self, n: int, k: int) -> str:
3 """
4 Find the k-th bit in the n-th binary string S_n.
5
6 The string S_n is constructed recursively:
7 - S_1 = "0"
8 - S_i = S_(i-1) + "1" + reverse(invert(S_(i-1)))
9
10 Args:
11 n: The index of the string S_n (1 <= n <= 20)
12 k: The position of the bit to find (1-indexed)
13
14 Returns:
15 The k-th bit as a string ("0" or "1")
16 """
17
18 def find_bit_recursive(level: int, position: int) -> int:
19 """
20 Recursively find the bit at given position in S_level.
21
22 Args:
23 level: Current level of the string S_level
24 position: Position to find (1-indexed)
25
26 Returns:
27 The bit value (0 or 1) as an integer
28 """
29 # Base case: first bit is always 0
30 if position == 1:
31 return 0
32
33 # Check if position is a power of 2 (middle positions are always 1)
34 # Using bitwise AND: if k & (k-1) == 0, then k is a power of 2
35 if (position & (position - 1)) == 0:
36 return 1
37
38 # Calculate the length of S_level (2^level - 1)
39 string_length = 1 << level # 2^level
40
41 # If position is in the first half (before middle)
42 if position * 2 < string_length - 1:
43 # The bit is the same as in S_(level-1)
44 return find_bit_recursive(level - 1, position)
45
46 # If position is in the second half (after middle)
47 # Find the corresponding position in S_(level-1) and invert
48 # The second half is reverse(invert(S_(level-1)))
49 mirrored_position = string_length - position
50 return find_bit_recursive(level - 1, mirrored_position) ^ 1 # XOR 1 to invert
51
52 # Convert the result from integer (0 or 1) to string
53 return str(find_bit_recursive(n, k))
54
1class Solution {
2 /**
3 * Finds the k-th bit in the n-th binary string S_n.
4 * The binary string is constructed recursively where:
5 * S_1 = "0"
6 * S_i = S_(i-1) + "1" + reverse(invert(S_(i-1)))
7 *
8 * @param n the index of the binary string (1 to 20)
9 * @param k the position of the bit to find (1-indexed)
10 * @return the k-th bit as a character ('0' or '1')
11 */
12 public char findKthBit(int n, int k) {
13 // Convert the numeric result (0 or 1) to character ('0' or '1')
14 return (char) ('0' + dfs(n, k));
15 }
16
17 /**
18 * Recursively determines the value of the k-th bit in S_n.
19 *
20 * @param n the index of the binary string
21 * @param k the position of the bit (1-indexed)
22 * @return 0 or 1 representing the bit value
23 */
24 private int dfs(int n, int k) {
25 // Base case: first position is always '0'
26 if (k == 1) {
27 return 0;
28 }
29
30 // Check if k is a power of 2 (middle positions are always '1')
31 // k & (k - 1) equals 0 only when k is a power of 2
32 if ((k & (k - 1)) == 0) {
33 return 1;
34 }
35
36 // Calculate the total length of S_n: 2^n - 1
37 int totalLength = 1 << n; // 2^n
38
39 // If k is in the first half (before middle), recurse on S_(n-1)
40 if (k * 2 < totalLength - 1) {
41 return dfs(n - 1, k);
42 }
43
44 // If k is in the second half (after middle), find corresponding position
45 // in S_(n-1) and invert the result (XOR with 1)
46 // The second half is reverse(invert(S_(n-1))), so we map k to its mirror position
47 return dfs(n - 1, totalLength - k) ^ 1;
48 }
49}
50
1class Solution {
2public:
3 char findKthBit(int n, int k) {
4 // Lambda function to recursively find the bit value at position k in string Sn
5 function<int(int, int)> dfs = [&](int n, int k) {
6 // Base case: The first bit is always 0
7 if (k == 1) {
8 return 0;
9 }
10
11 // Check if k is a power of 2 (middle position)
12 // When k is a power of 2, it represents the middle '1' in the pattern
13 if ((k & (k - 1)) == 0) {
14 return 1;
15 }
16
17 // Calculate the total length of string Sn
18 // Length of Sn = 2^n - 1
19 int totalLength = 1 << n;
20
21 // If k is in the first half (before middle)
22 // The first half is identical to Sn-1
23 if (k * 2 < totalLength - 1) {
24 return dfs(n - 1, k);
25 }
26
27 // If k is in the second half (after middle)
28 // The second half is the reverse and invert of Sn-1
29 // Find the corresponding position in Sn-1 and invert the result
30 return dfs(n - 1, totalLength - k) ^ 1;
31 };
32
33 // Convert the bit value (0 or 1) to character ('0' or '1')
34 return '0' + dfs(n, k);
35 }
36};
37
1/**
2 * Finds the k-th bit in the n-th binary string constructed using a specific pattern.
3 * The pattern builds strings where S_n = S_(n-1) + "1" + reverse(invert(S_(n-1)))
4 * Starting with S_1 = "0"
5 *
6 * @param n - The index of the binary string to construct (1-indexed)
7 * @param k - The position of the bit to find (1-indexed)
8 * @returns The k-th bit as a string ("0" or "1")
9 */
10function findKthBit(n: number, k: number): string {
11 /**
12 * Recursive helper function to determine the bit value at position k in string S_n
13 *
14 * @param n - Current string index
15 * @param k - Current bit position to find
16 * @returns The bit value (0 or 1) as a number
17 */
18 const dfs = (n: number, k: number): number => {
19 // Base case: First bit is always 0
20 if (k === 1) {
21 return 0;
22 }
23
24 // Check if k is a power of 2 (middle positions are always 1)
25 // Using bitwise AND: if k & (k-1) equals 0, then k is a power of 2
26 if ((k & (k - 1)) === 0) {
27 return 1;
28 }
29
30 // Calculate the total length of string S_n (2^n - 1)
31 const totalLength = 1 << n; // 2^n
32
33 // If k is in the first half of the string (excluding middle)
34 if (k * 2 < totalLength - 1) {
35 // The bit is the same as in S_(n-1)
36 return dfs(n - 1, k);
37 }
38
39 // If k is in the second half of the string
40 // Find the corresponding position in S_(n-1) and invert the result
41 return dfs(n - 1, totalLength - k) ^ 1; // XOR with 1 to invert
42 };
43
44 // Convert the numeric result (0 or 1) to string and return
45 return dfs(n, k).toString();
46}
47
Time and Space Complexity
The time complexity is O(n)
, where n
is the given parameter in the problem.
The recursive function dfs
makes at most one recursive call per level, and the recursion depth is bounded by n
. At each recursive call, the operations performed are constant time:
- Checking if
k == 1
:O(1)
- Checking if
(k & (k - 1)) == 0
to determine ifk
is a power of 2:O(1)
- Computing
m = 1 << n
:O(1)
- Comparison
k * 2 < m - 1
:O(1)
- The recursive calls either decrease
n
by 1 or maintain the same recursion path
Since we make at most n
recursive calls (decreasing n
by 1 each time until we reach a base case), and each call performs O(1)
work, the total time complexity is O(n)
.
The space complexity is O(n)
due to the recursion call stack. In the worst case, the recursion depth can reach n
levels deep (when we keep recursing with n-1
), and each recursive call adds a constant amount of space to the call stack. Therefore, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Middle Position Calculation
Pitfall: A common mistake is incorrectly identifying whether a position is in the first or second half of the string. Developers often confuse the middle position calculation or use incorrect comparison operators.
Incorrect Implementation:
# Wrong: Using <= instead of < if position * 2 <= string_length - 1: # This incorrectly includes the middle position return find_bit_recursive(level - 1, position) # Wrong: Incorrect middle position calculation middle = (1 << level) // 2 # This gives 2^(level-1), not the actual middle position if position < middle: return find_bit_recursive(level - 1, position)
Solution:
The correct approach uses position * 2 < string_length - 1
to check if a position is in the first half. This works because:
- The string S_n has length
2^n - 1
- The middle position is at
2^(n-1)
(which is(2^n - 1 + 1) / 2
) - If
k * 2 < 2^n - 1
, then k is strictly before the middle
2. Incorrect Power of 2 Check
Pitfall: Using inefficient or incorrect methods to check if a number is a power of 2.
Incorrect Implementation:
# Wrong: This checks if position equals 2^(level-1), missing other power of 2 positions
if position == (1 << (level - 1)):
return 1
# Inefficient: Using logarithm (can have floating-point precision issues)
import math
if math.log2(position) == int(math.log2(position)):
return 1
Solution:
Use the bitwise trick (position & (position - 1)) == 0
. This works because:
- Powers of 2 have exactly one bit set (e.g., 8 = 1000 in binary)
- Subtracting 1 flips all bits after the single set bit (e.g., 7 = 0111)
- ANDing them gives 0 only for powers of 2
3. Forgetting to Invert Bits in Second Half
Pitfall: When handling positions in the second half, forgetting that the bits are both reversed AND inverted.
Incorrect Implementation:
# Wrong: Only reversing without inverting if position * 2 >= string_length: mirrored_position = string_length - position return find_bit_recursive(level - 1, mirrored_position) # Missing XOR 1
Solution:
Always remember to XOR with 1 when dealing with the second half: return find_bit_recursive(level - 1, mirrored_position) ^ 1
4. Using 0-Indexed Instead of 1-Indexed Positions
Pitfall: The problem uses 1-indexed positions, but developers might accidentally use 0-indexed logic.
Incorrect Implementation:
def findKthBit(self, n: int, k: int) -> str:
# Wrong: Converting to 0-indexed but forgetting to adjust back
k = k - 1 # Convert to 0-indexed
result = find_bit_recursive(n, k) # But the recursive function expects 1-indexed!
return str(result)
Solution:
Keep the position 1-indexed throughout the recursive function, as the base case if position == 1: return 0
relies on 1-indexing.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Donβt Miss This!