力扣算法-组合问题-经典回溯

题目描述

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

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

输入:n = 1, k = 1
输出:[[1]]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

真的就,忘了!
上次做,明明总结了很久,可惜这次做又忘了。

其实总体思路是有的,就是递归内,加多一个for循环,但是卡在哪呢?

  1. 卡在两个list如何添加的问题
  2. 卡在回溯回溯,回溯与递归最大的不同就是需要回溯。
    就是有一步是返回的操作。

组合问题确实有点难想,就先记住组合最基本的问题,先记住这个,再去想变型吧。

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        if (path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        //for循环,这是组合问题的精髓, 就是for循环内有个 递归,然后for循环的条件就是起始坐标会变
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
        //这个剪枝其实可以理解,但是还是背起来吧。每次都去推也挺麻烦的
        //本来是n,现在是n减掉k——size再加1
        
            path.add(i);属于是,要把i加入
            combineHelper(n, k, i + 1);
            path.removeLast();//而这一步就是整个回溯的重点吧,就是要返回!!!整个组合问题的精髓!!!
        }
    }
}

作者:carlsun-2
链接:https://leetcode-cn.com/problems/combinations/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-0uql/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你懂吧,复制粘贴,,四舍五入等于我学会了。

刚刷了组合一,下不了手阿,没有思路,思考了很久。

遇到组合问题,第一步还是先回到最基础的组合模板来吧,就是上面的这道题。

辅助记忆

  • 两个全局变量的list,在递归的时候加入
  • 递归内
  • 1.结束条件,就是list满了
  • 2.for循环,起始下标要变。
  • 3.其实整个函数的关键就只在于递归函数,只需要考虑递归函数就行了
  • 整个递归函数的关键又在于for循环内,你可以把第一次就按照这个模板套
  • 首先,第一个数得加进去吧。
  • list.add
  • 递归后,从1去找,找2,满了之后就把2弹出
  • 继续找3,4,5

记住这个模板吧。不只是for循环和递归,还有list的增加删除,这才是回溯!!!

记得递归函数有个startindex,每一次for循环从startindex开始,for循环内的startindex要加1.

我是fw,只能记住模板了!!!

  1. 两个list
  2. 递归 startindex
  3. for循环 startindex开始
  4. 循环内 递归 startindex要加1

组合三:

题目描述

找出所有相加之和为 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]]

题解:

昨天下午看了一个多小时组合三想不出来,正常。因为怎么说,组合三还是构建在组合一的基础上,你组合1都忘了,还搞什么组合3嘛。

昨天把组合一过了一遍,今天的组合三简直醍醐灌顶。
确实,就照着模板来就好了。不过还是在一些小细节上出现了些许问题。

组合三的思路其实就是 建立在组合一上,每次判断一下组合内数的总和是不是等于k,是的话就输出。

而判断这里就是我出错的地方。原以为要传什么num进去,却一直解不出来,后来才发现确实不是一个好方法。那就按照笨方法来做阿。

就直接 对list内的元素for循环一遍加起来 看是否等于k喽。也不会很难滴。

public class demo15 {
    List<List<Integer>>list1=new ArrayList<>();
   LinkedList<Integer>list2= new LinkedList<>();//注意这里是Linklist,前后都得是Linklist
    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(k,n,1);
        return list1;
    }

    void dfs(int k,int n,int startindex)
    {
        //结束条件
            if (list2.size()==n) {
                int tempSum = 0;
                //本方法啦,就直接加强for循环加起来总和就好了
                for (int num : list2) {
                    tempSum += num;
                }
                if (tempSum == k) {
                    list1.add(new ArrayList<Integer>(list2));
                }

                return;
        }
            
        //for循环嵌套
        for(int i=startindex;i<=9;i++)
        {
            list2.add(i);
            dfs(k,n,i+1);
            list2.removeLast();
        }
    }


手动分割线,好激动,刚去力扣上看了题解,有大佬的思路和我一样,而我也知道自己错在哪了!!!

class Solution {

    List<List<Integer>> res = new ArrayList<>();        // 用于存储全部结果
    LinkedList<Integer> track = new LinkedList<>();     // 用于存储单次的返回结果

    public List<List<Integer>> combinationSum3(int k, int n) {
        backtrack(0, n, k, 1);
        return res;
    }

    // 递归函数
    public void backtrack(int curSum, int targetSum, int k, int start) {
        // base case 
        if (track.size() == k) {
            if (curSum == targetSum) {
                res.add(new ArrayList<>(track));
            }
            return;
        }

        for (int i = start; i <= 9; i++) {
        //减枝版本同样可以写
        i<=9-(k-size)+1
            // 做选择
            track.add(i);
            curSum += i;
            // 递归遍历
            backtrack(curSum, targetSum, k, i + 1);
            // 回溯,撤销选择
//!!!我就是因为少了这句!!!难怪一直搞不出来!、
//确实阿!!!回溯回溯,这才是回溯的本质阿,前面加了,递归回溯之后
//也得减回来!!!
//只有减掉,才不对for循环中加入下一个i造成影响!!! 回溯回溯回溯的本质
//有进有出才是回溯
            curSum -= i;
            track.removeLast();
        }
    }
}

其实这道题还有另外一个做法,也挺好的。就是很多题目都会应用到的二进制解法。二进制解法,用01可以来表示很多事。

比如说这道题

用一个逆向思维,组合中含有1-9个数,
然后只有k的数的集合,也就是说1到9之间只能选K个
我们假设1到9个比特位,每一个比特位为1就代表那个位有取到,比如第1位,第2位,第4位为1,就代表 1 2 4 为

所以要做一个从0到2的9次方的for循环,就代表了9个位置,每个位置有没有选的情况。

然后最主要地就是要判断一个数它的二进制形式,哪几个位为1,哪几个位为0.
用&运算!!!&预算,都为1,为1.否则全为0.

所以用for循环,i从0到9,1<<i&mask那个原本的数。

为什么能实现呢?比如1<<1会等于 00000010 ,此时跟一个多位二进制的数相&,只有该位,也就是第二位,也就是第i+1位为1时,整个结果才为1,否则为0.所以让其相&,如果结果为0,就说明i+1为1.

是吧,学到了吧。让一个多位二进制数与1<<i 相&,就能得到第i+1位是否为1.

代码:

class Solution {
    List<Integer> temp = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(1, 9, k, n);
        return ans;
    }

    public void dfs(int cur, int n, int k, int sum) {
        if (temp.size() + (n - cur + 1) < k || temp.size() > k) {
            return;
        }
        if (temp.size() == k) {
            int tempSum = 0;
            for (int num : temp) {
                tempSum += num;
            }
            if (tempSum == sum) {
                ans.add(new ArrayList<Integer>(temp));
                return;
            }
        }
        temp.add(cur);
        dfs(cur + 1, n, k, sum);
        temp.remove(temp.size() - 1);
        dfs(cur + 1, n, k, sum);
    }
}


  • 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、付费专栏及课程。

余额充值