主题
前言
首先明确输入的
- 基础:不含括号,所以也没有数字,此时
只包含字母,例如 。 - 嵌套:如题目所说,
k[encoded_string],即数字 + 左括号 + 括号中的字符串 + 右括号。- 例如
,解码后是 。 - 例如
,内层的 ,所以 。
- 例如
- 组合:多个
k[encoded_string]并在一起。- 例如
。 - 例如
。
- 例如
第一种递归写法
分类讨论:
- 如果
是空串,返回空串。 - 如果
是字母,我们可以递归解码 到 。 - 如果
是基础类型,例如 ,那么 仍然是 。 - 如果
是组合类型,例如 ,那么答案等于 ,后者可以继续递归解码。
- 如果
- 否则,
一定是数字。这意味着 至少包含一对括号。 - 找第一个左括号的下标
。 - 找与
匹配的右括号的下标 。⚠注意:对于 这种嵌套类型,我们需要跳过内层的括号。可以用一个变量 表示左括号减去右括号的个数,在遍历 的过程中维护 ,一旦 就表示我们找到了与第一个左括号匹配的右括号。 - 把
分成三部分: 到 转成数字 。 到 继续递归解码,得到字符串 。 到 继续递归解码,得到字符串 。
- 把
重复 次,再与 拼接,得到答案 。
- 找第一个左括号的下标
python
class Solution:
def decodeString(self, s: str) -> str:
if not s:
return s
# s[0] 是字母
if s[0].isalpha():
# 分离出 s[0],解码剩下的
return s[0] + self.decodeString(s[1:])
# s[0] 是数字,后面至少有一对括号
i = s.find('[') # 找左括号
# 找右括号,注意对于 [...[...]...] 这种情况,第一个右括号并不是我们要找的,第二个才是
balance = 1 # 左括号个数减去右括号个数
for j in count(i + 1): # 从 i+1 开始向右遍历,找与 s[i] 匹配的右括号
if s[j] == '[':
balance += 1
elif s[j] == ']':
balance -= 1
if balance == 0: # 左右括号个数相等,找到与 s[i] 匹配的右括号 s[j]
return self.decodeString(s[i + 1: j]) * int(s[:i]) + self.decodeString(s[j + 1:])cpp
// C++ 版待补充cpp
class Solution {
public:
string decodeString(string s) {
if (s.empty()) {
return s;
}
// s[0] 是字母
if (isalpha(s[0])) {
// 分离出 s[0],解码剩下的
return s[0] + decodeString(s.substr(1));
}
// s[0] 是数字,后面至少有一对括号
int i = s.find('['); // 找左括号
// 找右括号,注意对于 [...[...]...] 这种情况,第一个右括号并不是我们要找的,第二个才是
int balance = 1; // 左括号个数减去右括号个数
for (int j = i + 1; ; j++) {
if (s[j] == '[') {
balance++;
} else if (s[j] == ']') {
balance--;
if (balance == 0) { // 左右括号个数相等,找到与 s[i] 匹配的右括号 s[j]
int k = stoi(s.substr(0, i));
string t = decodeString(s.substr(i + 1, j - i - 1));
string res;
while (k--) {
res += t;
}
return res + decodeString(s.substr(j + 1));
}
}
}
}
};复杂度分析
- 时间复杂度:
,其中 为 的最大值, , 是 的长度。最坏情况下 形如 ,嵌套 层,生成的字符串长度为 。不过,本题保证答案长度不超过 。 - 空间复杂度:
。
第二种递归写法
把 k[encoded_string] 的并。
以
,加到答案中,现在答案为 。 ,加到答案中,现在答案为 。 ,更新重复次数 。 ,往下递归。 ,在下一级递归函数中的字符串为 。 ,在下一级递归函数中的字符串为 。 ,递归返回 。上层递归函数接收到 ,将其重复 次,得到 ,加到答案中,现在答案为 。然后重置 。⚠注意:如果不重置,后面遍历到数字 的时候,会把 加到 的后面,得到错误的 。 ,更新重复次数 。 ,和上面一样,递归,得到字符串 。将其重复 次,得到 ,加到答案中,最终答案为 。
细节
问:递归过程中,怎么知道当前遍历到哪个字符了?
答:可以用一个在递归函数外的变量
问:如果
答:利用式子
- 初始化
。 - 更新
为 。 - 更新
为 。 - 更新
为 。
python
class Solution:
def decodeString(self, s: str) -> str:
i = 0
def decode() -> str:
nonlocal i
res = '' # 或者 res = [],具体见另一份代码【Python3 列表】
k = 0
while i < len(s):
c = s[i]
i += 1
if c.isalpha():
res += c
elif c.isdigit():
k = k * 10 + int(c)
elif c == '[': # 递
res += decode() * k # 把括号内的字符串重复 k 次
k = 0 # 重置 k,若不重置,2[a]3[b] 后面的 3 会算出 k = 23
else: # ']' 归
break
return res
return decode()cpp
// C++ 版待补充python
class Solution:
def decodeString(self, s: str) -> str:
i = 0
def decode() -> str:
nonlocal i
res = []
k = 0
while i < len(s):
c = s[i]
i += 1
if c.isalpha():
res.append(c)
elif c.isdigit():
k = k * 10 + int(c)
elif c == '[': # 递
res.append(decode() * k) # 把括号内的字符串重复 k 次
k = 0 # 重置 k,若不重置,2[a]3[b] 后面的 3 会算出 k = 23
else: # ']' 归
break
return ''.join(res)
return decode()cpp
// C++ 版待补充cpp
class Solution {
public:
string decodeString(string s) {
int i = 0;
auto decode = [&](this auto&& decode) -> string {
string res;
int k = 0;
while (i < s.size()) {
char c = s[i];
i++;
if (isalpha(c)) {
res += c;
} else if (isdigit(c)) {
k = k * 10 + (c - '0');
} else if (c == '[') { // 递
string t = decode();
for (; k > 0; k--) {
res += t; // 把括号内的字符串重复 k 次
}
} else { // ']' 归
break;
}
}
return res;
};
return decode();
}
};复杂度分析
- 时间复杂度:
,理由同上。注意生成的字符串有多长,就需要多少的时间。不过,本题保证答案长度不超过 。 - 空间复杂度:
。
用栈模拟递归
把第二种递归改写成非递归写法。
首先你要理解计算机底层是如何实现递归的,请看视频 深刻理解递归【基础算法精讲 09】。
简单来说,在往下「递」的时候,计算机会把当前函数中的局部变量保存到栈中;下层递归函数「归」之后,再从栈中把保存的变量取出。我们可以手动模拟这个过程,具体实现如下。
python
class Solution:
def decodeString(self, s: str) -> str:
stack = [] # 用于模拟计算机的递归
res = ''
k = 0
for c in s:
if c.isalpha():
res += c
elif c.isdigit():
k = k * 10 + int(c)
elif c == '[':
# 模拟递归
# 在递归之前,把当前递归函数中的局部变量 res 和 k 保存到栈中
stack.append((res, k))
# 递归,初始化 res 和 k
res = ''
k = 0
else: # ']'
# 递归结束,从栈中恢复递归之前保存的局部变量
pre_res, pre_k = stack.pop()
# 此时 res 是下层递归的返回值,将其重复 pre_k 次,拼接到递归前的 pre_res 之后
res = pre_res + res * pre_k
return rescpp
// C++ 版待补充cpp
class Solution {
public:
string decodeString(string s) {
stack<pair<string, int>> stk; // 用于模拟计算机的递归
string res;
int k = 0;
for (char c : s) {
if (isalpha(c)) {
res += c;
} else if (isdigit(c)) {
k = k * 10 + (c - '0');
} else if (c == '[') {
// 模拟递归
// 在递归之前,把当前递归函数中的局部变量 res 和 k 保存到栈中
stk.emplace(move(res), k);
// 递归,初始化 res 和 k(由于 move 了,res 此时为空)
k = 0;
} else { // ']'
// 递归结束,从栈中恢复递归之前保存的局部变量
auto [pre_res, pre_k] = stk.top();
stk.pop();
// 此时 res 是下层递归的返回值,将其重复 pre_k 次,拼接到递归前的 pre_res 之后
while (pre_k--) {
pre_res += res;
}
res = move(pre_res);
}
}
return res;
}
};复杂度分析
- 时间复杂度:
,理由同上。注意生成的字符串有多长,就需要多少的时间。不过,本题保证答案长度不超过 。 - 空间复杂度:
。
相似题目
专题训练
见下面数据结构题单的「§3.5 表达式解析」。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府