【回溯Ⅱ】组合问题

news2024/11/14 5:23:14

用回溯(递归)解决组合问题

  • 第一类组合问题
    • 77.组合
    • 216.组合问题Ⅲ
  • 第二类组合问题
    • 39. 组合总和
      • 递归法一:组合位置填空
      • 递归法二:遍历数组
    • 40. 组合总和 II
      • 递归法一:组合位置填空
      • 递归法二:遍历数组
        • ❌ 常规思路:有重复
        • ❗hash表解决冲突
    • 377. 组合总和 Ⅳ 【动态规划】

第一类组合问题

77.组合

77.组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数组合
你可以按 任何顺序 返回答案。
在这里插入图片描述

这个题目之前的博客已经解答过了,里面还有字典序/二进制序列法求解代码。这里再说一下回溯法。遍历[1~n]里面的每个数,每次都有两种两种情况:选or不选;递归结束的条件是已经有k个数字了or数组遍历完了,其实还有一个结束条件:当前遍历的数字中选择的有m个,还剩下p个,然而m+p < k了,继续遍历下去就算全部选择也不可能满足“k个数”这个条件。Java代码如下:

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

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }
    public void dfs(int cur, int n , int k){
        if(temp.size() + (n - cur + 1) < k)
            return ;
        if(temp.size() == k){
            ans.add(new ArrayList<Integer>(temp));
            return ;
        }

        temp.add(cur);
        dfs(cur + 1, n, k);
        temp.remove(temp.size() - 1);
        dfs(cur + 1, n, k);
    }
}

216.组合问题Ⅲ

216. 组合问题Ⅲ

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
在这里插入图片描述

这和77的区别是,这个题目不仅要找k个数,还要求和为n。因此在上一个题的基础上,加上一个判断过程【和是否为n】即可:

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, k, n);
        return ans;
    }

    public void dfs(int cur, int k, int sum) {
        if (temp.size() + (9 - cur + 1) < 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, k, sum);
        temp.remove(temp.size() - 1);
        dfs(cur + 1, k, sum);
    }
}

第二类组合问题

下面的几个题,可以认为是一个系列的~

39. 组合总和

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个数字可以无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
在这里插入图片描述

递归法一:组合位置填空

这个递归思路,是每次递归都要给这个位置选择一个数,这个数是candidates数组中的——即每次递归,都遍历取依次candidates中的数。不过取的数字需要满足一些条件。

  • 我们肯定想先从小的数字开始选,毕竟题目给的答案也是从小的数字开始的。所以先对candidates排序;
  • 每次递归都找一个数,但是这个数num需要小于等于target;【num都比target大了还怎么满足加上去和为target是吧】
  • 根据这个的思路,第一个例子candidates =[2,3,6,7],第一次递归选2,第二次递归如果选3,第三次递归可以选2;那么和第一次递归选2,第二次递归选2,第三次递归选3就重复了;这里需要添加一个条件以去除重复:⭐我们的答案组合中的数字是从小到大的,因此我们每次递归选的数组 num 必须大于等于前一轮选的数字 ,假设为min,即num≥min;因为candidate是已经排序的,那么每一轮开始寻找的初始索引,应该是从上一轮选择的数的索引开始的;
  • 递归结束条件就是传入的target为0;以及遍历完数组,待选择的数为空;
    完整Java代码如下:【两种都可以,一个是直接传min;一个是传idx】
class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        dfs(candidates, target,0);
        return ans;
    }

    public void dfs(int[] candidates, int target,int idx){
        if(target == 0 ){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        //candidates数组中已经没有数字可选以满足条件
        if(idx == candidates.length)
            return;

        for(int i = idx ; i < candidates.length && candidates[i] <= target; i++){
            temp.add(candidates[i]);
            dfs(candidates, target - candidates[i], i);
            temp.remove(temp.size() - 1);
        }         
    }
}
class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates); //先排序,升序
        dfs(candidates, target,candidates[0]); //初始传入的min即为排序后candidates中的第一个元素
        return ans;
    }

    public void dfs(int[] candidates, int target,int min){
        if(target == 0 ){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        //candidates数组中已经没有数字可选以满足条件
        if(target < min)
            return;

        for(int num : candidates){ //遍历candidate
            if(num >= min && num <= target){ //选择满足条件的数字
                temp.add(num);
                dfs(candidates,target - num, num); // 更新target为target-num
                temp.remove(temp.size()-1);
            }
        }             
    }
}

递归法二:遍历数组

第二个思路是去遍历数组,那么每次针对数组中的数字有两种情况:选or不选。
每一轮递归的目标是选择一个数num,使得这个数num与当前还差的target相等。递归结束条件①target=0,即选了这些数以后,与目标值还差0;即选的这些数和已经为target了!②遍历完了这个数组,因此需要一个参数记录遍历到了数组的位置/下标,idx。这个情况不需要先对candidates排序。
完整Java代码如下:


