LeetCode 关于回溯问题的看法

YUAN 2019-06-26

回溯算法( BackTrack )在算法过程中就是类似于枚举算法,尝试在搜索过程中找到问题的解。

使用回溯算法解题的一般步骤

使用回溯算法解题的一般步骤:

  1. 针对所给问题得出一般的解空间
  2. 用回溯搜索方法搜索解空间
  3. 使用深度优先去搜索所有解并包含适当的剪枝操作

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);
    }
}

相关推荐