Medium

题目描述

给定一个编码字符串 s。为了将字符串解码成磁带,需要逐个字符读取编码字符串,并执行以下步骤:

  • 如果读取的字符是字母,则将该字母写入磁带。
  • 如果读取的字符是数字 d,则将当前磁带的内容重复写入 d - 1 次(总共重复 d 次)。

给定一个整数 k,返回解码字符串中第 k 个字母(从 1 开始索引)。

示例 1:

输入: s = "leet2code3", k = 10
输出: "o"
解释: 解码字符串为 "leetleetcodeleetleetcodeleetleetcode"。
字符串中第 10 个字母是 "o"。

示例 2:

输入: s = "ha22", k = 5
输出: "h"
解释: 解码字符串为 "hahahaha"。
第 5 个字母是 "h"。

示例 3:

输入: s = "a2345678999999999999999", k = 1
输出: "a"
解释: 解码字符串是 "a" 重复 8301530446056247680 次。
第 1 个字母是 "a"。

提示:

  • 2 <= s.length <= 100
  • s 由小写英文字母和数字 2-9 组成
  • s 以字母开头
  • 1 <= k <= 10^9
  • 保证 k 小于等于解码字符串的长度
  • 解码字符串的长度保证小于 2^63

解题思路

这道题的关键是理解解码过程和避免实际构建完整的解码字符串(因为它可能非常长)。

核心思路:

  1. 正向计算总长度:首先遍历编码字符串,计算解码后的总长度,但不实际构建字符串
  2. 逆向查找目标字符:从后往前处理,通过数学计算确定第k个字符的位置

算法步骤:

  1. 第一遍遍历:计算解码后字符串的总长度
  2. 第二遍从后往前遍历:
    • 如果遇到数字d,说明当前段被重复了d次,我们可以通过取模运算确定k在哪个重复段中
    • 如果遇到字母且当前位置正好是k,直接返回该字母
    • 不断缩小搜索范围直到找到答案

为什么这样做有效?

  • 避免了构建巨大的字符串,节省内存
  • 通过数学计算快速定位,时间复杂度只与编码字符串长度相关
  • 利用重复结构的规律,通过取模运算快速跳转

这种逆向思维的解法是处理大规模重复结构问题的经典方法。

代码实现

class Solution {
public:
    string decodeAtIndex(string s, int k) {
        long long size = 0;
        
        // 计算解码后字符串的总长度
        for (char c : s) {
            if (isdigit(c)) {
                size *= (c - '0');
            } else {
                size++;
            }
        }
        
        // 从后往前处理
        for (int i = s.length() - 1; i >= 0; i--) {
            k %= size;
            
            if (k == 0 && isalpha(s[i])) {
                return string(1, s[i]);
            }
            
            if (isdigit(s[i])) {
                size /= (s[i] - '0');
            } else {
                size--;
            }
        }
        
        return "";
    }
};
class Solution:
    def decodeAtIndex(self, s: str, k: int) -> str:
        size = 0
        
        # 计算解码后字符串的总长度
        for c in s:
            if c.isdigit():
                size *= int(c)
            else:
                size += 1
        
        # 从后往前处理
        for c in reversed(s):
            k %= size
            
            if k == 0 and c.isalpha():
                return c
            
            if c.isdigit():
                size //= int(c)
            else:
                size -= 1
        
        return ""
public class Solution {
    public string DecodeAtIndex(string s, int k) {
        long size = 0;
        
        // 计算解码后字符串的总长度
        foreach (char c in s) {
            if (char.IsDigit(c)) {
                size *= (c - '0');
            } else {
                size++;
            }
        }
        
        // 从后往前处理
        for (int i = s.Length - 1; i >= 0; i--) {
            k = (int)(k % size);
            
            if (k == 0 && char.IsLetter(s[i])) {
                return s[i].ToString();
            }
            
            if (char.IsDigit(s[i])) {
                size /= (s[i] - '0');
            } else {
                size--;
            }
        }
        
        return "";
    }
}
/**
 * @param {string} s
 * @param {number} k
 * @return {string}
 */
var decodeAtIndex = function(s, k) {
    let size = 0;
    
    // Calculate the total size of decoded string
    for (let c of s) {
        if (isNaN(c)) {
            size++;
        } else {
            size *= parseInt(c);
        }
    }
    
    // Work backwards to find the character
    for (let i = s.length - 1; i >= 0; i--) {
        k %= size;
        
        if (k === 0 && isNaN(s[i])) {
            return s[i];
        }
        
        if (isNaN(s[i])) {
            size--;
        } else {
            size /= parseInt(s[i]);
        }
    }
    
    return "";
};

复杂度分析

复杂度类型说明
时间复杂度O(n)需要遍历编码字符串两次,n为字符串长度
空间复杂度O(1)只使用了常数额外空间