一、问题描述:
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
二、问题分析:
分析此问题,输入数据s是一个由数字字符、字母字符和方括号字符组成的字符串,题目保证输入数据有效,那么可以发现’[]’里面的内容一定对应’[’左边紧挨着的数字,由于输入的数据可以进行方括号的嵌套,所以使用栈来解决这个问题完美契合题目的特性。
开辟一个栈空间stack;
开始遍历s;
遇到数字字符串,压入栈顶;
遇到字母字符串,压入栈顶;
遇到’]’,从当前栈顶向前遍历,拼接字符串,直到遇到第一个数字字符串,进行出栈、入栈操作;
循环结束,将栈stack连接成字符串作为res返回。
三、详细设计:
此算法的目标是解析一种特殊编码格式下的字符串s,并将其还原成原始字符串。假设该编码格式为:’count1[string1]count2[string2]...’,其中count表示s中字母字符串的数量,string为纯字母字符串。同一个字符串可能有不同的编码方式。例如,"abcd"可能会被编码为“1[a]1[b]1[c]1[d]”,也可能会被编码为’abcd’,同时,s也可能会被编码为嵌套的形式,例如’6[a2[bcd]]’。
具体实现上,可以使用栈这种数据结构来减少遍历以及深度优先搜索所需的复杂度,并通过从左向右扫描字符串的方式来处理它。具体包括以下步骤:
开辟一个栈空间stack,用来存储数字和字母字符串。
遍历字符串s的每个字符,对于每个字符执行以下操作:
(a) 如果当前字符是数字字符,则向后查找进行数字字符的拼接,作为curNum,直到遇到’[’。
(b) 如果当前字符是字母字符,则向后查找进行字母字符的拼接,作为curStr,直到当前字符不再是字母字符。
(c) 如果当前字符是']',则从当前栈顶向前遍历,拼接所有的字母字符串,作为sufsumStr(数组类型),直到遇到栈前面的第一个数字字符串,对栈顶的元素进行出栈操作:
(i) 从栈顶开始循环弹出元素,并将它们依次从头部添加到sufsumStr中,添加一个,出栈一个,直到遇到第一个数字字符串,结束循环。再将该数字字符串弹出栈顶,转换为Number类型后用popNum保存起来。
(ii) 将sufsumStr连接为字符串,再循环将sufsumStr拼接popNum次,作为strJoin。
(iii) 将strJoin压入栈顶。
(d) 如果s以字母字符串或数字字符串结尾,则不应该再重复的进行拼接入栈操作,应该直接结束循环。
s遍历完成后,使用stack.join(‘’)连接为字符串后返回。
此算法的时空复杂度均为O(n):仅需要一次针对字符串s的线性扫描,每个字符的处理时间均为常量级别。同时,栈的空间需求也随着输入数据的大小呈线性增长。
四、源代码:
// 字符串解码
function decodeString(s) {
// 开辟一个栈空间
const stack = new Array();
// 字符串s长度
const len = s.length;
// 遍历s,进行入栈、出栈操作
for (let i = 0; i < len; ++i) {
// 判断当前字符是否可以转换为Number类型,如果是,进行数字字符串的截取
if (!isNaN(s[i])) {
let curNum = new String();
// 拼接数字字符串,遇到'['停止拼接
for (var n = i; n < len; ++n) {
if (s[n] !== '[') {
curNum += s[n];
} else {
i = n;
break;
}
}
// 如果s由数字字符串结尾,则直接结束循环,不用进行入栈操作
if (n === len) {
break;
}
// 将拼接好的数字字符串压入栈顶
stack.push(curNum);
continue;
}
// 判断当前字符是否为字母
if (s[i].charCodeAt() >= 97 && s[i].charCodeAt() <= 122) {
let curStr = new String();
// 开始拼接字母字符串
for (var m = i; m < len; ++m) {
if (s[m].charCodeAt() >= 97 && s[m].charCodeAt() <= 122) {
curStr += s[m];
} else {
i = m - 1;
break;
}
}
// 入栈
stack.push(curStr);
//如果以字母字符串结尾,则需要先进行入栈操作再结束循环
if (m === len) {
break;
}
}
// 遇到']',进行出栈、入栈操作
if (s[i] === ']') {
let sufsumStr = new Array();
// 从当前栈顶向前遍历,向前进行字符串的拼接,找到前面的第一个数字字符串,结束循环
while (isNaN(stack[stack.length - 1])) {
// 逐个从数组头部添加进去
sufsumStr.unshift(stack[stack.length - 1]);
// 添加一个,出栈一个
stack.pop();
}
let strJoin = new String();
// 添加完毕后进行字符串的连接
sufsumStr = sufsumStr.join('');
// 将栈顶的数字字符串出栈,根据栈的先进后出原则,栈后面的字母字符串肯定对应前面的第一个数字
const popNum = Number(stack.pop());
// 找到第一个数字后循环进行字符串拼接
for (let j = 0; j < popNum; ++j) {
strJoin += sufsumStr;
}
// 拼接好后再入栈,如此循环
stack.push(strJoin);
}
}
// 最后将栈连接为字符串后返回即可
return stack.join('');
};
console.log('The decoded string is:', decodeString('2[a3[cd]]'));
运行结果: