回溯算法集合

11 篇文章 0 订阅

回溯算法

1.回溯算法与DFS的区别
DFS是朝某一个方向搜索,而回溯算法建立在DFS之上,在回溯中,达到结束条件后,恢复状态,回复上一层,再次搜索。因此回溯和DFS的区别在于有无状态重置(恢复状态)
2.使用回溯算法的时机

一般遇到子集、排列组合、搜索类问题时需要用到

子集与组合问题

子集

给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集

在这里插入图片描述

    public List<List<Integer>> subsets(int[] nums) {
        int len=nums.length;
        List<List<Integer>> res=new ArrayList<>();
        bachTrack(nums,0,new ArrayList<>(),res);
        return res;
    }
    public void bachTrack(int[] nums,int start,ArrayList<Integer> temp,List<List<Integer>> res){
        res.add(new ArrayList<>(temp));
        for(int i=start;i<nums.length;i++){
            temp.add(nums[i]);
            //递归进入下一层,此时从i+1开始遍历
            bachTrack(nums,i+1,temp,res);
            temp.remove(temp.size()-1);
        }
    }

子集plus

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)
在这里插入图片描述

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res=new ArrayList<>();
        backTrack(nums,0,res,new ArrayList<>());
        return res;
    }
    public void backTrack(int[] nums,int index,List<List<Integer>> res,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]);
            backTrack(nums,i+1,res,temp);
            temp.remove(temp.size()-1);
        }
    

组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合

在这里插入图片描述

    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res=new ArrayList<>();
        backTrack(n,k,1,res,new ArrayList<>());
        return res;
    }
    public void backTrack(int n,int k,int start,List<List<Integer>> res,List<Integer> temp){
    	当组合够k个元素时,就打破递归
        if(temp.size()==k){
            res.add(new ArrayList<>(temp));
            return;
        }
            
        for(int i=start;i<=n;i++){
            temp.add(i);
            backTrack(n,k,i+1,res,temp);
            temp.remove(temp.size()-1);
        }
    }

组合总和

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

在这里插入图片描述

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res=new ArrayList<>();
        Arrays.sort(candidates);
        backTrack(candidates,target,0,0,res,new ArrayList<>());
        return res;
    }
    public void backTrack(int[] candidates,int target,int sum,int start,List<List<Integer>> res,ArrayList<Integer> temp){
    	//当sum==target就打破循环
        if(sum==target){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=start;i<candidates.length;i++){
        	//注意此处要开辟一个新的变量来接收sum,否则sum数据会混乱
            int rs=candidates[i]+sum;
            if(rs<=target){
                temp.add(candidates[i]);
                //因为每个元素可以被重复使用,所以递归依旧从i开始
                backTrack(candidates,target,rs,i,res,temp);
                temp.remove(temp.size()-1);
            }else
                break;
        }
    }

组合总和plus

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。

    private List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTrack(candidates,0,target,0,new ArrayList<>());
        return res;
    }
    public void backTrack(int[] candidates,int start,int target,int sum,ArrayList<Integer> temp){
        if(sum==target){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=start;i<candidates.length;i++){
        	//排序去重,相同的元素使用一次
            if(i>start&&candidates[i]==candidates[i-1]){
                continue;
            }
            int rs=candidates[i]+sum;
            if(rs<=target){
                temp.add(candidates[i]);
                //递归从i+1开始
                backTrack(candidates,i+1,target,rs,temp);
                temp.remove(temp.size()-1);
            }
        }
    }

全排列问题

注意区分排列和组合的区别,排列区分数据的先后顺序,组合不区分顺序
全排列

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

在这里插入图片描述

   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) {
    	//因为是全排列,只有当list够全排列的长度才会保存
        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);
        }
    }

全排列ii

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列

在这里插入图片描述

    private List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        int[] flag=new int[nums.length];
        backTrack(nums,flag,new ArrayList<Integer>());
        return res;
    }
    public void backTrack(int[]nums,int[] flag,ArrayList<Integer> temp){
        if(temp.size()==nums.length){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
        	//剪枝问题,排序去重
            if(i>0&&nums[i]==nums[i-1]&&flag[i-1]==0)
               continue;
            if(flag[i]==1)
                continue;
            flag[i]=1;
            temp.add(nums[i]);
            backTrack(nums,flag,temp);
            flag[i]=0;
            temp.remove(temp.size()-1);
        }
    }

总结:

1.子集

当给定一个数组,其中元素互不相同,返回所有不重复的子集用以下方法

public void bachTrack(int[] nums,int start,ArrayList<Integer> temp,List<List<Integer>> res){
        res.add(new ArrayList<>(temp));
        for(int i=start;i<nums.length;i++){
            temp.add(nums[i]);
            bachTrack(nums,i+1,temp,res);
            temp.remove(temp.size()-1);
        }
    }

当其中元素有重复时,先将数组排序,加上剪枝的算法

public void backTrack(int[] nums,int index,List<List<Integer>> res,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]);
            backTrack(nums,i+1,res,temp);
            temp.remove(temp.size()-1);
        }
    }

