3307. Find the K-th Character in String Game II
Problem Description
Alice and Bob are playing a game where Alice starts with a string word = "a"
.
Given a positive integer k
and an integer array operations
, Bob asks Alice to perform all operations in sequence. Each operations[i]
determines the type of operation:
-
If
operations[i] == 0
: Append a copy of the currentword
to itself. For example,"a"
becomes"aa"
, and"ab"
becomes"abab"
. -
If
operations[i] == 1
: Generate a new string by changing each character inword
to its next character in the English alphabet, then append this new string to the originalword
. For example,"c"
becomes"cd"
(c + next(c)), and"zb"
becomes"zbac"
(zb + next(zb) where next(z)=a, next(b)=c).
The character 'z'
wraps around to 'a'
when applying the second type of operation.
After performing all operations in order, return the character at position k
(1-indexed) in the final string.
Example walkthrough:
- Start with
word = "a"
- If
operations = [0, 1]
andk = 3
:- After operation 0 (copy):
"a"
→"aa"
- After operation 1 (shift and append):
"aa"
→"aabc"
(aa + shift(aa) where shift(a)=b, shift(a)=b) - The 3rd character is
'b'
- After operation 0 (copy):
The solution uses the fact that each operation doubles the string length. By tracking which operations affect position k
while backtracking from the final length, we can determine the character without building the entire string.
Intuition
The key observation is that we don't need to build the entire string - we only need to find the character at position k
. Since each operation doubles the string length, after i
operations, the string has length 2^i
.
Think of the string growth as a binary tree structure. Each operation creates two halves: the left half (original) and the right half (newly appended). For operation type 0, both halves are identical. For operation type 1, the right half is the left half with each character shifted by 1.
Instead of building forward (which could create an exponentially large string), we can work backward from position k
. We first determine how many operations are needed to create a string of length at least k
. This gives us the final string length n
.
Now we trace back through the operations in reverse. At each step, we ask: "Is position k
in the left half or right half of the current string?"
- If
k > n/2
(right half): Positionk
came from positionk - n/2
in the left half. If this was a type 1 operation, the character was shifted, so we track this shift with a counterd
. - If
k ≤ n/2
(left half): Positionk
stays the same, unaffected by this operation.
We continue halving n
and adjusting k
until we reach the original string "a"
. The accumulated shifts in d
tell us how many times the character 'a' was shifted forward in the alphabet.
The final answer is chr((d % 26) + ord('a'))
, where we use modulo 26 to handle the wraparound from 'z' to 'a'.
This approach is efficient because we only need O(log k)
operations instead of actually building a string of length 2^i
.
Solution Approach
The implementation follows the backtracking strategy described in the intuition:
Step 1: Find the required string length
n, i = 1, 0 while n < k: n *= 2 i += 1
We start with n = 1
(initial string "a") and keep doubling it until n >= k
. The variable i
tracks how many operations we need to perform to reach this length.
Step 2: Initialize shift counter
d = 0
This counter tracks the total number of character shifts accumulated as we trace back through the operations.
Step 3: Backtrack through operations
while n > 1: if k > n // 2: k -= n // 2 d += operations[i - 1] n //= 2 i -= 1
For each operation (working backward):
- Check if
k
is in the right half (k > n // 2
) - If yes:
- Update
k
to its corresponding position in the left half:k -= n // 2
- If
operations[i - 1] == 1
, add 1 to the shift counterd
(characters in the right half were shifted)
- Update
- Halve the string length
n
and decrement the operation indexi
- Continue until we reach the original string (
n = 1
)
Step 4: Calculate the final character
return chr(d % 26 + ord("a"))
- Take
d % 26
to handle wraparound (since there are 26 letters) - Add to the ASCII value of 'a' to get the final character
- Convert back to a character using
chr()
Example walkthrough:
If k = 5
and operations = [0, 1, 0]
:
- After 3 operations,
n = 8
(string length is 8) - Backtrack:
k = 5 > 4
: In right half,operations[2] = 0
, so no shift. Updatek = 1
k = 1 ≤ 2
: In left half, no changek = 1 ≤ 1
: In left half, no change
- Final:
d = 0
, so return 'a'
The time complexity is O(log k)
for finding the position, and space complexity is O(1)
.
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 trace through a concrete example with operations = [0, 1, 0]
and k = 6
.
Building the string conceptually (to understand the problem):
- Start:
word = "a"
(length 1) - After operation 0 (copy):
"aa"
(length 2) - After operation 1 (shift and append):
"aabc"
(length 4)- Original:
"aa"
- Shifted:
"bc"
(a→b, a→b) - Combined:
"aabc"
- Original:
- After operation 0 (copy):
"aabcaabc"
(length 8)
We want the 6th character, which is 'a'.
Using our backtracking solution:
Step 1: Find required length
- Start with
n = 1, i = 0
n = 1 < 6
, son = 2, i = 1
n = 2 < 6
, son = 4, i = 2
n = 4 < 6
, son = 8, i = 3
- Now
n = 8 >= 6
, stop
Step 2: Initialize shift counter d = 0
Step 3: Backtrack through operations
-
Iteration 1:
n = 8, k = 6, i = 3
- Is
k = 6 > n/2 = 4
? Yes, in right half - Update
k = 6 - 4 = 2
operations[i-1] = operations[2] = 0
, sod = 0 + 0 = 0
- Update
n = 4, i = 2
- Is
-
Iteration 2:
n = 4, k = 2, i = 2
- Is
k = 2 > n/2 = 2
? No, in left half - No changes to
k
ord
- Update
n = 2, i = 1
- Is
-
Iteration 3:
n = 2, k = 2, i = 1
- Is
k = 2 > n/2 = 1
? Yes, in right half - Update
k = 2 - 1 = 1
operations[i-1] = operations[0] = 0
, sod = 0 + 0 = 0
- Update
n = 1, i = 0
- Is
Step 4: Calculate final character
d = 0
, sod % 26 = 0
chr(0 + ord('a')) = chr(97) = 'a'
The answer is 'a', which matches our expectation from the conceptual build!
Solution Implementation
1class Solution:
2 def kthCharacter(self, k: int, operations: List[int]) -> str:
3 # Find the smallest power of 2 that is >= k
4 # This determines how many operations we need to consider
5 length = 1
6 operation_index = 0
7 while length < k:
8 length *= 2
9 operation_index += 1
10
11 # Track the total shift amount for the character
12 total_shift = 0
13
14 # Work backwards through the operations to find which character
15 # at position k comes from and accumulate shifts
16 while length > 1:
17 # If k is in the second half of current segment
18 if k > length // 2:
19 # Move k to corresponding position in first half
20 k -= length // 2
21 # Add the operation's shift value
22 total_shift += operations[operation_index - 1]
23
24 # Move to previous operation level
25 length //= 2
26 operation_index -= 1
27
28 # Convert the accumulated shift to the final character
29 # Starting from 'a' and wrapping around after 'z'
30 return chr(total_shift % 26 + ord('a'))
31
1class Solution {
2 public char kthCharacter(long k, int[] operations) {
3 // Find the minimum power of 2 that is >= k
4 // This determines how many operations we need to consider
5 long currentLength = 1;
6 int operationIndex = 0;
7 while (currentLength < k) {
8 currentLength *= 2;
9 operationIndex++;
10 }
11
12 // Track the total shift amount for the character
13 int totalShift = 0;
14
15 // Work backwards through the operations to find the original position
16 // and accumulate the shifts applied
17 while (currentLength > 1) {
18 // If k is in the second half of the current segment
19 if (k > currentLength / 2) {
20 // Move k to the corresponding position in the first half
21 k -= currentLength / 2;
22 // Add the shift from this operation
23 totalShift += operations[operationIndex - 1];
24 }
25 // Reduce the segment size by half
26 currentLength /= 2;
27 // Move to the previous operation
28 operationIndex--;
29 }
30
31 // Apply the total shift to 'a' and wrap around using modulo 26
32 return (char) ('a' + (totalShift % 26));
33 }
34}
35
1class Solution {
2public:
3 char kthCharacter(long long k, vector<int>& operations) {
4 // Step 1: Find the minimum string length that contains the k-th character
5 // The string doubles in size with each operation, so we need to find
6 // the smallest power of 2 that is >= k
7 long long stringLength = 1;
8 int operationIndex = 0;
9 while (stringLength < k) {
10 stringLength *= 2;
11 operationIndex++;
12 }
13
14 // Step 2: Trace back through the operations to find the original character
15 // and accumulate the total shift amount
16 int totalShift = 0;
17
18 // Work backwards from the final string length to find which character
19 // in the original string eventually becomes our k-th character
20 while (stringLength > 1) {
21 // Check if k is in the second half of the current string
22 long long halfLength = stringLength / 2;
23
24 if (k > halfLength) {
25 // If k is in the second half, it came from the first half
26 // but was potentially shifted based on the operation type
27 k -= halfLength; // Map k back to its position in the first half
28 totalShift += operations[operationIndex - 1]; // Add shift if operation type is 1
29 }
30
31 // Move to the previous operation (smaller string)
32 stringLength = halfLength;
33 operationIndex--;
34 }
35
36 // Step 3: Apply the total shift to get the final character
37 // The original character is 'a', shifted by totalShift positions
38 return 'a' + (totalShift % 26);
39 }
40};
41
1/**
2 * Finds the kth character after applying a series of operations.
3 * Each operation doubles the string length and applies a transformation.
4 *
5 * @param k - The position of the character to find (1-indexed)
6 * @param operations - Array of operation values that determine character transformations
7 * @returns The character at position k after all operations
8 */
9function kthCharacter(k: number, operations: number[]): string {
10 // Find the smallest power of 2 that is >= k
11 // This determines how many operations we need to consider
12 let currentLength: number = 1;
13 let operationIndex: number = 0;
14
15 while (currentLength < k) {
16 currentLength *= 2;
17 operationIndex++;
18 }
19
20 // Track the cumulative transformation offset for the character
21 let characterOffset: number = 0;
22
23 // Work backwards through the operations to find the original position
24 // and accumulate the transformation effects
25 while (currentLength > 1) {
26 const halfLength: number = currentLength / 2;
27
28 // If k is in the second half of the current segment,
29 // it was created by the operation at operationIndex - 1
30 if (k > halfLength) {
31 // Move k to the corresponding position in the first half
32 k -= halfLength;
33 // Add the transformation value from this operation
34 characterOffset += operations[operationIndex - 1];
35 }
36
37 // Move to the previous operation level
38 currentLength = halfLength;
39 operationIndex--;
40 }
41
42 // Convert the final offset to a character (wrapping around the alphabet)
43 const baseCharCode: number = 'a'.charCodeAt(0);
44 const finalCharCode: number = baseCharCode + (characterOffset % 26);
45
46 return String.fromCharCode(finalCharCode);
47}
48
Time and Space Complexity
The time complexity is O(log k)
. The first while loop runs until n >= k
, where n
starts at 1 and doubles each iteration, so it takes O(log k)
iterations to reach a value greater than or equal to k
. The second while loop runs for the same number of iterations as the first loop (from i
down to 0), performing constant-time operations in each iteration. Therefore, the overall time complexity is O(log k) + O(log k) = O(log k)
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables n
, i
, and d
, regardless of the input size k
. No additional data structures that grow with the input are created, making the space complexity constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Operation Index
A common mistake is misaligning the operation index when backtracking. When we determine we need i
operations to reach length n
, the operations are indexed from 0
to i-1
in the array.
Incorrect:
# Wrong: Using operations[i] instead of operations[i-1] total_shift += operations[i]
Correct:
# Right: The i-th operation is at index i-1 total_shift += operations[operation_index - 1]
2. Incorrect Position Mapping in Backtracking
When determining if position k
is in the right half and mapping it back to the left half, using the wrong comparison or calculation can lead to incorrect results.
Incorrect:
# Wrong: Using >= instead of > if k >= length // 2: k -= length // 2
This would incorrectly treat position length//2
as being in the right half when it's actually the last position of the left half.
Correct:
# Right: Positions 1 to length//2 are left half, length//2+1 to length are right half if k > length // 2: k -= length // 2
3. Not Handling Character Wraparound Properly
Forgetting to use modulo 26 when calculating the final character can cause issues when the total shift exceeds 25.
Incorrect:
# Wrong: This will crash or give wrong character if total_shift >= 26
return chr(total_shift + ord('a'))
Correct:
# Right: Use modulo to handle wraparound from 'z' to 'a'
return chr(total_shift % 26 + ord('a'))
4. Building the Actual String
A naive approach would be to actually build the entire string by performing all operations, which is inefficient and may cause memory issues for large k values.
Inefficient:
word = "a"
for op in operations:
if op == 0:
word = word + word
else:
word = word + ''.join(chr((ord(c) - ord('a') + 1) % 26 + ord('a')) for c in word)
return word[k-1]
Efficient: Use the backtracking approach without building the string, as shown in the solution.
Which of these pictures shows the visit order of a depth-first search?
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
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!