我们这一节来看一个在字符串上进行搜索的问题,这道题是「力扣」上第 22 题:括号生成问题。
给出
n
代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
这是一个很典型地使用回溯算法完成的问题,因为要完成一件事情(生成 n 对括号),并且是有效的括号。
这件事情可以有若干种做法,每一种做法有若干个步骤。并且要我们求出这若干种解法,这样的问题显然可以使用回溯算法解决。
我们就拿示例 3 在草稿纸做一个计算。
要得到一个符合条件的字符串,首先第 1 个字符一定是 (
,不可能是右括号;
第 2 个字符,可以是左括号,也可以是右括号,
如果第 2 个字符使用了左括号,第 3 个字符可以使用左括号,也可以使用右括号;
如果第 2 个字符使用了右括号,第 3 个字符只能使用左括号,因为如果使用了右括号,就是不符合题意的括号,在它之前,没有与之配对的括号。
其实分析到这里,我们的思路就来了,是否可以使用左括号和右括号,其实是和已经使用的左右括号的个数相关的,因此我们在搜索的时候,就需要考虑这个因素。
我们再把刚才的分析概括一下:
- 只要左括号还有剩余的数量,换句话说,只要左括号可以用,那么就可以在当前位置添加左括号;
- 右括号的使用是有限制的,如果之前已经使用的左括号数量和右括号数量相等,那么当前就不能够使用右括号,原因我们刚刚也说了,如果使用了右括号,在之前就不能找到与之匹配的左括号。
根据这样的思路,我们可以把这张图画完。就是这样的一个树形结构。
(缺个图)
下面我们看一下如何编码:
根据我们刚才的分析,需要设计的状态变量有:
1、左括号还可以使用的个数;
2、右括号还可以使用的个数;
3、当前已经拼接出的字符串,可以理解为是一个路径变量
Java 代码:
import java.util.ArrayList;
import java.util.List;
public class Solution {
// 做减法
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
// 特判
if (n == 0) {
return res;
}
// 执行深度优先遍历,搜索可能的结果
dfs("", n, n, res);
return res;
}
/**
* @param curStr 当前递归得到的结果
* @param left 左括号还有几个可以使用
* @param right 右括号还有几个可以使用
* @param res 结果集
*/
private void dfs(String curStr, int left, int right, List<String> res) {
// 因为每一次尝试,都使用新的字符串变量,所以无需回溯
// 在递归终止的时候,直接把它添加到结果集即可,注意与「力扣」第 46 题、第 39 题区分
if (left == 0 && right == 0) {
res.add(curStr);
return;
}
// 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
if (left > right) {
return;
}
if (left > 0) {
dfs(curStr + "(", left - 1, right, res);
}
if (right > 0) {
dfs(curStr + ")", left, right - 1, res);
}
}
}
这就是代码。
我们会发现,这个代码有一点点神奇,它好像没有回溯的过程。但事实上,这个题回溯的过程并不明显。这是因为在 Java 语言里:curStr + "("
这个语法生成了新的字符串对象,因此,在递归的每一个结点,其实都生成了新的字符串对象。因此,在递归终止的时候,我们直接将字符串添加到结果集中就好了。
感兴趣的朋友,不妨使用 StringBuilder
字符序列这个类进行搜索,这样的操作就很像我们之前的搜索,就一定会有「撤销选择」和「状态重置」的操作。
但事实上,它们都是在树形问题上的深度优先遍历,都有「回到过去」的过程,大家依然是需要通过理解「深度优先遍历」这个过程去体会,我们使用「遍历」这个思想,特别是「深度优先遍历」去搜索整个状态空间的思路,它有一个「回到过去」的过程,就是通过回到过去,我们就可以尝试不同的选择,因此命名为「回溯」算法。
下面提供两道练习题:希望大家通过这两道练习,进一步体会这个思想。
练习
1、「力扣」第 17 题:电话号码的字母组合
2、「力扣」第 784 题: 字母大小写全排列