class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(candidates, target,0); //初始idx为0
        return ans;
    }

    public void dfs(int[] candidates, int target,int idx){
        if(target == 0 ){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        //已经遍历完数组
        if(idx == candidates.length)
            return;
        dfs(candidates,target,idx + 1); // 不选择这个数,跳过当前下标idx,下次从idx+1开始选
        //如果当前num比target小,选择
        if(target - candidates[idx] >=0){
            temp.add(candidates[idx]);
             //注:因为一个数可以选择多次,因此选择这个数后,idx不变化
            dfs(candidates, target - candidates[idx], idx);
            temp.remove(temp.size()-1);
        }
    }
}

40. 组合总和 II

40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target组合
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
在这里插入图片描述

递归法一:组合位置填空

这个题目和上面的区别在两点:

  • 上一题candidates中的数字没有重复;这个题目candidates是有重复的;
  • 上一题candidates中的数字可以无限次使用;这个题目candidates的数量是有限的;

组合位置填空法的遍历思路,还是每次遍历都要找一个数【在candidates中找一个】,但是这里需要增加一点以去除重复组合:比如candidates = [2,5,2,1,2]这个例子,先对candidates排序→candidates = [1,2,2,2,5];第一轮选择了1,第二轮选择[2,2,2,5]中的任意一个,那么第二个位置选2的就有3种情况【因为有3个2】,这就引起重复了。 去除重复:candidates已经排序,相等元素是相邻的,如果candidates[i] == candidates[i-1],说明这个candidates[i]不能再选了,但是i+1的情况知道,因此continue,后面的数字继续。
完整Java代码如下:

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        dfs(candidates,target,0);
        return ans;
    }

    public void dfs(int[] candidates, int target, int idx){
        if(target == 0){ //找到满足要求的组合,递归结束
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        if(idx == candidates.length) //遍历完数组,递归结束
            return; 
        // 遍历idx~ candidates.length-1的数,没有重复的就加入
        for(int i = idx; i < candidates.length && target >= candidates[i]; i++){
            if( i > idx && candidates[i] == candidates[i-1]) //重复了,跳过该数
                continue;
            temp.add(candidates[i]);
            dfs(candidates, target - candidates[i], i+1);
            temp.remove(temp.size()-1);
        }    
    }
}

递归法二:遍历数组

上面一题,在遍历数组的递归过程中,选择一个数字后,由于这个数字可以重复使用,因此idx不变化。 那么是否这个题目,针对一个数有选or不选的情况;不管选or不选,idx+1,选下一个,是否正确?
问题就出在这个题目的candidates是重复的,还是candidates = [1,2,2,2,5],target = 5这个例子,三个2,不选第一个2+选第二个2,选第一个2+不选第二个2,实际是一样的。

❌ 常规思路:有重复

用上面的常规思路,写的代码:

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        dfs(candidates,target,0);
        return ans;
    }
  public void dfs(int[] candidates, int target, int idx){
        if(target == 0){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        if(idx == candidates.length)
            return;
        if(target >= candidates[idx]){
            temp.add(candidates[idx]);
            dfs(candidates, target - candidates[idx],idx+1);
            temp.remove(temp.size()-1);
        } 
        dfs(candidates,target,idx+1);
    }
}

结果如下,有重复:

在这里插入图片描述
这个test case的结果中出现了两次[1,2,5]这是因为candidates中有两个1,组合第一位就有两种选择;同样[1,7]也重复。 但是[1,1,6]不重复,是两个1都用上了!
在这里插入图片描述
这个test case的结果中出现了三次[1,2,2]是因为candidates中有3个2,结果中的两个2,可以是(第一个2,第二个2)、(第一个2,第三个2)、(第二个2,第三个2)。

❗hash表解决冲突

这个解决冲突的思路,就是先把candidates去重。统计candidates中的数及出现的次数,存在freq中。那么freq中的每个数有选or不选,两种情况;针对选的情况,又有选几次的问题:min(target/num, count),即现在的target最多能选几个num、这个num在candidates中有一个,二者的最小值。 完整java代码如下:

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    List<int[]> freq = new ArrayList<int[]>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        // 先统计candidates中的数及出现的频率
        for(int num : candidates){
            int size = freq.size();
            if(freq.isEmpty() || num != freq.get(size-1)[0])
                freq.add(new int[]{num,1});
            else
                freq.get(size-1)[1]++;
         }        
        dfs(target,0);
        return ans;
    }
  public void dfs(int target, int idx){
        if(target == 0){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        // 已经遍历完freq中的数 或者 当前freq中的数已经大于targetl直接剪枝
        if(idx == freq.size() || target < freq.get(idx)[0])
            return;
    
        // 依次选择1~ target/num个num这个数
        int num = freq.get(idx)[0];
        int most = Math.min(target/num, freq.get(idx)[1]);
        for(int i = 1; i <= most; i++){
            temp.add(num);
            dfs(target - i*num, idx + 1);
        } 
        // 选择后要移除掉
        for(int i = 1; i <= most; i++)
            temp.remove(temp.size()-1);
         
        // 这个数字一个都不选!
        dfs(target, idx + 1);
    }
}

