给定一个仅包含 '('、')'、'{'、'}'、'['、']'的字符串,确定输入的字符串是否合法。
e.g. "()"、"()[]{}"、"[()]([]({}))" 是合法的,而"(]"、"([)]" 是不合法的。
使用栈stack
C++实现:
1 bool isValid(string s) { 2 stack<char> stack; 3 for (char &c : s) { 4 if (c == '(') 5 stack.push(')'); 6 else if (c == '{') 7 stack.push('}'); 8 else if (c == '[') 9 stack.push(']'); 10 else if (stack.empty() || (stack.top() != c)) 11 return false; 12 else 13 stack.pop(); 14 } 15 return stack.empty(); 16 }
对应的Java实现:
1 public boolean isValid(String s) { 2 Stack<Character> stack = new Stack<Character>(); 3 for (char c : s.toCharArray()) { 4 if (c == '(') 5 stack.push(')'); 6 else if (c == '{') 7 stack.push('}'); 8 else if (c == '[') 9 stack.push(']'); 10 else if (stack.isEmpty() || stack.pop() != c) 11 return false; 12 } 13 return stack.isEmpty(); 14 }
C++中的stack,其中有两个方法:
- pop(),返回void,不返回类型<T>。
- top(),返回栈顶的引用。
pop()不返回栈顶元素的原因是:
- 异常安全原因
如果要实现,代码如下,
template<class T> T stack<T>::pop() { if( vused_ == 0) { throw "pop from empty stack"; } else { return v_[--vused_ - 1]; } }
试图返回 v_[--vused_ - 1] 的时候,会调用 T 的拷贝构造函数,如果调用的时候发生异常,并没有返回正确的值,但是栈顶元素已经弹出了,这时就丢失了栈顶数据。
如果这样写,
template<class T> T stack<T>::pop() { if( vused_ == 0) { throw "pop from empty stack"; } else { T result = v_[vused_ - 1]; --vused_; return result; } }
先建立一个栈顶元素的副本,如果拷贝构造函数出现异常,那么 --vused_ 不会执行,就不会丢失数据。但假如 --vused_ 和 return result 之间发生异常,依然会出问题。也就是说,如果想同时做 pop() 和 top() ,一定要保证 --vused_ 和 return result 是一个执行事务。
- 效率原因
因为栈顶元素在栈中已经不存在,必须在按引用返回之前现将其存储到某个地方。如果选用动态内存,除非动态内存最终被删除,否则将导致内存泄露。
如果调用拷贝构造函数,对象按值传递的方式从函数返回,效率低下。
扩展 生成括号
给定 n 对括号,写出所有可能的合法括号组合。
e.g. 给定 n = 3,结果集为
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
我的思路是递归
1 vector<string> generateParenthesis(int n) { 2 if (n == 1) { 3 return {"()"}; 4 } else { 5 vector<string> result; 6 vector<string> pre = generateParenthesis(n - 1); 7 for (int i = 0; i < pre.size(); i++) { 8 result.push_back("(" + pre[i] + ")"); 9 result.push_back(pre[i] + "()"); 10 if ("()" + pre[i] != pre[i] + "()") 11 result.push_back("()" + pre[i]); 12 } 13 return result; 14 } 15 }
算法没什么问题,可以生成所有可能的括号集合,但 vector 中存储的顺序不论怎么改都和答案的不一样。
答案使用的思路是DFS。创建一个函数来增加括号,str 表示加入到结果 vector<string> res 中的一项。
left 表示剩下的需要增加的“ ( ”的个数,初始为 n;right 表示当前的 str 中需要与没有配对的 “ ( ” 配对的 “ ) ” 的个数。
若 left > 0,则在 str 里加入一个“ ( ”,并让下次需要增加的“ ( ”的个数减 1,且因为加入了一个“ ( ”,该左括号没有右括号与之配对,所以当前 str 需要的“ ) ”加 1;
若 right > 0,说明需要在 str 里加入一个“ ) ”,下次需要的“ ) ”少 1 个就行了。
1 vector<string> generateParenthesis(int n) { 2 vector<string> result; 3 addParen(result, "", n, 0); 4 return result; 5 } 6 7 void addParen(vector<string> &res, string str, int left, int right) { 8 if (left == 0 && right == 0) { 9 res.push_back(str); 10 return; 11 } 12 if (left > 0) 13 addParen(res, str + "(", left - 1, right + 1); 14 if (right > 0) 15 addParen(res, str + ")", left, right - 1); 16 }
类似的,
这种方法个人觉得比上面的直观,一项 str 需要 n 个左括号和 n 个右括号,那么 left 和 right 都直接表示 str 所需要的“ ( ”和“ ) ”的个数。
注意的是,str 中一定要有没有配对的 “ ( ” 存在,才能加入 “ ) ”,所以加入右括号的判断条件是 if (right > left)。
1 vector<string> generateParenthesis(int n) { 2 vector<string> result; 3 addParen(result, "", n, n); 4 return result; 5 } 6 7 void addParen(vector<string> &res, string str, int left, int right) { 8 if (left == 0 && right == 0) { 9 res.push_back(str); 10 return; 11 } 12 if (left > 0) 13 addParen(res, str + "(", left - 1, right); 14 if (right > left) 15 addParen(res, str + ")", left, right - 1); 16 }
将 str 使用引用传参的话则不需要建立许多局部临时变量 str,可以节省一定的空间。
1 vector<string> generateParenthesis(int n) { 2 vector<string> result; 3 string str = ""; 4 addParen(result, str, n, n); 5 return result; 6 } 7 8 void addParen(vector<string> &res, string &str, int left, int right) { 9 if (left == 0 && right == 0) { 10 res.push_back(str); 11 return; 12 } 13 if (left > 0) { 14 str += "("; 15 addParen(res, str, left - 1, right); 16 str.resize(str.length() - 1); 17 } 18 if (right > left) { 19 str += ")"; 20 addParen(res, str, left, right - 1); 21 str.resize(str.length() - 1); 22 } 23 }
注意 16 和 21 行 str.resize(str.length() - 1); 语句调用,由于 str 是引用,如果不重新设置 str 长度,str 会越来越长。
使用 pop_back 也可以
if (left > 0) { str.push_back('('); addParen(res, str, left - 1, right); str.pop_back(); } if (right > left) { str.push_back(')'); addParen(res, str, left, right - 1); str.pop_back(); }