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

力扣日记:【回溯算法篇】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.电话号码的字母组合
    • 在求和问题中,排序之后加剪枝是常见的套路!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【域名解析】如何将域名指向对应服务器IP

目录 &#x1f337;一、域名解析基本概念 &#x1f33c;1. 定义 &#x1f33c;2. 域名解析类型 &#x1f337;二、域名解析服务器IP地址 &#x1f33c;1. 操作步骤 &#x1f33c;2. 验证 &#x1f337;一、域名解析基础知识 &#x1f33c;1. 基本概念 定义&#xff1a; …

微信小程序开发scroll-view在预览或真机调试仅显示第一个元素解决方案

现象如下&#xff1a; 在编译时显示正常&#xff1a; 在预览或真机调试时仅显示第一个元素&#xff1a; 解决方案&#xff1a;将app.json文件中renderer类型由skyline改为webview 更多微信小程序内容欢迎关注我&#xff01; 有帮助的话欢迎打赏&#xff01;

二次开发RuoYi-Vue操作记录

二次开发RuoYi-Vue操作记录 一、本地启动修改1、修改文件路径2、修改redis配置3、数据源配置4、导入sql 二、新增模块1、创建模块2、添加依赖 三、新增页面1、数据准备2、代码生成3、菜单管理&#xff08;1&#xff09;目录创建&#xff08;2&#xff09;菜单创建&#xff08;3…

ESP32开发板可以承受的最大电压

ESP32的最大工作电压为3.3V。但这并不意味着我们不能向 ESP32开发板施加大于 3.3V 的电压。ESP32 具有板载稳压器&#xff0c;使用 VIN 和 GND 引脚可承受最大 15V 的电压。 一旦电压输入到 ESP32 开发板VIN 引脚&#xff0c;该电压就会通过 ESP32 板载稳压器&#xff08;AMS11…

宏景eHR SmsAcceptGSTXServlet XXE漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR SmsAcceptGSTXServlet接口处存在XML实体注入漏洞,未经身份认证的攻击者可以利用此漏洞读取系统内部敏感文件,获取敏…

群辉NAS的远程访问

群辉NAS是私有云存储&#xff0c;局域网访问很容易【详见&#xff1a;网上邻居访问设置、其它设备的访问设置】&#xff0c;远程访问相对复杂&#xff0c;涉及很多关键因素&#xff0c;现将过程记录如下&#xff1a; 目录 1、互联网接入 2、绑定MAC与IP地址 3、路由器开启5…

qml中访问控件内部的子项

如何访问Repeater类型内部的子项、Row等布局类型内部的子项以及ListView内部的子项等。。。 1、测试代码 import QtQuick 2.0 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.3 import QtQml 2.12Window {id: windowobjectName: "m…

米贸搜|Facebook“精准营销”越来越难?或许是“受众定位”没彻底搞清!

一、为何要确定目标受众 对于每个广告主而言&#xff0c;面向最有可能成为其客户的用户营销非常重要&#xff0c;因此&#xff0c;确定目标受众&#xff0c;是Facebook广告投放中极其重要的一环。 二、什么是目标受众&#xff1f; 目标受众是您希望向其传达营销信息&#xf…

java servlet 高校田径运动会管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 jsp高校田径运动会管理系统是一套完善的java web信息管理系统 采用mvc模式 servletdaobean 模式开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myecl…

“JavaScript 循环中的 ‘await‘

目录 前言 for使用await -- 有效的 forEach使用await -- 无效的 for of 使用await 有效的 for await of 使用await 有效的 总结 前言 在JavaScript的forEach方法中使用await是无效的&#xff0c;因为forEach方法不支持异步操作的等待。 forEach是一个数组的遍历方法&…

如何修改hosts文件-mac

1、什么是hosts文件&#xff1f; Hosts是一个没有扩展名的系统文件&#xff0c;可以用记事本等工具打开&#xff0c;其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”&#xff0c;即域名映射到IP地址&#xff0c;域名是为了方便人类记忆识别&#xff0c…

力扣354. 俄罗斯套娃信封问题

动态规划 思路&#xff1a; 同时控制 w、h 两个维度比较复杂&#xff0c;可以先固定一个维度&#xff0c;来找出另外一个维度的严格单调序列&#xff1a; 对 w 排序&#xff0c;然后再来找 h 维度严格单调递增序列长度&#xff1b;在 w 排序时&#xff0c;会遇到 w(i) w(j) 的…

VS2022联合Qt5开发学习11(QT5.12.3联合VTK在VS2022上开发医学图像项目5——qvtkWidget上显示STL三维图像并取点)

这篇博文是接着这个系列前面的博文&#xff0c;来讲如何实现医学图像三视图同步视图。我想到的一个思路是用Scrollbar来控制切面的改变&#xff0c;还有一个想法是在三维图像上取点&#xff0c;然后以这个点为切面中心更新三维视图。这篇博文主要介绍的就是第二个想法的三维图像…

C++ 之LeetCode刷题记录(十八)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 依旧是追求耗时0s的一天。 104. 二叉树的最大深度 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最…

防火墙源NAT配置

拓扑 需求 生产区在工作时间内可以访问服务器区&#xff0c;仅可以访问HTTP服务器。办公区全天可以访问服务区&#xff0c;其中&#xff0c;10.0.2.20可以访问FTP服务器和HTTP服务器 10.0.2.10仅可以ping通10.0.3.10办公区在访问服务区时采用匿名认证方式进行上网行为管理。办…

蓝桥杯备战——2.矩阵键盘

1.分析原理图 由上图可以看到若J5跳线帽接地&#xff0c;就S4~S7就可以当做四路独立按键&#xff0c;若接到P44&#xff0c;则就是4*4的矩阵键盘。 2.独立按键处理 相对传统的按键延时消抖方案&#xff0c;这里我采用更高效&#xff0c;更经典&#xff0c;更偏向产品级应用的…

JavaScript DOM之Cookie详解

cookie有的地方习惯使用复数形式的cookies&#xff0c;指的是网站为了识别用户的身份或者进行一些必要数据的缓存而使用的技术&#xff0c;它的数据是存在用户的终端上&#xff0c;也就是在浏览器上的。 一、什么是cookie 随着互联网的不断发展各种基于互联网的服务系统逐渐多…

第139期 做大还是做小-Oracle名称哪些事(20240125)

数据库管理139期 2024-01-25 第139期 做大还是做小-Oracle名称哪些事&#xff08;20240125&#xff09;1 问题2 排查3 扩展总结 第139期 做大还是做小-Oracle名称哪些事&#xff08;20240125&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文&#xff09; Oracle A…

【C#】基础巩固

最近写代码的时候各种灵感勃发&#xff0c;有了灵感&#xff0c;就该实现了&#xff0c;可是&#xff0c;实现起来有些不流畅&#xff0c;总是有这样&#xff0c;那样的卡壳&#xff0c;总结下来发现了几个问题。 1、C#基础内容不是特别牢靠&#xff0c;理解的不到位&#xff…

2016年认证杯SPSSPRO杯数学建模B题(第一阶段)低分辨率下看世界全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 B题 低分辨率下看世界 原题再现&#xff1a; 数码摄像技术被广泛使用于多种场合中。有时由于客观条件的限制&#xff0c;拍摄设备只能在较低的分辨率下成像。为简单起见&#xff0c;我们只考虑单色成像。假设成像的分辨率为 32 64&#xff0c…