377. 组合总和 Ⅳ 【动态规划】

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数
题目数据保证答案符合 32 位整数范围。
在这里插入图片描述

这个题目虽然是一系列的变化题目…但是需要用动态规划求解。对于数字k,遍历nums数组中比k小的其他元素j,dp[k] += dp[k-j]。 其中dp[0] = 1, 表示k == num的情况,只有这一种组合。那么完整版Java代码如下:

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int n = nums.length;
        int[] count = new int[target+1];
        count[0] = 1;
        Arrays.sort(nums);
        for(int i = 1; i <= target; i++){ //从小到大依次计算i的组合情况
            for(int j = 0; j < n && nums[j] <= i; j++) // 遍历nums数组中的元素,加入组合
                count[i] += count[i - nums[j]];
        }
        return count[target];
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2071086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot集成kafka-监听器手动确认接收消息(主要为了保证业务完成后再确认接收)

SpringBoot集成kafka-监听器手动确认接收消息 1、说明2、示例2.1、application.yml2.2、消费者2.3、生产者2.4、测试类2.5、测试 1、说明 kafak中默认情况下是自动确认消息接收的&#xff0c;也就是说先启动消费者监听程序&#xff0c;再启动生产者发送消息&#xff0c;此时消…

【动态规划】第 N 个泰波那契数

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 题目讲解算法原理代码实现 题目 题目如下&#xff1a; 讲解算法原理 我们先说一下动态规划题目的整体做题思路&#xff1a; 第一步&#xff1a; 状态表示 什么是状态表示? 做动态规划类题目一般…

跟李沐学AI:样式迁移

样式迁移需要两张输入图像&#xff1a;一张是内容图像&#xff0c;另一张是样式图像。 我们将使用神经网络修改内容图像&#xff0c;使其在样式上接近样式图像&#xff0c;得到合成图片。类似手机相册中的滤镜效果。 奠基性工作&#xff1a;基于CNN的样式迁移 任务&#xff1…

vue3+vite+axios+mock从接口获取模拟数据实战

文章目录 一、安装相关组件二、在vite.config.js中配置vite-plugin-mock插件三、实现mock服务四、调用api接口请求mock数据方法一、直接使用axios 请求mock 数据方法二、对axios进行封装统一请求mock数据 五、实际运行效果 在用Vue.js开发前端应用时通常要与后端服务进行交互&a…

WPF 选择对应控件技巧

当界面控件过多&#xff0c;选择对应的控件是比较困难的。

白酒与青年文化:潮流与传统的碰撞

在时代的洪流中&#xff0c;青年文化如同一股涌动的潮流&#xff0c;不断冲击着传统的边界。而白酒&#xff0c;作为中国传统文化的瑰宝&#xff0c;也在这一潮流中找到了新的表达方式。今天&#xff0c;我们就来探讨一下白酒与青年文化之间的碰撞与整合&#xff0c;以及豪迈白…

项目问题 | vscode连接远程Linux服务器报错: “> Host key verification failed. > 过程试图写入的管道不存在”

远程连接服务器时报错&#xff1a; Please contact your system administrator. Add correct host key in C:\Users\LiHon/.ssh/known_hosts to get rid of this message. Offending ECDSA key in C:\Users\LiHon/.ssh/known_hosts:9 Host key for 124.71.71.215 has changed a…

七种有效将msvcp140.dll丢失的解决方法,快速修复msvcp140.dll错误

在使用Windows操作系统的计算机上安装或运行软件时&#xff0c;用户可能遭遇“msvcp140.dll丢失”这一常见错误。这个问题通常发生在尝试启动某些程序时&#xff0c;系统会弹出一个警告窗口&#xff0c;提示“无法继续执行代码&#xff0c;因为系统未找到msvcp140.dll”。这样的…

【学习笔记】AD实现原理图的元器件自动标号

【学习笔记】AD24实现原理图的元器件自动标号 在原理图绘制过程中&#xff0c;载入的元器件封装并不会默认标号&#xff0c;而是“&#xff1f;”的形式显示&#xff0c;为避免手动标号所带来的大量繁琐工作&#xff0c;自动标号会是一个很好的选择。 在 Altium Designer&…

【网络】传输层协议——TCP协议(初阶)

