77. 组合
1.思路
回溯类似于遍历一颗二叉树,由于组合元素用过的不能重复,则每次递归的开始条件为i+1,即可去重。
2.问题
result.add(path)的结果为空,只有result.add(new ArrayList<>(path));才有值??
解释: 因为add(path)加入的时是一个path,即
result = [path,path,path],
当path发生变化时,最后一次的结果会覆盖掉之前的,path清空以后变为[],则
result = [[],[],[]],
每次都新定义一个path,后续的操作才不会对之前的造成影响
result = [path1,path2,path3]
3.循环条件优化
剪枝操作:在for循环终止条件里进行:
i <= n - (k - path.size()) + 1
216. 组合总和III
在每层递归中,使用以下代码向上返回:
if (sum > target) {
return;
}
这个判断如果放在sum += i后面,会影响回溯过程,即 sum不进行-i 操作
17. 电话号码的字母组合
1. 思路
这一题很巧妙:把数字对应的字符变成一个字符数组:
s.charAt(i) - '0':
例如'9'-'0',由于0的ASCII码为48,9的ASCII码为48+9=57,这两个字符减去就等于减去了他们的码所以57-48等于int类型9。利用这个机制,可以从一个字符串中的数字分解成整型数字。
39. 组合总和
剪枝优化操作:
对数组进行排序,若sum > target则break,break用于跳出循环,continue用于跳过本次循环。
40. 组合总和 II
1.去重技巧
实现树层去重-->使用used数组记录
1.声明数组 定义数组默认值为0
int[] used;
used = new int[candidates.length];
Arrays.fill(used, 0);
2.当使用元素时,设定used[i] = 1(表示正在使用),进行递归
3.当不满足条件回溯返回后,重新设置used[i] = 0
横向遍历时判断,如果used的前一位为0,则表示已经使用过了,重置为0(使用并完成了一轮递归)
131. 分割回文串(难)
使用两个函数,一个回溯,一个判断是否回文:
1.回溯
终止条件为 startIndex == s.length(),循环一开始就判断是否回文,否则直接continue,为什么不break,如:
abcba
当i=1时,ab明显不是回文的,若break则不会判断i++的循环,但是当i=4的时候能够得到回文串因此只能跳过本轮循环,而不是break终止循环。
2.回文
for(int i =start, j =end; i < j; i++,j--)
通过i < j判断,可以免去一次循环,当判断个数为偶数个时,正常执行。奇数个时:
(1)a,由于a肯定是回文的,此时i=0,j=0,直接返回true,不用判断
(2)aba,i=0,j=2,当i,j都为1时,都指向b,b肯定是回文的,省去判断
3.分割字符串
isPalindrome(s,startIndex,i)
93. 复原 IP 地址
1.回溯
终止条件为 pointSum == 长度 - 1,然后单独判断最后一段字符是否有效,每一次循环开始判断是否有效,区间为左闭右闭。不合格的有4种情况:
(1)判断是否为合格的ip地址
(2)不是合法字符:@、*、%等
if (s.charAt(i) >'9'||s.charAt(i)< '0') {
// 遇到⾮数字字符不合法
return false;
}
(3)大小在0-255之间
拿到每一位字符digit,digit = s.charAt(i)- '0';
num = num * 10 + digit;
if(num > 255)
return false;
}
(4)0开头的不合法
if(s.charAt(start) == '0' && start != end) return false;
2.??疑问
(1)、(2)可以合并——》省略(1)???
78. 子集
在循环一开始时,就把path加到result里面,可以不用在main里面单独加一次;
result.add(new ArrayList<>(path))
90. 子集 II
78的思路结合used数组
491. 递增子序列
使用set集合去重,set在每层for之前new,一旦把unordered_set uset放在类成员位置,它控制的就是整棵树!!!
Set<Integer> usedSet = new HashSet<>();
类似于result添加的时候add new ArrayList
result.add(new ArrayList<>(path));
46. 全排列
由于是排列,因此递归的下一层还是从0开始,因此没有startindex,i从0开始,使用used数组去重
法二:
也可以在back()中传入path,用path判断
if (path.contains(nums[i])) {
continue;
}
只使用于不含重复数字的数组 nums,used的使用范围更广
47. 全排列 II
使用一个used数组就行
if (used[i] == 1) continue;
进行树枝去重,如nums[123],前面取过1,则只能取23
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0 ) continue;
进行树层去重,如nums[112],取过1进行拍了,则后面的1不能再取,只能取2
51. N 皇后(难)
1.回溯
- 判断当前棋盘是否有效isVaild(),有效则调用Array2List()方法,把二维数组转换成list,并加入result集合
-
2.二维数组转换成list()
-
3.判断棋盘是否有效
(1)横竖
在单层搜索的过程中,每一层递归,只会选for循环(也就是同一行)里的一个元素,所以不用检查同行,只用写列。45°和135°的定义看个人习惯,由于i是每一层往下递归遍历,所以对角线是从当前位置往上搜寻,没遍历的下一层不用判断(比如当前是(1,1)则不用判断(2,2),判断(0,0)就行了。
(2)45°对角线
对于45°,他的上一个坐标为(row-1,col-1)
(3)135°对角线
同45°的情况,当目标在(1,1),搜索135°则是第一层的是(0,2)即(row-1,col+1)
起始条件: i =row-1, j=col+1;