leetcode——回溯算法题目合集

回溯算法简介

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止

39. 组合总和

题目:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 

示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

思路:
回溯算法的重点就是找到终止点和剪枝,此题每下个递归时target值为当前target值减去当前数组元素值,当target为0时表示找到结果集,返回。如果target < 0则表示之后的递归都不满足情况,直接返回,即剪枝。

注意:对于数组的回溯算法,需要先对数组进行排序,否则不能完成正确剪枝

代码:

class Solution {
    public List> combinationSum(int[] c, int target) {
        Arrays.sort(c);
        List> res = new ArrayList<>();
        if(c.length == 0) return res;
        helper(c,0,target,new ArrayList(),res);
        return res;
    }
    private void helper(int[] c,int start,int target,List list,List> res){
        //剪枝
        if(target < 0) return;
        if(target == 0){
            res.add(new ArrayList<>(list)); //注意这里插入的是List的镜像,否则只能保存它的引用,结果出错
        }
        for(int i = start;i < c.length;i++){
            list.add(c[i]);
            helper(c,i,target-c[i],list,res);
            list.remove(list.size()-1);
        }
    }
}

40.组合总和 II

题目:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

思路:
这道题只需要在39题的基础上去重即可,即在数组排序后,比较当前元素和前一个元素是否相同,如果相同则continue

代码:

class Solution {
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        List> res = new ArrayList<>();
        if(candidates.length <= 0) return res;
        helper(candidates,target,0,new ArrayList(),res);
        return res;
    }
    
    private void helper(int[] candidates,int target,int start,List list,List> res){
        if(target < 0) return;
        if(target == 0){
            res.add(new ArrayList<>(list));
            return;
        }
        
        for(int i = start;i < candidates.length;i++){
            if(i != start && candidates[i] == candidates[i-1]) continue; //去重
            list.add(candidates[i]);
            helper(candidates,target-candidates[i],i+1,list,res);
            list.remove(list.size()-1);
        }
    }
}

46. 全排列

题目
给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

思路
创建一个标识数组,和元素数组等长,用来记录当前索引的元素是否被使用,被使用赋值为1,没有使用为0。最外层循环固定结果的第一个元素,后面的元素由递归得到,每次递归都数组开头选取元素,如果当前元素已经使用,则选取下一个元素,直到第一个元素为数组最后一个值。这里递归的终止条件是集合中的元素长度等于数组的长度。
注意:数组需要先排序。

代码

class Solution {
    public List> permute(int[] nums) {
        Arrays.sort(nums);
        List> res = new ArrayList<>();
        if(nums.length == 0) return res;
        //维护一个等长数组,用来判断当前元素是否访问过
        int[] flag = new int[nums.length];
        helper(nums,new ArrayList(),res,flag);
        return res;
    }
    
    private void helper(int[] nums,List list,List> res,int[] flag){
        if(list.size() == nums.length){
            //插入镜像
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i = 0;i < nums.length;i++){
            if(flag[i] == 0){
                flag[i] = 1;
                list.add(nums[i]);
                helper(nums,list,res,flag);
                list.remove(list.size()-1);
                flag[i] = 0;
            }
        }
    }
}

47. 全排列 II

题目
给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

思路
在46题的基础上去重即可,这里去重的方法是设定一个变量储存上一个访问元素,如果当前元素和上个访问元素相等,则continue,也可以使用40题的去重方法。当然这些都是建立在数组有序的基础上的。

代码

class Solution {
    List> res = new ArrayList<>();
    public List> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        if(nums.length == 0) return res;
        int[] flag = new int[nums.length];
        helper(nums,flag,new ArrayList());
        return res;
    }
    
    private void helper(int[] nums,int[] flag,List list){
        if(list.size() == nums.length){
            res.add(new ArrayList<>(list));
            return;
        }
        //用来保存上一次访问的元素
        int lastUsed = Integer.MIN_VALUE;
        for(int i = 0;i < nums.length;i++){           
            //判断当前数组元素是否和上一个相同
            if(flag[i] == 0 && nums[i] != lastUsed){
                flag[i] = 1;
                list.add(nums[i]);
                helper(nums,flag,list);
                lastUsed = nums[i];
                list.remove(list.size()-1);
                flag[i] = 0;
            }
        }
    }
}

78. 子集

题目
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

思路
每次递归取下一个元素,且每次递归都将集合加入结果集,直到集合长度等于数组长度,剪枝。

代码

class Solution {
    public List> subsets(int[] nums) {
        Arrays.sort(nums);
        List> res = new ArrayList<>();
        if(nums.length == 0) return res;
        helper(nums,0,new ArrayList<>(),res);
        return res;
    }
    
    private void helper(int[] nums,int start,List list,List> res){
        if(list.size() == nums.length){
            res.add(new ArrayList<>(list));
            return;
        }
        res.add(new ArrayList<>(list));
        for(int i = start;i < nums.length;i++){
            list.add(nums[i]);
            helper(nums,i+1,list,res);
            list.remove(list.size()-1);
        }
    }
}

90. 子集 II

题目
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

思路
思路和78一致,去重即可,采用上述题目的两种去重方法都可以

代码

class Solution {
    public List> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        List> res = new ArrayList<>();
        if(nums.length == 0) return res;
        helper(nums,0,new ArrayList<>(),res);
        return res;
    }
    private void helper(int[] nums,int start,List list,List> res){
        res.add(new ArrayList<>(list));
        for(int i = start;i < nums.length;i++){
            if(i > start && nums[i] == nums[i-1])
                continue;
            list.add(nums[i]);
            helper(nums,i+1,list,res);
            list.remove(list.size()-1);
        }
    }
}

216. 组合总和 III

题目
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

思路:
比起上面几题,这道题添加了一个限定因素,即元素出现个数,在递归时加入k值控制即可

代码:

class Solution {
    public List> combinationSum3(int k, int n) {
        List> res = new ArrayList<>();
        helper(1,n,k,new ArrayList<>(),res);
        return res;
    }
    private void helper(int start,int target,int count,List list,List> res){
        if(target == 0 && count == 0){
            res.add(new ArrayList<>(list));
            return;
        }
        //剪枝,减少不必要的循环
        if(target <= 0 || count <= 0) return;

        for(int i = start;i <= 9;i++){
            list.add(i);
            helper(i+1,target-i,count-1,list,res);
            list.remove(list.size()-1);
        }
    }
}

你可能感兴趣的:(LeetCode,leetcode,回溯算法)