880. Decoded String at Index
Problem Description
You have an encoded string s
that needs to be decoded following specific rules. The decoding process reads the string character by character:
- When a letter is encountered, that letter is written to the tape
- When a digit
d
is encountered, the entire current tape content is repeatedd - 1
more times (makingd
total copies)
For example, if the encoded string is "leet2code3"
:
- Start with empty tape
- Read 'l': tape = "l"
- Read 'e': tape = "le"
- Read 'e': tape = "lee"
- Read 't': tape = "leet"
- Read '2': repeat tape once more, tape = "leetleet"
- Read 'c': tape = "leetleetc"
- Read 'o': tape = "leetleetco"
- Read 'd': tape = "leetleetcod"
- Read 'e': tape = "leetleetcode"
- Read '3': repeat tape 2 more times, tape = "leetleetcodeleetleetcodeleetleetcode"
Given an integer k
, you need to find and return the k
-th letter (1-indexed) in the decoded string.
The solution uses a clever approach: instead of actually building the decoded string (which could be extremely long), it first calculates the total length m
of the decoded string. Then it works backwards through the encoded string, using modular arithmetic to find which character in the original pattern corresponds to position k
. When traversing backwards:
- If a digit is encountered, divide the current length by that digit
- If a letter is encountered, decrease the length by 1
- Keep updating
k = k % m
untilk
becomes 0 and the current character is a letter
Intuition
The key insight is that we don't actually need to build the entire decoded string, which could be astronomically large. Instead, we can use the repetitive structure of the encoding to our advantage.
Think about what happens when we encounter a digit during decoding - we're essentially creating copies of what we already have. This means the decoded string has a repeating pattern. For example, if we have "abc" and then see a '3', we get "abcabcabc". If we want the 7th character, that's the same as wanting the 1st character (since 7 % 3 = 1).
This observation leads us to work backwards. We first calculate the total length of the decoded string by going forward through the encoded string. Then, we can traverse backwards and "undo" the encoding process:
-
When we see a digit going backwards, we're essentially undoing the repetition. If the current decoded length is
m
and we see digitd
, then before this digit was applied, the length wasm / d
. We can use modular arithmetic: if we want positionk
in a string of lengthm
that was created by repeating a patternd
times, thenk % (m/d)
tells us the position in the original pattern. -
When we see a letter going backwards, we're removing one character from the end, so we decrease the length by 1.
The brilliant part is using k %= m
at each step. This keeps reducing our target position based on the repeating patterns. Eventually, when k
becomes 0 (meaning we've found our position at the end of a repeating segment) and we're at a letter, that's our answer.
For instance, if the decoded string is "abcabcabc" (length 9) and we want the 7th character:
7 % 9 = 7
- Working backwards, if we see this came from "abc" repeated 3 times
7 % 3 = 1
, which points to the 1st character 'a'
This approach elegantly avoids memory issues while leveraging the mathematical properties of the encoding scheme.
Learn more about Stack patterns.
Solution Approach
The implementation consists of two main phases: calculating the decoded length and then finding the target character by working backwards.
Phase 1: Calculate Total Length
First, we traverse the encoded string forward to calculate the total length m
of the decoded string:
m = 0
for c in s:
if c.isdigit():
m *= int(c)
else:
m += 1
- For each letter, we increment
m
by 1 - For each digit
d
, we multiplym
byd
(since the current tape is repeatedd
times total)
Phase 2: Find the k-th Character
Now we traverse the string backwards and use modular arithmetic to find our target:
for c in s[::-1]:
k %= m
if k == 0 and c.isalpha():
return c
if c.isdigit():
m //= int(c)
else:
m -= 1
The key steps in each iteration:
-
Update k with modulo:
k %= m
reducesk
to its equivalent position within the current segment. This handles the repeating pattern - if we have a string of lengthm
and want positionk
wherek > m
, thenk % m
gives us the equivalent position. -
Check if we found our answer: If
k == 0
and the current character is a letter, we've found our target. Whenk
becomes 0 after the modulo operation, it means we're looking for the last character of the current segment. -
Undo the encoding:
- If current character is a digit
d
: We dividem
byd
(m //= int(c)
), effectively undoing the repetition - If current character is a letter: We subtract 1 from
m
(m -= 1
), removing this letter from consideration
- If current character is a digit
The algorithm cleverly uses the fact that position k
in a repeated string corresponds to position k % (original_length)
in the original string. By continuously applying this reduction while moving backwards through the encoding, we eventually find the exact character without ever building the full decoded string.
Time Complexity: O(n)
where n
is the length of the encoded string
Space Complexity: O(1)
as we only use a constant amount of extra space
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with the encoded string s = "ab2c3"
and we want to find the k = 10
th character.
Phase 1: Calculate Total Length
First, let's trace through the decoding to understand what we're dealing with:
- Start with empty tape
- Read 'a': tape = "a" (length = 1)
- Read 'b': tape = "ab" (length = 2)
- Read '2': repeat tape once more, tape = "abab" (length = 4)
- Read 'c': tape = "ababc" (length = 5)
- Read '3': repeat tape 2 more times, tape = "ababcababcababc" (length = 15)
So our decoded string is "ababcababcababc" with total length m = 15
.
Phase 2: Find the 10th Character
Now we traverse backwards through "ab2c3"
with k = 10
and m = 15
:
Iteration 1: Character = '3' (digit)
k = k % m = 10 % 15 = 10
(still looking for position 10)- Since it's a digit:
m = m / 3 = 15 / 3 = 5
- (We've "undone" the tripling - now considering just "ababc")
Iteration 2: Character = 'c' (letter)
k = k % m = 10 % 5 = 0
(position 10 in length-15 string = position 0 in length-5 pattern)k == 0
and 'c' is a letter, so we found our answer!- Return 'c'
To verify: The 10th character in "ababcababcababc" is indeed 'c' (at the end of the second repetition).
The key insight here is that position 10 in a string of length 15 that was created by repeating a pattern of length 5 three times is equivalent to position 0 (or the last character) of that length-5 pattern. The modulo operation 10 % 5 = 0
captures this relationship perfectly, and since we're at a letter when k
becomes 0, we've found our target character.
Solution Implementation
1class Solution:
2 def decodeAtIndex(self, s: str, k: int) -> str:
3 # First pass: calculate the total length of the decoded string
4 total_length = 0
5 for char in s:
6 if char.isdigit():
7 # If digit, multiply the current length by the digit value
8 total_length *= int(char)
9 else:
10 # If letter, increment length by 1
11 total_length += 1
12
13 # Second pass: traverse the string in reverse to find the k-th character
14 for char in reversed(s):
15 # Reduce k to stay within the current decoded string length
16 k %= total_length
17
18 # If k is 0 and current character is a letter, we found our answer
19 if k == 0 and char.isalpha():
20 return char
21
22 # Update the decoded string length based on current character
23 if char.isdigit():
24 # If digit, divide the length by the digit value (reverse of multiplication)
25 total_length //= int(char)
26 else:
27 # If letter, decrement length by 1 (reverse of addition)
28 total_length -= 1
29
1class Solution {
2 public String decodeAtIndex(String s, int k) {
3 // First pass: Calculate the total length of the decoded string
4 long decodedLength = 0;
5 for (int i = 0; i < s.length(); i++) {
6 char currentChar = s.charAt(i);
7 if (Character.isDigit(currentChar)) {
8 // If digit, multiply the current decoded length by the digit value
9 int digitValue = currentChar - '0';
10 decodedLength *= digitValue;
11 } else {
12 // If letter, increment the decoded length by 1
13 decodedLength++;
14 }
15 }
16
17 // Second pass: Work backwards to find the k-th character
18 for (int i = s.length() - 1; ; i--) {
19 // Adjust k to handle cyclic repetitions
20 k %= decodedLength;
21
22 char currentChar = s.charAt(i);
23
24 // If k becomes 0 and current character is a letter, we found our answer
25 if (k == 0 && !Character.isDigit(currentChar)) {
26 return String.valueOf(currentChar);
27 }
28
29 // Update decoded length based on current character
30 if (Character.isDigit(currentChar)) {
31 // If digit, divide the decoded length by the digit value
32 int digitValue = currentChar - '0';
33 decodedLength /= digitValue;
34 } else {
35 // If letter, decrement the decoded length by 1
36 decodedLength--;
37 }
38 }
39 }
40}
41
1class Solution {
2public:
3 string decodeAtIndex(string s, int k) {
4 // Calculate the total length of the decoded string
5 long long decodedLength = 0;
6 for (char& ch : s) {
7 if (isdigit(ch)) {
8 // If digit, multiply the current decoded length by the digit value
9 decodedLength *= (ch - '0');
10 } else {
11 // If letter, increment the decoded length by 1
12 ++decodedLength;
13 }
14 }
15
16 // Traverse the string backwards to find the k-th character
17 for (int i = s.size() - 1; ; --i) {
18 // Adjust k to handle repeated patterns
19 k %= decodedLength;
20
21 // If k becomes 0 and current character is a letter, we found our answer
22 if (k == 0 && isalpha(s[i])) {
23 return string(1, s[i]);
24 }
25
26 // Update decoded length based on current character
27 if (isdigit(s[i])) {
28 // If digit, divide the decoded length by the digit value
29 decodedLength /= (s[i] - '0');
30 } else {
31 // If letter, decrement the decoded length by 1
32 --decodedLength;
33 }
34 }
35 }
36};
37
1/**
2 * Decodes a string at a specific index position after expansion.
3 * The string is expanded by repeating substrings when digits are encountered.
4 * @param s - Input string containing letters and digits (2-9)
5 * @param k - The 1-indexed position to find in the expanded string
6 * @returns The character at position k in the expanded string
7 */
8function decodeAtIndex(s: string, k: number): string {
9 // Calculate the total length of the expanded string using BigInt to handle large numbers
10 let expandedLength: bigint = 0n;
11
12 // First pass: calculate the total length after expansion
13 for (const char of s) {
14 if (char >= '1' && char <= '9') {
15 // If it's a digit, multiply the current length by that digit
16 expandedLength *= BigInt(char);
17 } else {
18 // If it's a letter, increment the length by 1
19 expandedLength++;
20 }
21 }
22
23 // Second pass: traverse backwards to find the character at position k
24 for (let i = s.length - 1; ; i--) {
25 // Adjust k if it exceeds the current expanded length
26 if (k >= expandedLength) {
27 k %= Number(expandedLength);
28 }
29
30 // Check if we've found the target position and current character is a letter
31 if (k === 0 && s[i] >= 'a' && s[i] <= 'z') {
32 return s[i];
33 }
34
35 // Update the expanded length based on current character
36 if (s[i] >= '1' && s[i] <= '9') {
37 // If it's a digit, divide the length by that digit (reverse the multiplication)
38 expandedLength = (expandedLength / BigInt(s[i])) | 0n;
39 } else {
40 // If it's a letter, decrement the length by 1 (reverse the addition)
41 expandedLength--;
42 }
43 }
44}
45
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. The algorithm performs two passes through the string: the first loop iterates through all characters in s
to calculate the total decoded length m
, and the second loop iterates through the string in reverse to find the k-th character. Each loop processes each character exactly once with constant-time operations (checking if a character is a digit, multiplication, division, modulo, and comparison operations), resulting in O(n) + O(n) = O(n)
total time complexity.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space, storing variables m
(to track the decoded string length), k
(modified in place), and c
(current character being processed). No additional data structures that grow with input size are used, and the string reversal s[::-1]
in Python creates an iterator that doesn't require storing the entire reversed string in memory.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error with Zero-Indexed vs One-Indexed K
A critical pitfall is misunderstanding that k
is 1-indexed (as stated in the problem) but the modulo operation k % total_length
returns values from 0 to total_length - 1
. When k % total_length == 0
, it actually refers to the last character of the current segment, not the first.
Incorrect assumption: When k % total_length == 0
, we're looking for the first character.
Correct understanding: When k % total_length == 0
, we're looking for the last character of the current segment.
2. Integer Overflow in Length Calculation
The total length can grow exponentially large. For example, with input like "a9b9c9d9e9"
, the decoded length becomes astronomical. In languages with fixed integer sizes, this causes overflow.
Solution:
- Use appropriate data types (in Python, integers handle this automatically)
- Add early termination: if
total_length >= k
, we can stop calculating since we only need the k-th character
total_length = 0
for char in s:
if char.isdigit():
total_length *= int(char)
else:
total_length += 1
# Early termination to prevent unnecessary calculation
if total_length >= k:
break
3. Forgetting to Handle the Case When K Equals Total Length
When k
equals the total decoded length, k % total_length
becomes 0, which might incorrectly trigger the return condition even if the current character isn't the right one.
Solution: The algorithm handles this correctly by checking both conditions: k == 0
AND char.isalpha()
. This ensures we only return when we're at a letter position.
4. Misunderstanding the Backward Traversal Logic
During backward traversal, it's easy to confuse what "undoing" means:
- For a digit
d
: We divide byd
(not subtract) - For a letter: We subtract 1 (not divide)
The operations must be the inverse of the forward pass operations to correctly track the position.
In a binary min heap, the maximum element can be found in:
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top 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
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!