回溯定义 按照维基百科上给到的定义:回溯是一种最优选择法,如果发现某一些选择不符合目标,那么就会进行退回操作
在了解回溯算法之前,首先就不得不提到一个概念「递归」,递归 和 回溯 在基本的逻辑上其实是差不多的,只不过回溯都含有撤销操作,而递归则是一条路走到底,两者都属于暴力搜索法。
最经典的题目就属于「八皇后」
递归 斐波拉契数
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。509. 斐波那契数
这一题就是典型的递归,其公式表达为 F(x) = F(x - 1) + F(x - 2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int fib (int n) { return add(n); } private int add (int n) { if (n == 0 ){ return 0 ; } if (n == 1 ){ return 1 ; } return add(n-1 ) + add(n-2 ); } }
递归和循环的转换 每一次的递归都可以转换为「循环」,递归其实就是将上一次方法的计算值带入到下一次的计算当中,而循环其实也是有这个能力的,例如将上面的递归改成循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int fib (int n) { return add(n); } private int add (int n) { if (n == 0 ){ return 0 ; } if (n == 1 ){ return 1 ; } int [] array = new int [n + 1 ]; array[0 ] = 0 ; array[1 ] = 1 ; int total = 1 ; for (int i = 2 ; i <= n; i++){ array[i] = array[i-1 ] + array[i - 2 ]; } return array[n]; } }
for 循环的版本还有很多,但是与本文的「回溯」没太多关系,感兴趣的可以去看力扣的官方题解,里面的方法比较多。
递归所不能解决的问题 例如一个数组[1,2,2,3],需要算出该数组有多少个不重复的子集,这就是递归所无法解决的问题,类似的还有组合、切割等
回溯 回溯就是在递归的过程中不断判断当前条件与目标是否一致,如果不一致的话,那么就需要撤销当前的操作(俗称剪枝),回溯通常用来解决以下几个问题:
排列
切割
子集
组合
棋盘问题
排列
百度百科的定义:从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(permutation)。特别地,当m=n时,这个排列被称作全 排列(all permutation)。
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
标准模版一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public List<List<Integer>> permute(int [] nums) { List<List<Integer>> result = new ArrayList<>(); List<Integer> list = new ArrayList(); for (int i : nums){ list.add(i); } search(nums.length,list,result,0 ); return result; } private void search (int n,List<Integer> tempList,List<List<Integer>> result,int index) { if (index == n){ result.add(new ArrayList(tempList)); return ; } for (int i = index; i < n; i++){ Collections.swap(tempList,index,i); search(n,tempList,result,index + 1 ); Collections.swap(tempList,i,index); } } }
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> permuteUnique(int [] nums) { Boolean[] visited = new Boolean[nums.length]; Arrays.fill(visited,false ); Arrays.sort(nums); search(new ArrayList<>(),visited,nums); return result; } private void search (List<Integer> list,Boolean[] visited,int [] nums) { if (list.size() == nums.length){ result.add(new ArrayList<>(list)); return ; } for (int i = 0 ; i< nums.length; i++){ if (visited[i]){ continue ; } if (i > 0 && nums[i] == nums[i - 1 ] && visited[i - 1 ] == false ){ continue ; } visited[i] = true ; list.add(nums[i]); search(list,visited,nums); visited[i] = false ; list.remove(list.size() - 1 ); } } }
切割
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { int [] nums = new int []{}; public boolean canPartitionKSubsets (int [] nums, int k) { int sum = Arrays.stream(nums).reduce(0 ,Integer::sum); if (sum % k != 0 ){ return false ; } Arrays.sort(nums); this .nums = nums; return backtracking(k,sum / k, 0 , 0 ,new boolean [nums.length]); } private boolean backtracking (int k,int target,int total,int currentIndex,boolean [] used) { if (k == 1 ){ return true ; } if (total == target){ return backtracking(k - 1 , target, 0 , 0 ,used); } for (int i = currentIndex; i< nums.length; i++){ if (used[i]){ continue ; } if (total + nums[i] > target){ continue ; } used[i] = true ; if (backtracking(k,target,total + nums[i],i + 1 ,used)){ return true ; } used[i] = false ; } return false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { String S = "" ; List<List<String>> result = new ArrayList<>(); public List<List<String>> partition(String s) { this .S = s; backtracking(0 ,new ArrayList<>(),s); return result; } private void backtracking (int curr,List<String> list,String s) { if (curr == s.length()){ result.add(new ArrayList<>(list)); return ; } for (int i = curr; i < s.length(); i++){ String tmp = s.substring(curr,i + 1 ); if (isPalindrome(tmp)){ list.add(tmp); backtracking(i + 1 ,list,s); list.remove(list.size() - 1 ); } } } private boolean isPalindrome (String s) { if (s == null || s.length() == 0 ){ return false ; } return s.equals(new StringBuilder(s).reverse().toString()); } }
子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> subsets(int [] nums) { backtracking(0 ,nums,new ArrayList<>()); return result; } private void backtracking (int current, int [] nums, List<Integer> list) { if (current == nums.length){ result.add(new ArrayList<>(list)); return ; } backtracking(current + 1 , nums,list); list.add(nums[current]); backtracking(current + 1 , nums,list); list.remove(list.size() - 1 ); } }
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
for 循环版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> subsetsWithDup(int [] nums) { List<Integer> temp = new ArrayList<>(); Arrays.sort(nums); result.add(temp); backtracking(0 ,nums,temp); return result; } private void backtracking (int index,int [] nums,List<Integer> list) { if (index == nums.length){ return ; } for (int i = index; i< nums.length; i++){ if (i > index && nums[i] == nums[i - 1 ]){ continue ; } list.add(nums[i]); result.add(new ArrayList<>(list)); backtracking(i + 1 ,nums,list); list.remove(list.size() - 1 ); } } }
递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> subsetsWithDup(int [] nums) { List<Integer> temp = new ArrayList<>(); Arrays.sort(nums); backtracking(0 ,nums,temp,new boolean [nums.length]); return result; } private void backtracking (int index,int [] nums,List<Integer> list,boolean [] used) { if (index == nums.length){ result.add(new ArrayList<>(list)); return ; } backtracking(index + 1 ,nums,list,used); if (index > 0 && nums[index] == nums[index-1 ] && !used[index - 1 ]){ return ; } used[index] = true ; list.add(nums[index]); backtracking(index + 1 , nums, list, used); list.remove(list.size() - 1 ); used[index] = false ; } }