Facebook Pixel

880. Decoded String at Index

MediumStackString
Leetcode Link

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 repeated d - 1 more times (making d 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 until k becomes 0 and the current character is a letter
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 digit d, then before this digit was applied, the length was m / d. We can use modular arithmetic: if we want position k in a string of length m that was created by repeating a pattern d times, then k % (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 multiply m by d (since the current tape is repeated d 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:

  1. Update k with modulo: k %= m reduces k to its equivalent position within the current segment. This handles the repeating pattern - if we have a string of length m and want position k where k > m, then k % m gives us the equivalent position.

  2. Check if we found our answer: If k == 0 and the current character is a letter, we've found our target. When k becomes 0 after the modulo operation, it means we're looking for the last character of the current segment.

  3. Undo the encoding:

    • If current character is a digit d: We divide m by d (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

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 Evaluator

Example Walkthrough

Let's walk through a concrete example with the encoded string s = "ab2c3" and we want to find the k = 10th 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 by d (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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More