力扣日记1.25-【回溯算法篇】39. 组合总和

news2024/9/28 19:24:16

力扣日记:【回溯算法篇】39. 组合总和

日期:2023.1.25
参考:代码随想录、力扣

39. 组合总和

题目描述

难度:中等

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

题解

class Solution {
public:
#define SOLUTION 2
#if SOLUTION == 1
    vector<int> path;
    vector<vector<int>> results;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return results;    
    }
    // sum 记录当前组合总和,startindex记录当前正在从哪一个元素开始遍历
    void backtracking(vector<int>& candidates, int target, int sum, int startindex) {
        if (sum > target) return;
        if (sum == target) {
            results.push_back(path);
            return;
        }
        for (int i = startindex; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i);   // starindex = i, 而不是 i + 1, 则可重复取自身,但不能取前面的值
            path.pop_back();
        }
    }
#elif SOLUTION == 2 // 剪枝优化
    // 实际上本题的输入不一定是有序的
    // 假设输入都是有序的(先对输入进行排序),则在for循环遍历时,可以通过以下方法进行剪枝:
    // 如果sum + 下一个要加的值(即sum + candidates[i])已经比target大了,则不需要再继续for循环遍历
    // 即条件还需要有 sum + candidates[i] <= target
    // (如果没有这个剪枝,即使sum已经大于target也会继续遍历递归并在递归中return)
    vector<int> path;
    vector<vector<int>> results;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates, target, 0, 0);
        return results;    
    }
    // sum 记录当前组合总和,startindex记录当前正在从哪一个元素开始遍历
    void backtracking(vector<int>& candidates, int target, int sum, int startindex) {
        // if (sum > target) return; // 剪枝后这个不需要了(因为<=target才会进入递归)
        if (sum == target) {
            results.push_back(path);
            return;
        }
        for (int i = startindex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i);   // starindex = i, 而不是 i + 1, 则可重复取自身,但不能取前面的值
            path.pop_back();
        }
    }
#endif
};

复杂度

时间复杂度:O(n * 2^n)
空间复杂度:O(target)

TODO:怎么算?每个元素都要与集合中所有元素判断(弹入和弹出即为2),n * 2*n ?

思路总结

  • 思路:

    • 最开始的时候,由于看到题目同一个数字可以无限制重复被选取,就直接取消了 216. 组合总和 III 问题中 使用的startindex,这样每次for循环都从头遍历一遍集合,肯定可以重复取,但运行后看到:
      在这里插入图片描述 在这里插入图片描述
      发觉这样存在一个问题,就是for循环从头遍历会取到当前元素之前的元素,导致结果集存在重复的组合(如[3,2,3]与[2,3,3]重复,就是因为把3放入path后,下一层递归还从2开始取值),因此要避免选到之前的元素,但又要能重复选取到自身,所以在递归时设置startindex = i,表示从当前遍历到的元素重新开始遍历,即可解决问题。
    • 对于终止条件,则是通过 sum 进行判断
    • 除此之外,其他思路与 216. 组合总和 III 问题类似,套用模板即可。
    • 对比两道题目,都是从同一个元素取值,因此都需要startindex,但是 第216题 中不能重复取自身,所以每次for循环在递归的时候 startindex = i + 1;但本题可以重复取自身,所以startindex = i。且前者一定是k个数的组合,所以终止条件可以通过path.size() == k来判断;但本题不然,只能通过sum > targetsum == target 判断
    • 树状结构示意图:
      在这里插入图片描述
  • 剪枝优化:

    • 实际上本题的输入不一定是有序的,假设输入都是有序的(先对输入进行排序),则在for循环遍历时,可以通过以下方法进行剪枝:
    • 如果sum + 下一个要加的值(即sum + candidates[i])已经比target大了,则不需要再继续for循环遍历
    • 即条件还需要有 sum + candidates[i] <= target
    • (如果没有这个剪枝,即使sum已经大于target也会继续遍历后面的值进行递归并在递归中return)
    • 示意图:在这里插入图片描述
  • 套路总结(by 代码随想录):

    • 对于组合问题,什么时候需要startIndex呢:
      • 如果是一个集合来求组合的话,就需要startIndex,例如:77.组合,216.组合总和III。
      • 如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合
    • 在求和问题中,排序之后加剪枝是常见的套路!

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

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

