回溯算法( BackTrack )在算法过程中就是类似于枚举算法,尝试在搜索过程中找到问题的解。
使用回溯算法解题的一般步骤
使用回溯算法解题的一般步骤:
- 针对所给问题得出一般的解空间
- 用回溯搜索方法搜索解空间
- 使用深度优先去搜索所有解并包含适当的剪枝操作
LeetCode 使用回溯算法的题目主要有 36 题,代表性有 N Queens(51,52), SubSets(78), Permutation(46(distinct), 47(with duplicates)), Combination, Combination Sum.
Problems
SubSets:
Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,3], a solution is:
[[3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[]]
SubSets 这道题要求 print 出定长 int array 中所有子集合,使用回溯算法遍历定长 array,然后回溯来选择出所有可能的组合.
针对 backTrack 时间复杂度的分析直接复习结果集的大小有效
Runtime: O(2^n), Space: O(n)
Solution:
public List<List<Integer>> subsets(int[] nums) {
if(nums == null || nums.length == 0) return null;`
List<List<Integer>> res = new ArrayList<List<Integer>>();
helper(nums, res, 0, new ArrayList<>());
return res;
}
public void helper(int[] nums, List<List<Integer>> res, int index, List<Integer> temp) {
//考虑空子集的情况
res.add(new ArrayList<>(temp));
for(int i = index; i < nums.length; i++) {
//每次加下一个index的element
temp.add(nums[i]);
//深度优先到下一层
helper(nums, res, i + 1, temp);
//backTracking
temp.remove(temp.size() - 1);
}
}
针对如果输入数组有重复的元素,但是要求输出的结果没有重复,可以一些去重的方法并注意防止溢出
If nums = [1,2,2], a solution is
[[2],[1],[1,2,2],[2,2],[1,2],[]]
Solution:
public List<List<Integer>> subsetsWithDup(int[] nums) {
if(nums == null || nums.length == 0) return null;
List<List<Integer>> res = new ArrayList<List<Integer>>();
//对input数组进行排序,准备为数组进行去重
Arrays.sort(nums);
helper(nums, res, 0, new ArrayList<>());
return res;
}
public void helper(int[] nums, List<List<Integer>> res, int index, List<Integer> temp) {
res.add(new ArrayList<>(temp));
for(int i = index; i < nums.length; i++) {
//去重复,并且防止溢出
if(i != index && nums[i] == nums[i - 1]) continue;
temp.add(nums[i]);
helper(nums, res, i + 1, temp);
temp.remove(temp.size() - 1);
}
}
Combination Sum
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [2, 3, 6, 7] and target 7, A solution set is:
[[7],[2, 2, 3]]
这道题要求given一个定长数组,要求找到所有子数组集合以至于子数组所有数的和等于目标数,可以运用回溯算法,区间为given的定长数组
Solution:
同样是用回溯的方法,但是针对数组中子数可以重复使用time:O(2^n), space: O(n)
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates == null || candidates.length == 0) return null;
List<List<Integer>> res = new ArrayList<List<Integer>>();
helper(candidates, target, res, new ArrayList<Integer>(), 0);
return res;
}
public void helper(int[] candidates, int target, List<List<Integer>>res, List<Integer> temp, int index) {
if(target < 0) return;
else if(taget == 0) res.add(new ArrayList<Integer>(temp));
else {
for(int i = index; i < candidats.length; i++) {
temp.add(candidates[i]);
//DFS dive into next level, 因为需要重复使用所以递归调用i 没有变化
helper(candidates, target - candidates[i], res, temp, i);
temp.remove(temp.size() - 1);
}
}
变种,针对CombinationSum 数组中子数只可以被使用一次:
时间复杂度仍然为O(2^n), 空间复杂度为O(n)
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates == null || candidates.length == 0) return null;
List<List<Integer>> res = new ArrayList<List<Integer>>();
helper(candidates, target, res, new ArrayList<Integer>(), 0);
return res;
}
public void helper(int[] candidates, int target, List<List<Integer>>res, List<Integer> temp, int index) {
if(target < 0) return;
else if(taget == 0) res.add(new ArrayList<Integer>(temp));
else {
for(int i = index; i < candidats.length; i++) {
//进行防溢出和去重
if(i != index && candidates[i] == candidates[i - 1]) continue;
temp.add(candidates[i]);
//DFS dive into next level, 因为需要重复使用所以递归调用i 没有变化
helper(candidates, target - candidates[i], res, temp, i);
temp.remove(temp.size() - 1);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。