回溯法详解
一、概述
解决一个回溯问题,实际上就是一个决策树的遍历过程。只需要考虑以下三个问题:
(1)路径:已经做出的选择。
(2)选择列表:也就是你当前所做出的选择。
(3)结束条件:也就是到达决策树底层,无法在做出的条件。
注意:
(1)ans为全局变量
(2)路径最后新建如:ans.add(new ArrayList<>(track))
(3)track长度是全数还是部分,部分用i+1,全数为cur+1。
二、代码模板
result = [] def backtrack(路径, 选择列表): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择
核心:其实就是for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。
三、代码实战
(1)无重复的全排列:非交换
class Solution{ List<List<Integer>> res=new ArrayList<>(); public List<List<Integer>>premute(int []nums){ List<Integer>track=new ArrayList<>(); backTrack(nums,track); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素 // 结束条件:nums 中的元素全都在 track 中出现 public void backTrack(int nums[],List<Integer>track){ //触发结束条件 if(nums.length==track.size()){ res.add(new ArrayList<>(track)); return; } for(int i=0;i<nums.length;i++){ if(track.contains(nums[i])){ continue; } track.add(nums[i]); backTrack(nums,track); track.remove(track.size()-1); } } }
(2)无重复排列:使用信号量代替contains方法。
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> res = new ArrayList<>(); int[] visited = new int[nums.length]; backtrack(res, nums, new ArrayList<Integer>(), visited); return res; } private void backtrack(List<List<Integer>> res, int[] nums, ArrayList<Integer> tmp, int[] visited) { if (tmp.size() == nums.length) { res.add(new ArrayList<>(tmp)); return; } for (int i = 0; i < nums.length; i++) { if (visited[i] == 1) continue; visited[i] = 1; tmp.add(nums[i]); backtrack(res, nums, tmp, visited); visited[i] = 0; tmp.remove(tmp.size() - 1); } } }
(3)无重复的全排列:交换
class Solution { List<List<Integer>>res=new ArrayList<>(); public List<List<Integer>> permute(int[] nums) { backTrack(nums,0); return res; } public void backTrack(int[]nums,int cur){ if(cur==nums.length){ List<Integer>track=new ArrayList<>(); for(int i:nums){ track.add(i); } res.add(track); return; } for(int i=cur;i<nums.length;i++){ swap(nums,cur,i); backTrack(nums,cur+1); swap(nums,cur,i); } } public void swap(int nums[],int i,int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
(4)若排列中可以出现重复元素,求全排列
给定一个可包含重复数字的序列,返回所有不重复的全排列。 输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ] class Solution { Set<List<Integer>> set = new HashSet<>(); public List<List<Integer>> permuteUnique(int[] nums) { List<Integer>list=new ArrayList<>(); backTrack(nums,0); List<List<Integer>>res=new ArrayList<>(set); return res; } public void backTrack(int[]nums,int cur){ if(cur==nums.length){ List<Integer>track=new ArrayList<>(); for(int i:nums){ track.add(i); } set.add(track); return; } for(int i=cur;i<nums.length;i++){ swap(nums,cur,i); backTrack(nums,cur+1); swap(nums,cur,i); } } public void swap(int nums[],int i,int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
四、以上都是判断条件都是根据是否cur==nums.length和track.size()==nums.length;当求子集时,该如何操作呢?即track.size() in [0,nums.length];
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
除了迭代法,还有回溯法。
class Solution{ List<List<Integer>>res=new ArrayList<>(); int k; public List<List<Integer>>subsets(int[] nums){ List<Integer>track=new ArrayList<>(); for(k=0;k<nums.length+1;k++){ backTrack(nums,track,0); } return res; } public void backTrack(int[] nums,List<Integer>track,int first){ if(track.size()==k){ res.add(new ArrayList<>(track)); return; } for(int i=first;i<nums.length;i++){ track.add(nums[i]); backTrack(nums,track,i+1); track.remove(track.size()-1); } } }