相关文章

完成接口配置实验

步骤一&#xff1a;IP地址规划 步骤二&#xff1a;配置二层设备 生产区内配置设备 办公区内配置设备 配置 DMZ区地址 valn配置 [Huawei]vlan batch 2 3 [Huawei]int g 0/0/2 [Huawei-GigabitEthernet0/0/2]port link-type access [Huawei-GigabitEthernet0/0/2]port…

【数据结构】链表的分类和双向链表

本篇是基于上篇单链表所作&#xff0c;推荐与上篇配合阅读&#xff0c;效果更加 http://t.csdnimg.cn/UhXEj 1.链表的分类 链表的结构非常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 我们一般叫这个头为哨兵位 我们上回…

LeetCode做题总结 226. 翻转二叉树

226. 翻转二叉树 代码1 报错代码2 报错代码3 正确。 代码1 报错 class Solution {public TreeNode invertTree(TreeNode root) {// TreeNode _root root; // 这是在保证// _root preOrderTree(root);// return root;root preOrderTree(root);return root;}public TreeNode …

MATLAB中uibutton函数用法

目录 语法 说明 示例 创建普通按钮 创建状态按钮 设置和访问按钮属性值 编写响应以下操作的代码&#xff1a;点击按钮 uibutton函数的功能是创建普通按钮或状态按钮组件。 语法 btn uibutton btn uibutton(parent) btn uibutton(style) btn uibutton(parent,style…

【方法论】费曼学习方法

费曼学习方法是由诺贝尔物理学奖得主理查德费曼提出的一种学习方法。这种方法强调通过将所学的知识以自己的方式解释给别人来提高学习效果。 费曼学习方法的步骤如下&#xff1a; 选择一个概念&#xff1a;选择一个要学习的概念或主题。 理解和学习&#xff1a;用自己的方式学…

全栈工程师

很多人说&#xff0c;全栈工程师是程序猿的终极目标。过去&#xff0c;笔者也一直往这个方面在努力&#xff0c;前后端代码都能写&#xff0c;测试也做一做&#xff0c;原型也画一画。但随着深耕it行业越来越久&#xff0c;笔者觉得条条大路通罗马&#xff0c;作为骡马的我们&a…

HCIA-HarmonyOS设备开发认证-1.HarmonyOS简介

目录 前言目标一、HarmonyOS简介1.1、初识HarmonyOS1.2、HarmonyOS典型应用场景 二、HarmonyOS架构与安全2.1、HarmonyOS架构2.1.1 内核层2.1.2 系统服务层2.1.3 框架层2.1.4 应用层 2.2、HarmonyOS安全 3、HarmonyOS关键特性4、HarmonyOS生态5、思考题坚持就有收获 前言 本章…

web安全学习笔记【08】——算法1

思维导图在最后 #知识点&#xff1a; 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OSS&反向&负载均衡等 ----------------------------------- 1、APP架构-封装&原生态&…

大语言模型推理提速:TensorRT-LLM 高性能推理实践

作者&#xff1a;顾静 TensorRT-LLM 如何提升 LLM 模型推理效率 大型语言模型&#xff08;Large language models,LLM&#xff09;是基于大量数据进行预训练的超大型深度学习模型。底层转换器是一组神经网络&#xff0c;这些神经网络由具有 self-attention 的编码器和解码器组…

Docker安装Clickhouse详细教程

简介 ClickHouse是一种列式数据库管理系统&#xff0c;专门用于高性能数据分析和数据仓库应用。它是一个开源的数据库系统&#xff0c;最初由俄罗斯搜索引擎公司Yandex开发&#xff0c;用于满足大规模数据分析和报告的需求。 特点 开源的列式存储数据库管理系统&#xff0c;…

好书推荐丨豆瓣评出9.2高分!Python编程入门就看蟒蛇书

目录 写在前面 内容简介 业内专家推荐 编辑推荐 资源丰富 作者介绍 Q&A 粉丝福利 写在后面 写在前面 在这日新月异的科技新时代&#xff0c;编程如同一把万能钥匙&#xff0c;为无数人打开了通向无限可能的大门。而在众多编程语言中&#xff0c;Python无疑是最耀…

工业阀门3D数字化展厅降低投入成本

大型机械设备的销售面临诸多挑战&#xff0c;其中最突出的问题之一是展示区域的限制。与家用小汽车不同&#xff0c;大型机械设备的展示需要大面积的场地&#xff0c;同时还需要展示各种型号和功能&#xff0c;这使得在一个城市内实现网格布点覆盖整座城市的难度加大。 为了解决…

DataStream API(转换算子)

目录 源算子 转换算子 1&#xff0c;基本转换算子 1.1映射&#xff08;map&#xff09; 1.2过滤&#xff08;filter&#xff09; 1.3扁平映射&#xff08;flatMap&#xff09; 2&#xff0c;聚合算子 2.1按键分区&#xff08;keyBy&#xff09; 2.2简单聚合 3&#x…

医学图像跨模态转换:配准方法生成对图 + 扩散模型 + 成对配对方法

医学图像跨模态转换&#xff1a;配准方法生成对图 扩散模型 成对配对方法 MRI 到 CT 的高精度转换X 光、核磁共振 MRI、CT 区别最关键的配准方法读后启发&#xff1a;Fundus 转 OCT &#xff08;只是猜想&#xff0c;不一定&#xff09; 数据图像预处理5 个图像转换算法评估图…

数据建模分析与数仓维度建模规范

前言&#xff1a;数据建模是将定义现实世界的数据抽象成模型的过程&#xff0c;以便更好的分析&#xff0c;管理和操作数据实现需求。数据建模在数仓管理、数据库设计、算法模型训练等领域都有着重要的作用。在不同的领域&#xff0c;数据建模的关注点不同&#xff1a;在数据库…

K8S搭建(centos)七、节点加入集群

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

53-JS之BOM,打开,关闭窗口,screen对象,history对象,location对象,工作区尺寸,滚动距离

1.简介 BOM(Browser Object Model)---浏览器对象模型,提供JS当中对浏览器的各种操作对象 1.1BOM与DOM 2.打开窗口window.open(URL,name,features) 2.1 URL字符串:地址网址文件源 2.2name:指target属性,规定在哪个窗口打开新的url链接 blank:打开一个新窗口 _parent…

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-4 轨迹追踪

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-4 轨迹追踪 1. 目标误差控制-误差的调节2. 稳态非零值控制3. 输入增量控制 1. 目标误差控制-误差的调节 2. 稳态非零值控制 3. 输入增量控制

Wpf 使用 Prism 实战开发Day14

备忘录接口增删&#xff08;CURD&#xff09;改查实现 一.添加备忘录控制器&#xff08;MemoController&#xff09; 备忘录控制器&#xff08;MemoController&#xff09;和待办事项控制器 &#xff08;ToDoController&#xff09;功能实现差不多一样。基本套路就是&#xff1…

用艺术陪伴困境群体活动在庐阳区双岗街道万小店社区开展

用艺术陪伴困境群体活动在庐阳区双岗街道万小店社区开展 1月23日上午9时&#xff0c;王莉老师带领“一欣工作室”的七位小朋友冒着严寒&#xff0c;来到位于万小店社区和煦园小区的合肥市庐阳区为民社会工作服务中心&#xff0c;慰问陪伴中心的兄弟姐妹。 大家一起唱歌、一起表…