力扣3——回溯算法

1.回溯算法的框架

1.1.解决一个回溯问题,实际上就是一个决策树的遍历过程,只需要思考3个问题

  • 1.路径:就是已经做出的选择;
  • 2.选择列表:也就是你当前可以做的选择;
  • 3.结束条件:也就是到达决策树底层,无法再做出选择的条件。

1.2.代码框架

result = []
def backtrack(路径,选择列表)if 满足结束条件“
		result.add(路径)
		returnfor 选择 in 选择列表:
		做选择;
		backtrack(路径,选择列表);
		撤销选择;

2.全排列问题(腾讯50题)

全排列问题
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
在这里插入图片描述

2.1.解题思路

  • 1.这种数组性质的全排列问题,需要考虑swap交换方法的应用,不应该考虑delete删除方法的应用。
    因为delete删除方法,还需要考虑是不是需要new的问题。
  • 2.你要理解swap方法的应用,这很重要:
    第1个:swap(数组,i,j) 做出选择
    第2个:swap(数组,i,j) 撤销选择
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        进行数组的处理,方便后期使用
        List<Integer> numsInt = new ArrayList<Integer>();
        for (int n: nums) {
            numsInt.add(n);
        }
        1.调用回溯
        backtrack(res,numsInt,0);
        return res;
    }

    public void backtrack(List<List<Integer>> res,List<Integer> numsInt,int index) {
        0.终止条件
        if (first==numsInt.size()-1){
            0.5这里需要new一个新的对象,不然即使存进了res,res内的内容海事会动态变化
            res.add(new ArrayList<>(numsInt));
            return;
        }
        
        1.选择列表
        for (int i = index; i < numsInt.size(); i++) {
            1.1.做出选择
            Collections.swap(numsInt,index,i);
            1.2.递归
            backtrack(res,numsInt,index+1);
            1.3撤销选择
            Collections.swap(numsInt,index,i);
        }
    }
}

2+.剑指 Offer 38. 字符串的排列

剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
在这里插入图片描述

1.解题思路

  • 1.使用swap交换,每次做选择就是交换一次,
    第1层递归是依次把下标0及其后的所有元素交换到下标0的位置;
    每递进1层就是在上一层的基础上把下标1及其后边的元素交换到下标1的位置;
    ……
    直到递归进入length-1层,就没有了,可以把当前序列存进resList;
  • 2.雨露均沾:每一层就是让 index及其之后的元素都到index处1次。
class Solution {
    public String[] permutation(String s) {
        char[] chars = s.toCharArray();
        Set<String> set = new HashSet<>();
        backTrack(chars,0,set);
        String[] res = set.toArray(new String[0]);
        return res;
    }

    public void backTrack(char[] chars,int index,Set<String> set){
        0.终止条件
        if (index== chars.length-1){
            2个知识点
              1.set保证排列不重复
              2.new Strng保证存入set的元素不会再变化
            set.add(new String(chars));
            return;
        }

        1.选择列表
        for (int i = index; i < chars.length; i++) {

            1.做选择,依次把index及其后的元素交换到index坐标处
            swap(chars,index,i);
            2.swap交换后的新chars,传入下一层,在新chars基础上,再把index+1,及其之后的元素交换到index+1backTrack(chars, index+1, set);
            3.撤销选择,准备进入下1for循环
            swap(chars,index,i);
        }
    }
    public void swap(char[] chars,int i,int j){
        Character temp = chars[i];
        chars[i]       = chars[j];
        chars[j]       = temp;
    }
}

3.数组的全部子集(腾讯50题)

子集

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

3.1.解题思路

  • 1.此题与全排列问题不同,用不到swap交换方法;
  • 2.每次进入backTrack()方法,先添加进上一层传进来的list(Integer),即:res.add(new ArrayList<>(list))(这里一定要new!);
    3.在选择列表内做选择,相当于把上一层传进来的单个list子集,末尾再添加选择的元素,组成新的list,并再传入下一层。
    4.然后撤销选择。
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        if (nums.length==0) return new ArrayList<>();
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        0.进行数组的处理,方便后期使用
        List<Integer> numsInt = new ArrayList<Integer>();
        for (int n: nums) {
            numsInt.add(n);
        }
        1.调用回溯
        backTrack(res,list,numsInt,0);
        return res;
    }

    public void backTrack(List<List<Integer>> res,List<Integer> list,List<Integer> numsInt,int index){
        0.每次进来就先添加上次传进来的list
        res.add(new ArrayList<>(list));

        1.循环列表
        for (int i = index; i < numsInt.size(); i++) {
            1.1.做选择(list末尾插入选择的元素)
            list.add(numsInt.get(i));
            1.2.递归
            backTrack(res,list,numsInt,i+1);  注意这里是 i+1,而非index+1
            1.3.撤销选择(list末尾删除选择的元素)
            list.remove(numsInt.get(i));

        }
    }
}

