回溯
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。
关于回溯的理解和本质在之前已经讲过,今天做了几道题目,对于其中两道找左下角的值和电话号码的字母组合都用到了回溯。其实找左下角的值利用层序遍历更加简单和直观,但是为了增加对递归的理解,用递归做也是可以的。
回溯(递归)三步
- 确定返回值和参数
对于电话号码这道题,虽然是字符串,但是可以抽象成树形结构。
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组res保存起来,这两个变量我依然定义为全局。
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
注意这个index可不是 77.组合 (opens new window)和216.组合总和III (opens new window)中的startIndex了。
这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
//设置全局列表存储最后的结果
List<String> list = new ArrayList<>();
//比如digits如果为"23",num 为0,则str表示2对应的 abc
public void backTracking(String digits, String[] numString, int num)
对于左下角的值,其实题目的本质就是求最大深度的最左边的值。当用DFS遍历时不会涉及对中间节点的处理逻辑。所以前中后序都是可以的。因为无论前中后序,都是先遍历左再遍历右(节点)。其最大深度用Deep作为全局变量,值用value记录。参数为根节点和每次遍历的深度。
private int Deep = -1;
private int value = 0;
private void findLeftValue (TreeNode root,int deep)
2、回溯(递归)终止条件
对于电话号码这道题目,当记录的num长度和传入的字符串长度相同时,递归结束。
此时,需要进行记录路径字符数组。
//遍历全部一次记录一次得到的字符串
if (num == digits.length()) {
list.add(temp.toString());
return;
}
左下角的值则可以理解为到叶子节点则递归终止。
if (root == null) return;
if (root.left == null && root.right == null) {
if (deep > Deep) {
value = root.val;
Deep = deep;
}
}
3、递归&回溯逻辑
对于电话号码的这道题。不像二叉树这种天然的二分树,这道题需要自己拆分循环次数,因此在递归前应该先找到循环次数,对于这道题,循环次数就是当前数字对应的字母字符个数。进入循环后即需要添加当前的字符而后进入递归,递归需要弹出递归前加入的字符。目的:找到所有字符的组成。
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i));
//c
backTracking(digits, numString, num + 1);
//剔除末尾的继续尝试
temp.deleteCharAt(temp.length() - 1);
}
对于左下角值这个题目来说,每次递归都需要判断递归的下一节点是否为空来避免空指针异常。其次递归前需要将deep+1,递归结束后,需要弹出该深度。目的:遍历所有深度。
if (root == null) return;
if (root.left == null && root.right == null) {
if (deep > Deep) {
value = root.val;
Deep = deep;
}
}