decodeString
此题是leetCode 394
第一个版本写的非常挫折,代码也十分混乱。比较大的收获是gdb调试熟悉多了. 这也是比较挫的实现,让人失之东隅收之桑榆吧! 总结一些心得:
- 先不要着急开始写;
- 有些情况是递归方便, 有些情况是非递归方便(例如按层遍历树, 包括此题). 前提是, 如果需要递归把递归的操作拆解清楚(返回类型, 入参等); 对于递归情况很复杂的,尝试拆解手工栈的操作;
- 对于这种明显利用栈的, 先把栈操作的内容搞清楚, 入栈出栈都是哪些内容? 时机是哪些? 出栈后的具体动作是什么? 此题解法2,3中是数字和字符入栈,而不是’[’, '['只是标记入栈的时机。这些问题没有搞清,就不要想着直接去走递归利用系统栈去得到解法。如果顺利可以得到,但大概率会不顺利。对于设计其他数据结构的把基本操作也先尝试搞清.
- 有关解析状态:
- 一般还是写一下BNF表达式比较好(而不是直接用脑子想),有助于搞清楚所处的若干个状态; 理解下一个字符对状态转换的分支的作用; 有关这点参考一下第一个递归解法的注释;
- 先搞1个或几个具体的例子,拆解解析字符的每一步的动作应该是什么(入栈, 出栈, 具体动作, etc.)
- 使用iterator或integer counter不是问题关键。搞清前面2项才是关键;
解法1: 最初的递归版本
#include <iostream>
#include <cctype>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
string decodeString(const string &s);
private:
vector<char> stack_;
int parse_repeat_num_(string::const_iterator *beg);
string decodeString_(string::const_iterator *beg);
string makeup_partial_string(int repeat_num, const string& str);
string handleStartWithDigit(string::const_iterator *beg);
};
int Solution::parse_repeat_num_(string::const_iterator *beg) {
string repeat_num_str = "";
while (isdigit(**beg)) {
repeat_num_str += **beg;
(*beg)++;
}
return stoi(repeat_num_str);
}
string Solution::decodeString(const string& s) {
auto st = s.cbegin(), ed = s.cend();
string ans("");
do {
string tmp = decodeString_(&st);
ans += tmp;
cout << "tmp=" << tmp << endl;
}
while (st != ed);
cout << "result=" << ans << endl;
return ans;
}
string Solution::makeup_partial_string(int repeat_num, const string& str) {
string tmp = str;
string result = string(str);
while (--repeat_num) {
result += tmp;
}
return result;
}
string Solution::handleStartWithDigit(string::const_iterator *beg) {
auto *st = beg;
string ans;
if (isdigit(**st)) {
int repeat_num = parse_repeat_num_(st);
if (**st == '[') {
stack_.push_back('[');
(*st)++;
ans = decodeString_(st);
if (**st == ']') {
stack_.pop_back();
(*st)++; // point to the next one after ']';
ans = makeup_partial_string(repeat_num, ans);
return ans;
}
throw runtime_error("Error: handleDigit error");
} else {
throw runtime_error("Error: handleDigit error");
}
}
return "";
}
// BNF:
// <decode_string> ::= <decode_string>{, <decode_string>} //1
// decode_string ::= [<digits> ] "[" <decode_string> "]" //2
//
// 这个解法的decodeString_承担着2个职能,从handleStartWithDigit的调用,
// 需要将结果返回给handleStartWithDigit;通过stack_计数的,则由stack的状态控制(因为同一层的decode_string可能有多个并列的递归定义);
// 这导致返回的出口有好几处,结合不同状态,这让代码很不清晰;
//
string Solution::decodeString_(string::const_iterator *beg) {
auto *st = beg;
string ans = "";
do {
if (isdigit(**st)) {
ans += handleStartWithDigit(st);
if (**st == ']') {
// 一个handleStartWithDigit结构解析出(已经处理了digit对应的], 下一个仍是]), 返回上一层;
return ans;
}
} else if (isalpha(**st)) {
string tmp;
alpha_label:
while (isalpha(**st)) {
char c = **st;
tmp += string(1, c);
(*st)++;
}
ans += tmp;
tmp = "";
if (isdigit(**st)) {
tmp = handleStartWithDigit(st);
ans += tmp;
tmp = "";
goto alpha_label;
}
if (**st == ']') {
// 字符结构的结束标志是], 返回调用decode_string_的上一层
return ans;
}
}
} while(stack_.size() != 0);
return ans;
};
int main() {
Solution solution;
string s = "3[a]2[bc]";
string s1 = "3[a2[c]]";
string s2 = "2[2[bc]]";
solution.decodeString(s2); // bcbcbcbc
solution.decodeString(s1); // accaccacc
solution.decodeString(s); // aaabcbc
return 0;
}
解法2,3: 非递归实现
阅读了leetCode上的题解, 又写了以下2个非递归解法。入栈内容是数字和字符; 代码清晰了许多。 时间复杂度O(n)
空间复杂度最大也小于O(n);
解法2:
#include <iostream>
#include <stack>
using namespace std;
class Solution {
public:
string decodeString(const string &s) {
int n = s.size();
int num = 0;
string current_str("");
for (int i = 0; i < n; ++i) {
char c = s[i];
if (isdigit(c)) {
num = num * 10 + (c - '0');
} else if (c == '[') {
num_stack_.push(num);
num = 0;
str_stack_.push(current_str);
current_str = "";
} else if (isalpha(c)) {
current_str += string(1, c);
} else if (c == ']') {
string str_top = str_stack_.top();
int num_top = num_stack_.top();
str_stack_.pop();
num_stack_.pop();
string tmp = current_str;
while (--num_top) {
current_str += tmp;
}
current_str = str_top + current_str;
}
}
cout << "result: current_str=" << current_str << endl;
return current_str;
}
private:
stack<int> num_stack_;
stack<string> str_stack_;
};
int main() {
Solution solution;
string s = "3[a]2[bc]";
string s1 = "3[a2[c]]";
string s2 = "2[2[bc]]";
solution.decodeString(s2);
solution.decodeString(s1);
solution.decodeString(s);
return 0;
}
解法3:
#include <iostream>
#include <stack>
#include <string>
using namespace std;
class Solution {
public:
string decodeString(const string &s) {
auto st = s.cbegin(), ed = s.cend();
int num = 0;
string current_str("");
// 使用iterator与否并不重要;
for (; st != ed; ++st) {
char c = *st;
if (isdigit(c)) {
num = parse_repeat_num(&st);
} else if (c == '[') {
num_stack_.push(num);
num = 0;
str_stack_.push(current_str);
current_str = "";
} else if (isalpha(c)) {
current_str += string(1, c);
} else if (c == ']') {
string str_top = str_stack_.top();
int num_top = num_stack_.top();
str_stack_.pop();
num_stack_.pop();
string tmp = current_str;
while (--num_top) {
current_str += tmp;
}
current_str = str_top + current_str;
}
}
cout << "result: current_str=" << current_str << endl;
return current_str;
}
private:
stack<int> num_stack_;
stack<string> str_stack_;
int parse_repeat_num(string::const_iterator *beg) {
auto st = *beg;
string res("");
while (isdigit(*st)) {
res += *st;
st++;
}
*beg = st - 1;
return stoi(res);
}
};
int main() {
Solution solution;
string s = "3[a]2[bc]";
string s1 = "3[a2[c]]";
string s2 = "2[2[bc]]";
solution.decodeString(s2);
solution.decodeString(s1);
solution.decodeString(s);
return 0;
}
gdb 调试tip
// 需要用-g, 生成符号表; 否则gdb会报No symbol table is loaded, error;
// g++会自动调用ld,使用linker生成binary可执行文件, gcc不会; 会报出undefined reference error
g++ -g -std=c++11 -Wall 394.cpp -o main_394
gdb -q main_394
其他: s, n, p, file. etc.
结果
总结
重申一下总结要点:
- 此题的递归解法很难搞定, 所以注意某些情况是递归较容易,某些情况例如涉及状态机的情形,递归并不一定容易写出。此题在写解法1时,画了好复杂的一张状态转换图,并恰好直觉的将处理数字开头和‘[’开头的部分区分为2部分, 又发现必须利用栈来控制同一层字符中混有多个数字的情况,较顺利的得到结果。但尝试合并handleStartWithDigit和decodeString_2者并不成功,递归十分难写。
- 结合具体例子,搞清怎么设计栈内容,入栈时机和出栈时机,是较快较容易解出本题的关键;