2.组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。只需判定temp.size()==k即可返回

public void backTrack(int n,int k,int start,List<List<Integer>> res,List<Integer> temp){
        if(temp.size()==k){
            res.add(new ArrayList<>(temp));
            return;
        }
            
        for(int i=start;i<=n;i++){
            temp.add(i);
            backTrack(n,k,i+1,res,temp);
            temp.remove(temp.size()-1);
        }

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字只可以使用一次。依旧排序加剪枝

    public void backTrack(int[] candidates,int start,int target,int sum,ArrayList<Integer> temp){
        if(sum==target){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=start;i<candidates.length;i++){
            if(i>start&&candidates[i]==candidates[i-1])
                continueint rs=candidates[i]+sum;
            if(rs<=target){
                temp.add(candidates[i]);
                backTrack(candidates,i+1,target,rs,temp);
                temp.remove(temp.size()-1);
            }
        }
    }

当candidates 中的数字可以无限制重复被选取,此时start每次递归都需从i开始,而不是i+1

public void backTrack(int[] candidates,int target,int sum,int start,List<List<Integer>> res,ArrayList<Integer> temp){
        if(sum==target){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=start;i<candidates.length;i++){
            int rs=candidates[i]+sum;
            if(rs<=target){
                temp.add(candidates[i]);
                backTrack(candidates,target,rs,i,res,temp);
                temp.remove(temp.size()-1);
            }else
                break;
        }
    }

3.排列
排列和组合不同,元素顺序改变则变成新的排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。设定一个int[] flag来标定是否被访问,如被访问则跳过

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

当给定的数组包含重复元素时,需考虑剪枝问题

public void backTrack(int[]nums,int[] flag,ArrayList<Integer> temp){
        if(temp.size()==nums.length){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
        	//
            if(i>0&&nums[i]==nums[i-1]&&flag[i-1]==0)
               continue;
            if(flag[i]==1)
                continue;
            flag[i]=1;
            temp.add(nums[i]);
            backTrack(nums,flag,temp);
            flag[i]=0;
            temp.remove(temp.size()-1);
        }
    }

求子集问题:当元素互不相同则用最简单的回溯算法。当有相同元素,先排序,再用if(i>index&&nums[i]==nums[i-1])continue;判断剪枝

组合问题:先找到递归终止的条件,然后使用回溯算法,如果元素可以无限制重复使用,则递归从i开始,否则从i+1开始

排列问题:当元素都不重复时,先找到递归终止的条件,比如temp.sizr()==nums.length,使用int[] flag标记元素是否被使用,当回溯时,flag的状态也要重置。有重复元素时,排序加剪枝算法if(i>0&&nums[i]==nums[i-1]&&flag[i-1]==0)continue;

17. 电话号码的字母组合

题目链接
在这里插入图片描述

采用回溯法,注意map多组数据的初始化方法

public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<>();
        Map<Character, String> map = new HashMap<>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        backTrack(list, 0, new StringBuilder(), map, digits);
        return list;
    }

    public void backTrack(List<String> list, int index, StringBuilder sb, Map<Character, String> map, String digits) {
        if (sb.length() == digits.length()) {
            list.add(sb.toString());
            return;
        }
        char c = digits.charAt(index);
        String s = map.get(c);
        for (int i = 0; i < s.length(); i++) {
            sb.append(s.charAt(i));
            backTrack(list, index + 1, sb, map, digits);
            sb.deleteCharAt(sb.length() - 1);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯算法是一种经典的搜索算法,Java语言可以通过递归来实现回溯算法回溯算法通常用于求解一个问题的所有可能解或最优解。该算法通过尝试所有可能的解来解决问题,然后回溯(返回上一个状态)并尝试其他可能的解,直到找到解决方案或者所有可能的解都已经尝试过。 下面是Java实现回溯算法的一般步骤: 1. 确定问题的解空间:即确定问题的所有可能解组成的集合。 2. 确定约束条件:即问题需要满足的条件。 3. 确定搜索策略:即在问题解空间中进行搜索的方式。 4. 实现回溯算法:通过递归实现,对问题的所有可能情况进行搜索,并返回找到的解或无解。 下面是一个简单的Java代码实现回溯算法的例子,该例子用回溯算法求解数独问题: ``` public class SudokuSolver { public void solveSudoku(char[][] board) { if (board == null || board.length == 0) { return; } solve(board); } private boolean solve(char[][] board) { for (int i = 0; i < board.length; i++) { for (int j = 0; j < board.length; j++) { if (board[i][j] == '.') { for (char c = '1'; c <= '9'; c++) { if (isValid(board, i, j, c)) { board[i][j] = c; if (solve(board)) { return true; } else { board[i][j] = '.'; } } } return false; } } } return true; } private boolean isValid(char[][] board, int row, int col, char c) { for (int i = 0; i < 9; i++) { if (board[i][col] != '.' && board[i][col] == c) { return false; } if (board[row][i] != '.' && board[row][i] == c) { return false; } if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] != '.' && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c) { return false; } } return true; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值