回溯(backtracking)
回溯(抽象成树型结构、一般无返回值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++;
}
}
}
}
332. 重新安排行程
- 先排序
- 那么找到的第一个就是符合条件的,就是最小的
class Solution {
boolean[] used;
List<ticket> list = new ArrayList<>();
List<List<ticket>> res = new ArrayList<>();
int N;
ticket[] arr;
public List<String> findItinerary(List<List<String>> tickets) {
N = tickets.size();
used = new boolean[N];
arr = new ticket[N];
tickets.sort(new Comparator<List<String>>() {
@Override
public int compare(List<String> o1, List<String> o2) {
for (int i = 0; i < o1.size(); i++) {
String A = o1.get(i);
String B = o2.get(i);
for (int j = 0; j < A.length(); j++) {
if (A.charAt(j) != B.charAt(j)) {
return A.charAt(j) - B.charAt(j);
}
}
}
return 0;
}
});
for (int i = 0; i < N; i++) {
List<String> list = tickets.get(i);
String from = list.get(0);
String to = list.get(1);
arr[i] = new ticket(from, to);
}
backtracking(arr);
List<String> ans = new ArrayList<>();
List<ticket> tickets1 = res.get(0);
ans.add("JFK");
for (ticket ticket : tickets1) {
ans.add(ticket.to);
}
return ans;
}
// 1. 假定所有机票至少存在一种合理的行程。
// 2. 且所有的机票 必须都用一次 且 只能用一次。
public void backtracking(ticket[] arr) {
if (list.size() == N) {
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < N; i++) {
if (used[i]) {
continue;
}
ticket ticket = arr[i];
if (list.size() == 0 && !ticket.from.equals("JFK")) { // 从 "JFK" 开始
continue;
}
if (list.size() > 0 && !list.get(list.size() - 1).to.equals(ticket.from)) {
continue;
}
list.add(ticket);
used[i] = true;
backtracking(arr);
if (list.size() == N){
return;
}
list.remove(list.size() - 1);
used[i] = false;
}
}
}
class ticket {
String from;
String to;
public ticket(String from, String to) {
this.from = from;
this.to = to;
}
}
60. 排列序列
由于数组是有序的,那么我们 backtracking 得到的全排列就是从小到大的顺序的
class Solution {
boolean[] isVisited;
List<Integer> list = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
int count;
public String getPermutation(int n, int k) {
count = k;
isVisited = new boolean[n + 1];
backtracking(n, k);
StringBuilder sb = new StringBuilder();
List<Integer> list = res.get(k - 1);
for (Integer integer : list) {
sb.append(integer);
}
return sb.toString();
}
public void backtracking(int n, int k){
if (count == 0){ // 只取 k 个
return;
}
if (list.size() == n){
res.add(new ArrayList<>(list));
count--;
return;
}
for (int i = 1; i <= n; i++) { // 全排列
if (isVisited[i]){ // 访问过了
continue;
}
isVisited[i] = true;
list.add(i);
backtracking(n, k);
list.remove(list.size() - 1);
isVisited[i] = false;
}
}
}
698. 划分为k 个相等的元素
class Solution {
int count;
public boolean canPartitionKSubsets(int[] nums, int k) {
count = k;
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % k != 0){
return false;
}
int[] bucket = new int[k];
int weight = sum / k; // 每一个子集和:>= 任何一个数字【最大值】
Arrays.sort(nums);
swap(nums, 0, nums.length - 1); // 逆序排序,便于理解逻辑!!!
if (weight < nums[0]){
return false; // 最大值 > 桶容量
}
// 我们可以想象划分子集问题,可以看成向k个桶中放球,每个桶的承重为subSum,而现在nums中存放着重量不一的多个球,如果nums中的球都能放到k个桶中,则可以划分子集成功。
return backtracking(nums, bucket, 0, weight);
}
public boolean backtracking(int[] arr, int[] bucket, int start, int target){
if (start == arr.length){ // 所有的球都找到了位置
return true;
}
int nowWeight = arr[start]; // 选个球
for (int i = 0; i < bucket.length; i++) {
if (i - 1 >= 0 && bucket[i] == bucket[i - 1]){ // 如果所有都相同的话,就一直向右推进【剪枝】
continue;
}
if (nowWeight + bucket[i] <= target){
bucket[i] += nowWeight;
if(backtracking(arr, bucket, start + 1, target)){
return true;
}
bucket[i] -= nowWeight;
}
}
return false;
}
public void swap(int[] arr, int l, int r){
while (l < r){
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
}
数据最节约的备份方法【二分 + 回溯】
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] split = in.nextLine().split(",");
int[] arr = new int[split.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(split[i]);
}
Arrays.sort(arr);
reverse(arr, 0, arr.length - 1);
// 先用二分法求出光盘数量
int min = 1;
int max = arr.length;
int res = 0;
while (min <= max){
int mid = min + (max - min) / 2;
int[] bucket = new int[mid];
if (backtracking(arr, bucket, 0, 500)){
res = mid;
max = mid - 1; // 尽量取小值
}else {
min = mid + 1;
}
}
System.out.println(res);
}
public static boolean backtracking(int[] arr, int[] bucket, int start, int target){
if (start == arr.length){
return true;
}
int nowWeight = arr[start]; // 当前小球
for (int i = 0; i < bucket.length; i++) {
if (i + 1 < bucket.length && bucket[i] == bucket[i + 1]){
continue;
}
if (nowWeight + bucket[i] <= target){
bucket[i] += nowWeight;
if (backtracking(arr, bucket, start + 1, target)){
return true;
}
bucket[i] -= nowWeight;
}
}
return false;
}
public static void reverse(int[] arr, int l, int r){
while (l < r){
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
}
叠木块
Ⅰ【每层最多2块 ===> 使用贪心策略,每次都是最大的 或者 最大 + 最小配成】
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] split = in.nextLine().split(" ");
int[] arr = new int[split.length];
int sum = 0;
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(split[i]);
sum += arr[i];
}
Arrays.sort(arr);
reverse(arr);
int res = -1;
for (int i = arr.length; i >= 1; i--) {
if (sum % i == 0 && isValid(arr, sum / i)){
res = i;
break;
}
}
System.out.println(res);
}
public static void reverse(int[] arr){
int l = 0;
int r = arr.length - 1;
while (l < r){
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
public static boolean isValid(int[] arr, int target){
int l = 0;
int r = arr.length - 1;
if (arr[l] > target){
return false;
}
while (l < r){
if (arr[l] == target){
l++;
continue;
}
// 如果不是最大和最小的凑成一对的话,那么最小的就至少有3块,不符合题意
if (arr[l] + arr[r] == target){
l++;
r--;
}else {
return false;
}
}
return true;
}
}
Ⅱ【至少有2层,而且每层可以多个木块】
public class mukuai1 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] split = in.nextLine().split(" ");
int[] arr = new int[split.length];
int sum = 0;
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(split[i]);
sum += arr[i];
}
Arrays.sort(arr);
reverse(arr);
int res = -1;
for (int i = arr.length; i >= 2; i--) {
int[] bucket = new int[i];
if (sum % i == 0 && backtracking(arr, bucket, 0, sum / i)){
res = i;
break;
}
}
System.out.println(res);
}
public static boolean backtracking(int[] arr, int[] bucket, int start, int target){
if (start == arr.length){
return true;
}
int now = arr[start];
for (int i = 0; i < bucket.length; i++) {
if (i + 1 < bucket.length && bucket[i + 1] == bucket[i]){
continue;
}
if (now + bucket[i] <= target){
bucket[i] += now;
if (backtracking(arr, bucket, start + 1, target)){
return true;
}
bucket[i] -= now;
}
}
return false;
}
public static void reverse(int[] arr){
int l = 0;
int r = arr.length - 1;
while (l < r){
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
}
计算礼品发放的最小分组数目【也是最多2份】
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int target = Integer.parseInt(in.nextLine());
String[] split = in.nextLine().split(" ");
Integer[] arr = new Integer[split.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(split[i]);
}
Arrays.sort(arr, (o1, o2) -> o2 - o1);
int l = 0;
int r = arr.length - 1;
int res = 0;
while (l < r) {
if (arr[l] == target || arr[l] + arr[r] > target) {
l++;
res++;
continue;
}
res++;
l++;
r--;
}
if (l == r){ // 还剩最后一个的情形
res++;
}
System.out.println(res);
}
}
313. 迷宫问题【DFS】
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static int row;
static int col;
static int[][] arr;
static List<String> list = new ArrayList<>();
static List<String> res = new ArrayList<>();
static int count = Integer.MAX_VALUE;
static boolean[][] isVisited;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
row = in.nextInt();
col = in.nextInt();
arr = new int[row][col];
isVisited = new boolean[row][col];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
arr[i][j] = in.nextInt();
}
}
backtracking1(0, 0);
for (String re : res) {
System.out.println(re);
}
}
public static void backtracking1(int i, int j){
list.add("(" + i + "," + j + ")"); // 当前
if (i == row - 1 && j == col - 1){
if (count > list.size()){
count = list.size();
res = new ArrayList<>(list);
}
return;
}
if (i + 1 < row && arr[i + 1][j] == 0 && !isVisited[i + 1][j]) {
isVisited[i + 1][j] = true;
backtracking1(i + 1, j);
list.remove(list.size() - 1);
isVisited[i + 1][j] = false;
}
if (j + 1 < col && arr[i][j + 1] == 0 && !isVisited[i][j + 1]) {
isVisited[i][j + 1] = true;
backtracking1(i, j + 1);
list.remove(list.size() - 1);
isVisited[i][j + 1] = false;
}
if (i - 1 >= 0 && arr[i - 1][j] == 0 && !isVisited[i - 1][j]) {
isVisited[i - 1][j] = true;
backtracking1(i - 1, j);
list.remove(list.size() - 1);
isVisited[i - 1][j] = false;
}
if (j - 1 >= 0 && arr[i][j - 1] == 0 && !isVisited[i][j - 1]) {
isVisited[i][j - 1] = true;
backtracking1(i, j - 1);
list.remove(list.size() - 1);
isVisited[i][j - 1] = false;
}
}
}
324. 最大时间
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static boolean[] isVisited;
static List<List<Integer>> res = new ArrayList<>();
static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
String substring = s.substring(1, s.length() - 1);
String[] split = substring.split(",");
int[] arr = new int[split.length];
isVisited = new boolean[arr.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(split[i]);
if (arr[i] >= 10){ // 1. 超过 10 的数
System.out.println("invalid");
return;
}
}
backtracking(arr);
Collections.sort(res, (o1, o2) -> {
for (int i = 0; i < o1.size(); i++) {
if (!o1.get(i).equals(o2.get(i))){
return o2.get(i) - o1.get(i);
}
}
return 0;
});
if (res.size() == 0){
System.out.println("invalid");
return;
}
List<Integer> ans = res.get(0);
System.out.println(ans.get(0) + "" + ans.get(1) +":"+
ans.get(2) + "" + ans.get(3) +":"+
ans.get(4) + "" + ans.get(5));
}
public static void backtracking(int[] arr){
if (list.size() == isVisited.length && list.get(0) <= 2 && list.get(2) <= 5 && list.get(4) <= 5){
if (list.get(0) == 2 && list.get(1) >= 4){ // 第一个数为2,第二个数大于等于 4 时舍弃!!!
return;
}
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < arr.length; i++) {
if (!isVisited[i]){
list.add(arr[i]);
isVisited[i] = true;
backtracking(arr);
list.remove(list.size() - 1);
isVisited[i] = false;
}
}
}
}