近几日,在刷力扣时,看到 最长有效括号这一题(后文会给出解答),便想写一个自动生成括号组合并判断是否匹配的程序。
一、自动生成括号及括号匹配
首先,我们要解决的是括号生成问题,当然,我们可以利用回溯法直接写出符合匹配规则的括号组合,但这样便没有判断是否匹配的必要,这不符合我的本意。
在括号生成中,先画出回溯树,在本问题中,就不对回溯树进行剪枝。不难发现,在每个可选择的结点中,都面临两种选择,选’(’ 或 ‘)’。该回溯树的终点就是路径的长度等于需要生成括号的个数,下面来看一下生成括号的程序。
public static LinkedList<String> list = new LinkedList();
public static void partition(int start, int len, StringBuilder sb){
if(start == len){
list.add(sb.toString());
return;
}
sb.append("(");
partition(start + 1, len, sb);
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
partition(start + 1, len, sb);
sb.deleteCharAt(sb.length() - 1);
}
运行程序,调用 partition(0,3,new StringBuilder(“”));得到的结果如下图所示:
现在,我们已经解决了第一个问题,在判断括号是否匹配时,可以借助栈这一数据结构来实现。
栈不同于队列的先进先出,而是先进后出,使用压栈和出栈的方式来解决各种问题。
在本问题中,如何使用栈来解决呢。首先,我们判断栈为空还是非空。
- 如果为空:这说明,目前还没有进行括号匹配,或者前面的元素已经一一匹配,符合条件。
- 如果没有进行括号匹配,判断入栈的元素是否为’(‘,如果是,可以入栈,反之,直接返回不匹配,因为,’(‘总是出现在’)‘之前,如果直接出现’)',与事实不符。
- 如果前面的元素已经一一匹配,还是判断判断入栈的元素是否为’(',分析与上文一致,只不过前提条件不一样。在代码中,并没有体现二者的不同,这里只是为了让大家可以更好地理解。
- 如果是非空:
- 判断栈顶数据和要入栈的数据是否匹配,若匹配,则出栈,并把继续判断下一个元素。若不匹配,把数据入栈。
最后,遍历完所有数据之后,判断栈是否为空即可得出答案。代码如下:
public static boolean judgement(String s, int left, int right){
if(left == right || s.charAt(left)== ')'|| right - left + 1 % 2 == 1)
return false;
Stack<Character> stack = new Stack();
stack.push(s.charAt(left));
int index = left + 1;
while (index <= right ){
if(stack.isEmpty()){
//栈为空
if(s.charAt(index) == ')')
return false;
else
stack.push('(');
index++;
}else {
//栈不为空,判断栈顶和入栈的元素是否匹配,若不匹配则入栈
char down = stack.peek();
char up = s.charAt(index);
if(down == '(' && up == ')'){
stack.pop();
index++;
}else {
stack.push(s.charAt(index));
index++;
}
}
}
return stack.isEmpty();
}
最终,把两个部分的代码汇总,就可以得到整个程序,完整代码如下(直接生成符合匹配条件括号程序也会在下文给出)
import java.util.LinkedList;
import java.util.Stack;
public class w0403_study {
public static LinkedList<String> list = new LinkedList();
public static void main(String[] args) {
partition(0,3,new StringBuilder(""));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i)+" 括号是否匹配? "+judgement(list.get(i), 0, list.get(i).length() - 1));
}
}
public static void partition(int start, int len, StringBuilder sb){
if(start == len){
list.add(sb.toString());
return;
}
sb.append("(");
partition(start+1,len,sb);
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
partition(start+1,len,sb);
sb.deleteCharAt(sb.length() - 1);
}
public static boolean judgement(String s, int left, int right){
if(left == right || s.charAt(left)== ')'|| right - left + 1 % 2 == 1)
return false;
Stack<Character> stack = new Stack();
stack.push(s.charAt(left));
int index = left + 1;
while (index <= right ){
if(stack.isEmpty()){
//栈为空
if(s.charAt(index) == ')')
return false;
else
stack.push('(');
index++;
}else {
//栈不为空,判断栈顶和入栈的元素是否匹配,若不匹配则入栈
char down = stack.peek();
char up = s.charAt(index);
if(down == '(' && up == ')'){
stack.pop();
index++;
}else {
stack.push(s.charAt(index));
index++;
}
}
}
return stack.isEmpty();
}
}
运行结果如下图所示:
二、自动生成匹配括号
题目:请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 :
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
与上文不同的是:
本题需要直接实现括号生成程序。在这里,又加入了两个新的参数,left和right,即剩余左括号和右括号的个数。
在括号匹配的规则中,已经生成的括号,左括号的个数必须要大于等于右括号的个数,这里大家需要理解一下,这是剪枝的一个条件。
终止条件就是左括号和右括号都被用完,即left = 0,right = 0。
当然,在left或right小于01时,显然与事实不符,直接return;
public List<String> str = new LinkedList<>();
public List<String> generateParenthesis(int n) {
generate_parenthesis(new StringBuilder(""), n, n);
return str;
}
public void generate_parenthesis(StringBuilder sb, int left, int right) {
if (left == 0 && right == 0) {
str.add(sb.toString());
return;
}
if (left < 0 || right < 0)
return;
if (left > right)
return;
sb.append("(");
generate_parenthesis(sb, left - 1, right);
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
generate_parenthesis(sb, left, right - 1);
sb.deleteCharAt(sb.length() - 1);
}
测试:当left = right = 3时,得到的结果如下图所示
三、最长有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
思路
定义 d p [ i ] dp[i] dp[i] 为以下标 i i i 结尾的最长有效括号的长度。在遍历时,我们注意到以’(‘结尾的子串的有效长度为0,所以只需要计算以’)'结尾的子串的有效长度。
当 s [ i ] s[i] s[i] 为’('时, d p [ i ] = 0 dp[i] = 0 dp[i]=0
当 s [ i ] s[i] s[i] 为’)'时,
- 如果 s [ i − 1 ] s[i-1] s[i−1] 为’(', d p [ i ] = d p [ i − 2 ] + 2 dp[i] = dp[i-2]+2 dp[i]=dp[i−2]+2
- 如果 s [ i − 1 ] s[i-1] s[i−1] 为’)‘,并且 s [ i − d p [ i − 1 ] − 1 ] s[i - dp[i-1]-1] s[i−dp[i−1]−1] 为’(', d p [ i ] = d p [ i − 1 ] + 2 + d p [ i − d p [ i − 1 ] − 2 ] dp[i] =dp[i-1]+2+dp[i-dp[i-1]-2] dp[i]=dp[i−1]+2+dp[i−dp[i−1]−2]
最终返回的长度就是 d p dp dp 数组中的最大值。
public int longestValidParentheses(String s) {
int dp[] = new int[s.length()];
int max = 0;
for (int i = 1; i < dp.length; i++) {
if(s.charAt(i) == '(') {
dp[i] = 0;
} else {
if(s.charAt(i - 1) == '(') {
if(i - 2 > 0){
dp[i] = dp[i - 2] + 2;
}else {
dp[i] = 2;
}
}else {
if (i - dp[i - 1] - 1 >= 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
if (i - dp[i - 1] - 2 >= 0) {
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];
} else {
dp[i] = dp[i - 1] + 2;
}
}
}
}
max = Math.max(max, dp[i]);
}
return max;
}
当然,大家也可以尝试使用双重for循环,加上上文中的判别括号是否匹配的函数,也能解决此问题,但是这种方法的复杂度非常高,不建议使用。
ps:后续会更新大量有关回溯、动态规划、dfs、bfs的刷题blog。
尽情期待,未完待续…
看到这的小伙伴不妨留下一个免费的赞吧。