4.区别:全排列 & 子集

1.在选择列表内做完选择后,调用递归时,下标参数不同!
  • 全排列问题传入的是:backtrack(总返回集合,选择路径 ,index+1);
  • 子集问题传入的是: backTrack(总返回集合, 选择路径,源数组元素(不用管),i+1);
    主要是传入的最后1个参数,因为全排列问题有swap交换操作,所以即使是index+1也不必担心取到重复的元素,但是子集问题,必须是i+1,也就是当前for遍历元素的后1个元素开始;否则会出现重复取某些元素的问题。
2.子集问题 不需要有swap交换操作
  • 所以,子集问题在向下一层调用递归传参时,必须传入i+1,确保下一层从本层for循环元素的下一个开始。

5. 组合总和

组合总和

  • 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
  • candidates 中的数字可以无限制重复被选取。
  • 说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。
    在这里插入图片描述

1.解题思路

  • 1.此问题 数组元素可以重复取,当然,每一个=target的组合是不能重复的。
  • 2.先看backTrack(数组(固定), target(固定) ,index(重要!!), comSum(动态加和), pathList(经过路径), resList(总返回));
  • 3.backTrack传入参数中只有index需要关注,它是下一层for循环时,在数组中的开始下标
  • 4.而且传入下层的index,其实是当前层for循环的 i,为什么?
  • 5.因为下一层再从数组中取出元素时,不能取当前层数组下标i之前的元素,为什么?
  • 6.因为如果把 i 去掉,直接每层的for循环都从0开始,那就会出现重复的组合,如下图。
    在这里插入图片描述
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        List<List<Integer>> resList = new ArrayList<>();
        List<Integer> pathList = new ArrayList<>();
        
        backTrack(candidates,target,0,0,pathList,resList);
        
        return resList;

    }

    public void backTrack(int[] candi,int target,int index,int comSum,List<Integer> pathList,List<List<Integer>> resList){
        0.终止条件 当满足总和 conSum==target时
        if (target==comSum){
            resList.add(new ArrayList<Integer>(pathList));
            System.out.println("+1");
            return;
        }
        0.终止条件:总和 > target 时
        if (target<comSum)
            return;

        1.循环列表
        for (int i = index; i < candi.length; i++) {

            1.1.做出选择
            comSum += candi[i];
            pathList.add(candi[i]);
            
            1.2.调用递归
            下一层的for循环从本层的下标i开始
            backTrack(candi, target,i, comSum, pathList, resList);
            1.3.撤销选择
            pathList.remove((Integer)candi[i]);
            comSum -= candi[i];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
力扣是一个在线编程平台,提供了大量的算法题目,可以帮助程序员提高算法能力。回溯算法是一种搜索算法,它通过不断地尝试所有可能的解来求解问题。在回溯算法中,我们首先定义一个解空间,然后从解空间中搜索所有可能的解,直到找到符合要求的解为止。回溯算法通常用于求解组合问题、排列问题、子集问题等。 在 Java 中实现回溯算法,通常需要定义一个递归函数来搜索解空间。在递归函数中,我们首先判断当前状态是否符合要求,如果符合要求,则将当前状态加入到解集中;否则,我们继续搜索下一个状态。在搜索下一个状态时,我们需要对当前状态进行一些修改,然后递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。 以下是回溯算法的一般步骤: 1. 定义解空间:确定问题的解空间,并定义一个数据结构来表示解空间中的每个状态。 2. 确定约束条件:确定哪些状态是合法的,并定义一个函数来判断当前状态是否符合要求。 3. 确定搜索策略:确定搜索解空间的顺序,并定义一个函数来生成下一个状态。 4. 搜索解空间:使用递归函数搜索解空间,如果当前状态符合要求,则将其加入到解集中;否则,继续搜索下一个状态。 5. 回溯:当搜索完所有可能的状态后,回溯到上一个状态,继续搜索其他可能的状态。 以下是一个力扣题目的回溯算法 Java 实现示例: ``` class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums, 0); return res; } private void dfs(int[] nums, int start) { res.add(new ArrayList<>(path)); for (int i = start; i < nums.length; i++) { path.add(nums[i]); dfs(nums, i + 1); path.remove(path.size() - 1); } } } ``` 该算法用于求解给定数组的所有子集。在递归函数中,我们首先将当前状态加入到解集中,然后从当前位置开始搜索下一个状态。在搜索下一个状态时,我们将当前元素加入到路径中,并递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值