Find the K-th Character in String Game II
HardPrompt
Alice and Bob are playing a game. Initially, Alice has a string word = "a".
You are given a positive integer k. You are also given an integer array operations, where operations[i] represents the type of the ith operation.
Now Bob will ask Alice to perform all operations in sequence:
- If
operations[i] == 0, append a copy ofwordto itself. - If
operations[i] == 1, generate a new string by changing each character inwordto its next character in the English alphabet, and append it to the originalword. For example, performing the operation on"c"generates"cd"and performing the operation on"zb"generates"zbac".
Return the value of the kth character in word after performing all the operations.
Note that the character 'z' can be changed to 'a' in the second type of operation.
Example 1:
Input: k = 5, operations = [0,0,0]
Output: "a"
Explanation:
Initially, word == "a". Alice performs the three operations as follows:
- Appends
"a"to"a",wordbecomes"aa". - Appends
"aa"to"aa",wordbecomes"aaaa". - Appends
"aaaa"to"aaaa",wordbecomes"aaaaaaaa".
Example 2:
Input: k = 10, operations = [0,1,0,1]
Output: "b"
Explanation:
Initially, word == "a". Alice performs the four operations as follows:
- Appends
"a"to"a",wordbecomes"aa". - Appends
"bb"to"aa",wordbecomes"aabb". - Appends
"aabb"to"aabb",wordbecomes"aabbaabb". - Appends
"bbccbbcc"to"aabbaabb",wordbecomes"aabbaabbbbccbbcc".
Constraints:
1 <= k <= 10141 <= operations.length <= 100operations[i]is either 0 or 1.- The input is generated such that
wordhas at leastkcharacters after all operations.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the string generation process described in the problem. It starts with the initial string "a" and applies each operation in the operations array one by one, updating the string at each step. After all operations are performed, it returns the character at the k-th position.
Algorithm
- Initialize a
StringBuilderor a similar mutable string structure with the initial value"a". - Iterate through each operation in the
operationsarray from start to finish. - For each operation, modify the string according to the rules:
- If
operations[i] == 0, append the current string to itself. - If
operations[i] == 1, generate a new string by transforming each character of the current string to the next in the alphabet ('z'becomes'a'). Then, append this new transformed string to the original.
- If
- To avoid running out of memory immediately, you can add an optimization to stop growing the string once its length exceeds
k, as characters beyond thek-th position are not needed. - After all operations are completed (or the length is sufficient), retrieve the character at the
(k-1)-th index (sincekis 1-indexed). - Return this character as a string.
Walkthrough
The brute force method involves constructing the string step-by-step, exactly as the game's rules dictate. We begin with word = "a" and loop through the operations array. In each iteration, we double the string's length by either appending a copy of itself (for operation 0) or a transformed copy (for operation 1). We continue this until all operations are processed. Finally, we access the k-th character from the fully constructed string. Given the constraints (k up to 10^14, operations.length up to 100), the final string can have up to 2^100 characters, making this approach practically impossible to execute.
class Solution { // This is a conceptual implementation and will fail due to memory and time limits. public String findKthCharacter(long k, int[] operations) { StringBuilder word = new StringBuilder("a"); for (int op : operations) { // Optimization: Stop if length is already greater than k. // Note: This is insufficient as k can be very large. if (word.length() >= k) { break; } if (op == 0) { word.append(word.toString()); } else { StringBuilder transformed = new StringBuilder(); for (int i = 0; i < word.length(); i++) { char c = word.charAt(i); if (c == 'z') { transformed.append('a'); } else { transformed.append((char)(c + 1)); } } word.append(transformed); } } // This cast is unsafe as k is a long and string length can exceed Integer.MAX_VALUE. return String.valueOf(word.charAt((int)(k - 1))); }}Complexity
Time
O(L), where L is the length of the string. In the worst case, this is O(2^n), which is infeasible.
Space
O(L), where L is the length of the string. In the worst case, this is O(2^n), which is infeasible.
Trade-offs
Pros
Simple to understand and follows the problem description directly.
Cons
Extremely inefficient in both time and space.
The length of the string grows exponentially, reaching up to
2^100characters, which is far too large to store in any computer's memory.This approach will result in
OutOfMemoryErrorfor most test cases that adhere to the problem's constraints.It will also be too slow, leading to
TimeLimitExceededeven if memory were not an issue.
Instead of building the string forwards, we can determine the k-th character by working backward from the final state. The key observation is that each operation doubles the string's length. This means the k-th character is either in the first half (the original string from the previous step) or the second half (the appended part). By repeatedly halving the problem space, we can trace back to the initial string "a" and find the character.
Algorithm
- Adjust
kto be 0-indexed by decrementing it:k--. - Create a
longarray,lengths, of sizen+1, wherenis the number of operations. lengths[i]will store the length of the string before thei-th operation. Initializelengths[0] = 1.- Populate the
lengthsarray. Forifrom 0 ton-1, calculatelengths[i+1] = lengths[i] * 2. To preventlongoverflow for largen, if the length exceedsk, we can cap it at a value larger thank(e.g.,k+1), as the exact value beyondkdoesn't affect the logic. - Initialize an integer
transformCount = 0. - Iterate backward through the operations, from
i = n-1down to0. - In each iteration, get the length of the string before the current operation:
len = lengths[i]. - Check if the target index
kfalls in the second half of the string formed by operationi:if (k >= len).- If it does, update
kto its position relative to the start of the second half:k -= len. - If
operations[i]was type 1, it means the second half was transformed, so incrementtransformCount.
- If it does, update
- After the loop, the base character is
'a'. The final character is found by applyingtransformCounttransformations to'a', which is(char)('a' + (transformCount % 26)).
Walkthrough
This method avoids building the string by analyzing the structure of the operations. Since each operation doubles the length, we can determine if the k-th character is in the first or second half of the string at any stage. We work backward from the last operation.
First, we pre-calculate the length of the string before each operation and store it in an array. Then, we iterate from the last operation to the first. For each operation i, we check if our target index k (0-indexed) is greater than or equal to the length of the string before that operation, len = 2^i. If it is, the character is in the appended half. We then adjust k to be relative to this second half (k = k - len) and, if the operation was type 1, we note that one transformation must be applied. If k < len, the character is in the first half, and we simply move to the previous operation without changing k or the transformation count. This process continues until we've considered all operations. The final character is 'a' plus the total number of transformations counted.
class Solution { public String findKthCharacter(long k, int[] operations) { int n = operations.length; long[] lengths = new long[n + 1]; lengths[0] = 1; for (int i = 0; i < n; i++) { lengths[i + 1] = lengths[i] * 2; // Cap the length to avoid overflow and because exact length > k is not needed. if (lengths[i + 1] > k) { lengths[i + 1] = k + 1; } } k--; // Use 0-based indexing int transformCount = 0; for (int i = n - 1; i >= 0; i--) { long len = lengths[i]; if (k >= len) { k -= len; if (operations[i] == 1) { transformCount++; } } } char finalChar = (char) ('a' + (transformCount % 26)); return String.valueOf(finalChar); }}Complexity
Time
O(n), where n is `operations.length`. The two loops for pre-calculation and backward iteration both run `n` times.
Space
O(n), where n is `operations.length`, for storing the `lengths` array.
Trade-offs
Pros
Very efficient in time, with a linear complexity relative to the number of operations.
Avoids the memory and time blow-up of the brute-force approach by using a mathematical shortcut.
Handles large values of
kcorrectly.
Cons
Requires extra space proportional to the number of operations to store the pre-calculated lengths.
This approach is an optimization of the backward iteration method. Instead of pre-calculating and storing all intermediate lengths in an array, we can calculate the required length on the fly inside the backward loop. This eliminates the need for O(n) extra space, making the solution highly efficient in both time and space.
Algorithm
- Adjust
kto be 0-indexed:k--. - Initialize an integer
transformCount = 0. - Iterate backward through the operations, from
i = n-1down to0. - In each iteration, we need the length of the string before operation
i, which is2^i. We can calculate this on the fly. - A
longcan store up to2^63 - 1. Ifi >= 63, then2^iwill be larger than anylong, includingk. In this case,kmust be in the first half, so we can simply continue to the next iteration. - If
i < 63, calculatelen = 1L << i. - Check if the target index
kfalls in the second half:if (k >= len).- If it does, update
kto its position relative to the start of the second half:k -= len. - If
operations[i]was type 1, incrementtransformCount.
- If it does, update
- After the loop, the base character is
'a'. The final character is found by applyingtransformCounttransformations to'a', which is(char)('a' + (transformCount % 26)).
Walkthrough
This is the most optimized solution. It builds upon the backward iteration logic but improves space complexity to constant. The key insight is that the length of the string before operation i is always 2^i. We don't need to store these lengths in an array; we can compute them as needed during our backward pass.
The algorithm remains the same: iterate from i = n-1 down to 0. In each step, calculate len = 2^i. A check is needed for i >= 63, as 1L << i would overflow a long. Since k is at most 10^14 (which is less than 2^47), if i is large, len will certainly be greater than k, meaning k is in the first half. For smaller i, we compute len and perform the same check as before: if k >= len, we adjust k and increment transformCount if the operation was type 1. This achieves the same result with O(1) space.
class Solution { public String findKthCharacter(long k, int[] operations) { k--; // Use 0-based indexing int n = operations.length; int transformCount = 0; for (int i = n - 1; i >= 0; i--) { // The length of the string before operation i is 2^i. // If i >= 63, 1L << i would overflow. However, k (max 10^14) is much smaller // than 2^63, so k will always be in the first half. We can just continue. if (i >= 63) { continue; } long len = 1L << i; if (k >= len) { k -= len; if (operations[i] == 1) { transformCount++; } } } char finalChar = (char) ('a' + (transformCount % 26)); return String.valueOf(finalChar); }}Complexity
Time
O(n), where n is `operations.length`. The single backward loop runs `n` times.
Space
O(1), as we do not use any auxiliary data structures that scale with the input size.
Trade-offs
Pros
Most efficient solution with optimal time and space complexity.
Constant space usage makes it scalable regardless of the number of operations.
Cons
The logic for handling potential
longoverflow when calculating2^imight be slightly less intuitive at first glance.
Solutions
Solution
class Solution {public char kthCharacter(long k, int[] operations) { long n = 1; int i = 0; while (n < k) { n *= 2; ++i; } int d = 0; while (n > 1) { if (k > n / 2) { k -= n / 2; d += operations[i - 1]; } n /= 2; --i; } return (char)('a' + (d % 26)); }}class Solution {public: char kthCharacter(long long k, vector<int> &operations) { long long n = 1; int i = 0; while (n < k) { n *= 2; ++i; } int d = 0; while (n > 1) { if (k > n / 2) { k -= n / 2; d += operations[i - 1]; } n /= 2; --i; } return 'a' + (d % 26); }};class Solution: def kthCharacter(self, k: int, operations: List[int]) -> str: n, i = 1, 0 while n < k: n *= 2 i += 1 d = 0 while n > 1: if k > n // 2: k -= n // 2 d += operations[i - 1] n //= 2 i -= 1 return chr(d % 26 + ord("a"))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.