回溯(抽象成树型结构、一般无返回值backtracking)
1. 理论基础
回溯 和递归相辅相成
- 一般递归函数下面的部分就是回溯的逻辑
- 默认是纯暴力(后续可以剪枝)
应用:
- 组合【没有顺序】
- 切割
- 子集
- 排列【有顺序】
- 棋盘
- N 皇后
- 解数独
回溯法都可以抽象为一个树型结构
- 树的宽度:集合大小
- 树的深度:递归深度
回溯模板
void backtracking(参数){
if(终止条件){
收集结果 # 通常在叶子节点
return;
}
for(集合元素集){
处理节点;
backracking(路径、选择列表); # 递归
回溯,撤销处理结果
}
}
回溯三部曲:
- 递归函数参数、返回值
- 确定终止条件
- 单层递归逻辑
77. 组合(没有顺序)
注意:这里 k:递归的层数
class Solution {
List<Integer> temp = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(1, n, k);
return res;
}
public void backtracking(int start, int n, int k){
if (k == 0){ // 到叶子节点了
res.add(new ArrayList<>(temp));
return;
}
// 注意:如果是取出第一个节点的话:i 的极限是:n - k + 1
// 随着 temp 增大,i 的极限也会向右推进
for (int i = start; i <= n - k + 1; i++) { // i:1 -> 3【n - k + 1】
temp.add(i);
backtracking(i + 1, n, k - 1);
temp.remove(temp.size() - 1); // 回溯
}
}
}
组合问题剪枝
如果早就直到达不成要求的话,就提前终止
216. 组合总和Ⅲ【在一个集合中求解(利用 start 避免得到重复的组合)】
注意:⭐
我们每次都要存储list 内的临时值
否则,最后
- 由于 list 最后一定是 null
- 而 list 是引用类型
- 所以res 中存放的也只是 null
验证下:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
# 如果猜的没错的话,res 中有三个 null
// 理解:不能直接写 res.add(list)
// 1. list 是引用类型
// 2. 由于对于list 的操作,每次都在添加后,马上清除
// 所以如果仅仅存放 list的话只有 null
res.add(new ArrayList<>(list));
再次验证:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
// 相加和为n 的 k 个数
public List<List<Integer>> combinationSum3(int k, int n) {
int sum = 0;
for (int i = 1; i <= 9; i++) {
sum += i;
}
if (n > sum){
return res;
}
backtracking(1, n, k);
return res;
}
public void backtracking(int start, int n, int k){
if (n == 0 && k == 0){
res.add((new ArrayList<>(list)));
return;
}
if (k == 0){
return;
}
// 如果当前值 > n
for (int i = start; i <= 9 - k + 1; i++) { // 确定起始位置
list.add(i);
backtracking(i + 1, n - i, k - 1);
list.remove(list.size() - 1);
}
}
}
17. 电话号码的字母组合【2个集合,无需 start】
class Solution {
List<String> list = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
StringBuilder sb = new StringBuilder();
public List<String> letterCombinations(String digits) {
if (digits.length() == 0){
return list;
}
map.put(2, "abc");
map.put(3, "def");
map.put(4, "ghi");
map.put(5, "jkl");
map.put(6, "mno");
map.put(7, "pqrs");
map.put(8, "tuv");
map.put(9, "wxyz");
backtracking(digits, 0);
return list;
}
public void backtracking(String str, int index){
if (index == str.length()){ // 指向末尾才真正结束
list.add(sb.toString());
return;
}
int temp = Integer.parseInt(str.charAt(index)+"");
String s = map.get(temp); // 第一个数字对应的字符串
for (int j = 0; j < s.length(); j++) {
sb.append(s.charAt(j));
backtracking(str, index + 1);
sb.delete(sb.length() - 1, sb.length());
}
}
}
39. 组合总和
可以重复选取
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtrack(candidates, 0, target);
return res;
}
public void backtrack(int[] arr, int start, int target){
if (target < 0){
return;
}
if (target == 0){
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i < arr.length; i++) {
list.add(arr[i]);
backtrack(arr, i,target - arr[i]); // 由于元素可以重复取,所以 这里 i 不加一了就
list.remove(list.size() - 1);
}
}
}
40. 组合总和Ⅱ【组合去重,先排序,如果和上一个相同就右移】
如果用 Set 的话,底层是红黑树,效率较低!!!
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(candidates, 0, target);
return res;
}
public void backtracking(int[] arr, int start, int target){
if (target < 0){
return;
}
if (target == 0){
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i < arr.length; i++) {
list.add(arr[i]);
backtracking(arr, i + 1, target - arr[i]);
list.remove(list.size() - 1);
while (i < arr.length - 1 && arr[i + 1] == arr[i]){ // 如果下一步和当初选择的一致,那么跳过即可!!!
i++;
}
}
}
}
131. 分割回文串
对应的树型结构【和组合类似】
class Solution {
List<String> list = new ArrayList<>();
List<List<String>> res = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return res;
}
public void backtracking(String str, int index){
if (index == str.length()){
res.add(new ArrayList<>(list));
return;
}
for (int i = index; i < str.length(); i++) { // 注意 index 是定制
String substring = str.substring(index, i + 1);
if (isReverse(substring)) {
list.add(str.substring(index, i + 1));
}else{
continue;
}
backtracking(str, i + 1); // 在 str 的 i + 1 位置处继续递归
list.remove(list.size() - 1);
}
}
public boolean isReverse(String str){
int l = 0;
int r = str.length() - 1;
while (l < r){
if (str.charAt(l) != str.charAt(r)){
return false;
}
l++;
r--;
}
return true;
}
}
93. 复原 IP 地址
class Solution {
StringBuilder sb = new StringBuilder();
List<String> list = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtracking(s, 0, 4);
System.out.println(list);
return list;
}
public void backtracking(String str, int startIndex, int k){
if (startIndex == str.length() && k == 0){
list.add(sb.substring(0, sb.length() - 1)); // 取出末尾 .
return;
}
for (int i = startIndex; i < str.length(); i++) {
String substring = str.substring(startIndex, i + 1);
int len = substring.length(); // 添加字符串的长度
if (isValid(substring)){
sb.append(substring).append(".");
}else {
continue;
}
backtracking(str, i + 1, k - 1);
sb.delete(sb.length() - len - 1, sb.length());
}
}
public boolean isValid(String str){
char[] chars = str.toCharArray();
for (char aChar : chars) {
if (!Character.isDigit(aChar)){
return false;
}
}
if (str.length() > 3) {
return false;
}
int i = Integer.parseInt(str);
if (i < 0 || i > 255){
return false;
}
return (i + "").equals(str); // 不含前导 0
}
}
78. 子集【收集全部结果】
注意:这里不能提前 return
因为第一次 list 为 [],如果 return了,后面的代码不会运行
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0);
return res;
}
public void backtracking(int[] arr, int startIndex){
if (startIndex <= arr.length){
res.add(new ArrayList<>(list)); // 第一次 就收集到了 []
}
for (int i = startIndex; i < arr.length; i++) {
list.add(arr[i]);
backtracking(arr, i + 1);
list.remove(list.size() - 1);
}
}
}
90. 子集Ⅱ【先排序,后去重】
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtracking(nums, 0);
return res;
}
public void backtracking(int[] arr, int startIndex){
if (startIndex <= arr.length){
res.add(new ArrayList<>(list)); // 第一次 就收集到了 []
}
for (int i = startIndex; i < arr.length; i++) {
list.add(arr[i]);
backtracking(arr, i + 1);
list.remove(list.size() - 1);
while (i < arr.length - 1 && arr[i + 1] == arr[i]){
i++;
}
}
}
}
491. 递增子序列
但是同一树枝上是可以重复取的,其实也没有重复取,它取的是不同的元素,仅仅是不同的元素数值相同而已
前面的 7 下的所有分支,一定包含了后面的 7 的所有分支
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0);
return res;
}
public void backtracking(int[] arr, int startIndex){
if (startIndex <= arr.length && list.size() >= 2){
res.add(new ArrayList<>(list)); // 第一次 就收集到了 []
}
Set<Integer> set = new HashSet<>(); // 每一层都用 set 去重
for (int i = startIndex; i < arr.length; i++) {
if (isMore(arr[i]) && set.add(arr[i])){
list.add(arr[i]);
}else {
continue;
}
backtracking(arr, i + 1);
list.remove(list.size() - 1);
}
}
public boolean isMore(int num){
if (list.size() == 0){
return true;
}
Integer integer = list.get(list.size() - 1);
return (num - integer) >= 0;
}
}
46. 全排列【强调元素顺序】
看下排列和组合的区别???
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
int[] used;
public List<List<Integer>> permute(int[] nums) {
used = new int[nums.length];
backtracking(nums);
return res;
}
public void backtracking(int[] arr){
if (list.size() == arr.length){
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < arr.length; i++) {
if (used[i] != 1) {
list.add(arr[i]);
used[i] += 1;
}else {
continue;
}
backtracking(arr);
list.remove(list.size() - 1);
used[i] = 0; // 回溯
}
}
}
47. 全排列Ⅱ(有重复的)
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
int[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
used = new int[nums.length];
Arrays.sort(nums); // 先排序
backtracking(nums);
return res;
}
public void backtracking(int[] arr){
if (list.size() == arr.length){
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < arr.length; i++) {
if (used[i] != 1) {
list.add(arr[i]);
used[i] += 1;
}else {
continue;
}
backtracking(arr);
list.remove(list.size() - 1);
used[i] = 0; // 回溯
while (i < arr.length - 1 && arr[i + 1] == arr[i]){
i++;
}
}
}
}
标签:arr,return,int,list,backstracking,回溯,new,backtracking
From: https://www.cnblogs.com/aclq/p/17664449.html