目录 1.TCP协议 1.1.什么是TCP协议 1.2.为什么TCP叫传输控制协议 1.2.TCP是面向字节流的 2.TCP协议段格式 2.1.流量控制——窗口大小&#xff08;16位&#xff09; 2.2.确认应答机制 2.2.1.什么是确认应答机制 5.2.2.推导确认应答机制 5.3.2.确认号和序列号 2.3.六位…

日志审计-graylog ssh登录超过6次告警

Apt 设备通过UDP收集日志&#xff0c;在gray创建接收端口192.168.0.187:1514 1、ssh登录失败次数大于5次 ssh日志级别默认为INFO级别&#xff0c;通过系统rsyslog模块处理&#xff0c;日志默认存储在/var/log/auth.log。 将日志转发到graylog vim /etc/rsyslog.conf 文件末…

四、前后端分离通用权限系统(4)

&#x1f33b;&#x1f33b; 目录 一、前端开发和前端开发工具1.1、前端开发介绍1.2、下载和安装 VS Code1.2.1、下载地址1.2.2、插件安装1.2.3、创建项目1.2.4、保存工作区1.2.5、新建文件夹和网页1.2.6、预览网页1.2.7、设置字体大小 二、Node.js2.1、Node.js 简介2.1.1、什么…

汇编知识MOV,MRS,MSR,PUSH和POP指令

处理器做得最多的事情就是在处理器内部来回的进行数据传递 1) 将数据从一个寄存器传递到另一个寄存器中 2) 将数据从一个寄存器传递到特殊寄存器&#xff0c;例如CPSR,SPSR寄存器 3) 将立即数传递到寄存器。 数据传输常用的三个指令&#xff1a;MOV,MRS,MSR指令 常用的…

微信小程序模板与配置(三)app.json对小程序进行全局性配置

全局配置文件及常用的配置项 小程序根目录下的app.json文件是小程序的全局配置文件。常用的配置项如下&#xff1a; pages 记录当前小程序所有页面的存放路径window 全局设置小程序窗口的外观tabBar 设置小程序底部的tabBar效果style 是否启用新版的组件样式 一、全局配置-w…

Python测试框架Pytest的使用

pytest基础功能 pytset功能及使用示例1.assert断言2.参数化3.运行参数4.生成测试报告5.获取帮助6.控制用例的执行7.多进程运行用例8.通过标记表达式执行用例9.重新运行失败的用例10.setup和teardown函数 pytset功能及使用示例 1.assert断言 借助python的运算符号和关键字实现不…

解决 VMware 中 Ubuntu文件系统磁盘空间不足

目录 问题引入 解决方案 第一步、在VMware中扩展容量&#xff1a; 第二步、查看磁盘空间使用情况&#xff1a; 第三步、安装分区工具&#xff1a; 第四步、启动该分区工具&#xff1a; 第五步、操作分区&#xff1a; 第六步、修改挂载文件夹的读写权限&#xff1a; 第七…

全网最全的yolo系列转换工具,从txt转xml,再从xml转txt,亲自测试好用

在训练yolo的过程中&#xff0c;难免涉及标注的数据格式转化&#xff0c;经过了几次修改和迭代&#xff0c;最终把转化代码跟大家一起分享。 先把xml转txt部分的代码分享一下&#xff0c;py_convert_xml2txt.py&#xff1a; # -*- coding:utf-8 -*-import os import shutil im…

GRAPHCARE:双向图神经网络 + 个性化知识图谱 + 大模型,打开医疗保健预测领域之门

GRAPHCARE&#xff1a;双向图神经网络 个性化知识图谱 大模型&#xff0c;医疗保健预测领域 关系图双向图神经网络个性化知识图谱GRAPHCARE框架创意视角 如果取消双向图神经网络&#xff0c;直接用医学大模型分析&#xff0c;还能做医疗保健预测领域吗&#xff1f;使用双向图…

防患未然:构建AIGC时代下开发团队应对突发技术故障与危机的全面策略

文章目录 一、快速响应与精准问题定位1. 实时监控与预警系统2. 高效的日志管理和分析3. 分布式追踪与调用链分析4. 紧急响应机制 二、建立健全的应急预案与备份机制1. 制定详尽的应急预案2. 定期应急演练3. 数据备份与快速恢复4. 冗余部署与负载均衡 三、事后总结与持续改进1. …

MATLAB 低版本Matlab-读取LAS格式点云文件并可视化(78)

las格式的文件属于标准的激光点云文件,也是最常见的点云文件,下面是读取并可视化方法 MATLAB 低版本Matlab-读取LAS格式点云文件并可视化(78) 一、LAS文件简介二、算法实现1.代码2.下载地址总结之前介绍过MATLAB自带的Las文件读取函数:(稳定,推荐使用该方法) MATLAB 读取…