leetcode:394. 字符串解码
题目
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
输入:s = "3[a2[c]]"
输出:"accaccacc"
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
解答 & 代码
解法一:模拟 + 递归
class Solution {
private:
// 将字符串 s 重复 k 次并返回
string repeatStringKTimes(string s, int k)
{
string result = "";
for(int i = 0; i < k; ++i)
result += s;
return result;
}
public:
string decodeString(string s) {
string result = ""; // 结果
int idx = 0; // 当前下标
// 遍历字符串 s
while(idx < s.size())
{
// 1. 处理字母
while(idx < s.size() && (s[idx] >= 'a' && s[idx] <= 'z'))
{
result += s[idx];
++idx;
}
// 2. 处理 k[encoded_string],括号里面可能存在嵌套括号
// 2.1 处理数字 k
int number = 0; // 重复次数 k
while(idx < s.size() && (s[idx] >= '0' && s[idx] <= '9'))
{
number = number * 10 + s[idx] - '0';
++idx;
}
// 2.2 跳过 "["
if(s[idx] == '[')
++idx;
// 2.3 处理括号中的 encoded_string
string curStr = ""; // 括号中的 encoded_string,即待被重复的字符串
while(idx < s.size() && s[idx] != ']')
{
// 如果是字母,直接加到 curStr
if(idx < s.size() && s[idx] >= 'a' && s[idx] <= 'z')
curStr += s[idx];
// 如果是数字,则提取内容 k_2[encoded_string_2],递归处理
else if(s[idx] >= '0' && s[idx] <= '9')
{
string subS = "";
int leftBracket = 0; // 左括号数目
int rightBracket = 0; // 右括号数目
while(idx < s.size())
{
char ch = s[idx];
++idx;
subS += ch;
if(s[idx] == '[')
++leftBracket;
else if(s[idx] == ']')
{
++rightBracket;
// 若遍历到右括号,且遍历到的右括号数 == 左括号数,则退出当前循环
if(leftBracket == rightBracket)
break;
};
}
// 递归解码 k_2[encoded_string_2],并加到 curStr 后面
curStr += decodeString(subS);
}
++idx;
}
// 2.4 跳过 "]"
if(s[idx] == ']')
++idx;
// 2.5 将当前的 curStr 重复 number 次
result += repeatStringKTimes(curStr, number);
}
return result;
}
};
复杂度分析:设字符串 s
长为 N
- 时间复杂度 O(N):
- 空间复杂度 O(N):取决于递归深度,即字符串
s
中括号套括号的最大深度,最坏情况下为 O(N),eg.2[2[2[a]]]
执行结果:
执行结果:通过
执行用时:0 ms, 在所有 C++ 提交中击败了 100.00% 的用户
内存消耗:6.2 MB, 在所有 C++ 提交中击败了 93.27% 的用户
解法二:辅助栈法
本题难点在于括号内嵌套括号,需要从内向外生成与拼接字符串,这与栈的先入后出特性对应。
核心思路就是用两个栈分别保存字符串和数字(重复次数),如果遇到 "["
则入栈,遇到 "]"
则出栈
class Solution {
public:
string decodeString(string s) {
// 辅助栈
stack<string> resultStack; // 存储字符串
stack<int> numberStack; // 存储数字(重复次数)
string result = ""; // 结果字符串
int number = 0; // 数字(重复次数)
// 遍历字符串 s 中的每个字符 s[i]
for(int i = 0; i < s.size(); ++i)
{
char ch = s[i];
// case 1. 若当前字符为字母,则添加到 result 尾部
if(ch >= 'a' && ch <= 'z')
result += ch;
// case 2. 若当前字符为数字,则更新 number
else if(ch >= '0' && ch <= '9')
number = number * 10 + ch - '0';
// case 3. 若当前是左括号,则将当前的 result 和 number 压入栈中,并重新初始化
else if(ch == '[')
{
resultStack.push(result); // 将当前的 result 压入栈
numberStack.push(number); // 将当前的 number 压入栈
result = ""; // 将 result 清空
number = 0; // 将 number 清零
}
// case 4. 若当前是右括号,则从栈中弹出之前的 result 和 number,来拼接字符串
else if(ch == ']')
{
string lastResult = resultStack.top(); // 之前的字符串
resultStack.pop();
int lastNumber = numberStack.top(); // 代表当前字符串需要重复的次数
numberStack.pop();
// 将当前字符串重复 lastNumber 次,拼接到之前的字符串之后
for(int j = 0; j < lastNumber; ++j)
lastResult += result;
result = lastResult; // 更新当前字符串
}
}
return result;
}
};
复杂度分析:设字符串 s
长为 N
- 时间复杂度 O(N):一次遍历
s
- 空间复杂度 O(N):辅助栈大小取决于字符串
s
中括号套括号的最大深度,最坏情况下为 O(N),eg.2[2[2[a]]]
执行结果:通过
执行用时:0 ms, 在所有 C++ 提交中击败了 100.00% 的用户
内存消耗:6.4 MB, 在所有 C++ 提交中击败了 45.55% 的用户
解法三:递归法
class Solution {
private:
int end = 0; // dfs 递归函数返回时遍历到的下标
// 递归函数定义:从下标 start 开始解码字符串 s,并返回解码后的字符串,返回时更新索引 end
string dfs(string s, int start)
{
string result = ""; // 解码后的结果字符串
int number = 0; // 代表要重复的次数
int idx = start; // 下标
// 遍历字符串
while(idx < s.size())
{
char ch = s[idx];
// case 1:如果当前字符是字母,则添加到 result 尾部
if(ch >= 'a' && ch <= 'z')
result += ch;
// case 2:如果当前字符是数字,则更新 number
else if(ch >= '0' && ch <= '9')
number = number * 10 + ch - '0';
// case 3:如果当前字符是 "[",则开启新一层递归,并拼接
else if(ch == '[')
{
// 递归得到括号里面解码后的字符串
string subStr = dfs(s, idx + 1);
// 将 subStr 重复 number 次,添加到 result 尾部
// 同时 number 清零
while(number > 0)
{
result += subStr;
--number;
}
// 更新下标为上面递归函数遍历到的最后一个字符的位置
idx = end;
}
// case 4:如果当前字符是 "]",则触发递归结束条件
// 用 end 记录当前访问到的下标,并返回本次递归得到的字符串 result
else if(ch == ']')
{
end = idx;
return result;
}
// 准备遍历下一个字符
++idx;
}
end = idx; // 这里写不写都行
return result;
}
public:
string decodeString(string s) {
return dfs(s, 0);
}
};
复杂度分析:设字符串 s
长为 N
- 时间复杂度 O(N):递归返回的时候会更新当前已遍历到的下标 end,因此实际还是一次遍历
s
- 空间复杂度 O(N):递归深度(递归栈空间复杂度)取决于字符串
s
中括号套括号的最大深度,最坏情况下为 O(N),eg.2[2[2[a]]]
执行结果:通过
执行用时:0 ms, 在所有 C++ 提交中击败了 100.00% 的用户
内存消耗:6.2 MB, 在所有 C++ 提交中击败了95.86% 的用户