leetCode 39.组合总和 + 回溯算法 + 剪枝 + 图解 + 笔记

39. 组合总和 - 力扣(LeetCode)


给你一个 无重复元素 的整数数组 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
输出: []

思路和分析:对比 77. 组合 - 力扣(LeetCode) 和  216. 组合总和 III - 力扣(LeetCode) ,和本题的区别,本题没有数量要求,且可以无限重复,但是受到总和限制,故间接的限制了个数

(一)回溯算法

>>回溯三部曲:

1).确定回溯函数参数

  • path来收集符合条件的结果
  • result 保存 path,作为结果集
  • startIndex 来控制for循环的起始位置

>>注意(来自代码随想录Carl老师的总结:代码随想录 (programmercarl.com))

  • 如果是一个集合来求组合的话,就需要startIndex:77. 组合 - 力扣(LeetCode)  和 216. 组合总和 III - 力扣(LeetCode)
  • 如果是多个集合取组合,各个集合之间相互不影响,那么就不用 startIndex:17. 电话号码的字母组合 - 力扣(LeetCode)
vector<vector<int>> result;
vector<int> path; 
void backtracking(vector<int>& candidates,int target,int sum,int startIndex) {

2).递归的终止条件

if (sum > target) {
    return;
}
if (sum == target) {
    result.push_back(path);
    return;
}

  3).单层搜索的逻辑

  • 单层 for 循环依然是从 startIndex 开始,搜索 candidates 集合。
  • 为了实现重复选取,backtracking 的时候传入的 startIndex i ,而不是 i + 1。比如在本层已经使用了 "2",下一层依然可以取到
for (int i = startIndex; i < candidates.size(); i++) {
    sum += candidates[i];
    path.push_back(candidates[i]);
    backtracking(candidates, target, sum, i); // 关键点:不用i+1了,表示可以重复读取当前的数
    sum -= candidates[i];   // 回溯
    path.pop_back();        // 回溯
}

 C++代码:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum > target) {
            return;
        }
        if (sum == target) {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size(); i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数
            sum -= candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

sum>target 这种情况,其实还是进入了下一层递归,只是下一层递归结束判断的时候,发现sum > target 就返回(结束本层递归),这里可以做优化,接着往下看!

(二)剪枝操作 

我们看这张图,是candidates为[2,4,5] ,而上图是candidates为[2,5,4],它们绿色节点出现的位置并不一样,发现下图排完序之后,若能找到符合的节点,其实是当前同层的前面是没有不符合的节点的。(描述可能有点问题,大家可以对比一下找规律)

其实如果已经知道下一层的sum > target,就没必要再进入下一层递归了 


1.在求和问题中,排序之后加剪枝是常见的套路,所以candidates需要先排序,后剪枝

sort(candidates.begin(), candidates.end()); // 需要排序

2.for循环剪枝:

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)

C++代码: 

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) {
            result.push_back(path);
            return;
        }

        // 如果 sum + candidates[i] > target 就终止遍历
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i);
            sum -= candidates[i];
            path.pop_back();

        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

(三)减少一个参数

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path; 
    void backtracking(vector<int>& candidates,vector<int> path,int target,int startIndex) {
        if(target == 0){
            result.push_back(path);
            return;
        }
        for(int i=startIndex;i<candidates.size() && target-candidates[i] >= 0;i++) {
            target -= candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,path,target,i);
            target += candidates[i];
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates,path,target,0);
        return result;
    }
};

 参考和推荐文章、视频:

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html#%E6%80%9D%E8%B7%AF带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1KT4y1M7HJ/?spm_id_from=333.788&vd_source=a934d7fc6f47698a29dac90a922ba5a3

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

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

相关文章

前端入门(四)Ajax、Promise异步、Axios通信、vue-router路由

文章目录 AjaxAjax特点 Promise 异步编程&#xff08;缺&#xff09;Promise基本使用状态 - PromiseState结果 - PromiseResult Axios基本使用 Vue路由 - vue-router单页面Web应用&#xff08;single page web application&#xff0c;SPA&#xff09;vue-router基本使用路由使…

ruby3.2.2 报错 undefined symbol: EC_GROUP_new_curve_GF2m

一、执行ruby -ropenssl -e puts OpenSSL::OPENSSL_VERSION 查看openssl版本时报错 ruby -ropenssl -e puts OpenSSL::OPENSSL_VERSION 这是因为ruby内的openssl版本是3.2.0版本的 而自openssl3.0以后已经废弃 EC_GROUP_new_curve_GF2m了 二、解决方案 指定ruby内的openssl…

手写promise A+、catch、finally、all、allsettled、any、race

目录 手写promise 同步版 1.Promise的构造方法接收一个executor()&#xff0c;在new Promise()时就立刻执行executor回调 2.executor()内部的异步任务被放入宏/微任务队列&#xff0c;等待执行 3.状态与结果的管理 状态只能变更一次 4.then()调用成功/失败回调 catch是…

解决:SyntaxError: Non-UTF-8 code starting with À in file but no encoding declared

解决&#xff1a;SyntaxError: Non-UTF-8 code starting with in file but no encoding declared 文章目录 解决&#xff1a;SyntaxError: Non-UTF-8 code starting with in file but no encoding declared背景报错问题报错翻译报错原因解决方法使用utf-8格式使用gbk格式今天…

计算机网络408

一&#xff1a;计算机网络体系结构 1.计网的概念&#xff0c;组成&#xff0c;功能和分类 一&#xff1a;计算机网络的发展 (3)从功能组成视觉看&#xff1a;分为资源子网和通信子网 2.计网性能指标

后台管理系统开源项目

最近项目没有什么事做&#xff0c;就自己整理&#xff0c;修改了一些vue2&#xff0c;react的后台管理系统项目&#xff0c;方便以后有需要可以直接提取&#xff0c;当然也方便了大家 vue2技术栈 lyl-vueProjectAdmin: vue2后台管理系统 react技术栈 lyl-reactAdminProject:…

快速筛出EXCEL行中的重复项

比如A列是一些恶意IP需要导入防火墙&#xff0c;但包括一些重复项&#xff0c;为不产生错误&#xff0c;需要把重复项筛出来&#xff1a; 1、给A列排序&#xff0c;让重复项的内容排在相邻的行 2、在B列中写一个条件函数&#xff1a;IF(A1A2,1,0)&#xff0c;然后下拉至行尾完成…

2023-11-28-直播单细胞图表美化-seurat数据结构 featureplot dotplot vlnplot

单细胞常见的可视化方式有DimPlot&#xff0c;FeaturePlot &#xff0c;DotPlot &#xff0c;VlnPlot 和 DoHeatmap几种 &#xff0c;Seurat中均可以很简单的实现&#xff0c;但是文献中的图大多会精美很多。 之前 跟SCI学umap图| ggplot2 绘制umap图&#xff0c;坐标位置 &am…

在 C# 中复制 Word、Excel、PDF 和 PPT 文档

在 C# 中复制文档可能是各种软件应用程序中的一项基本任务。无论您是构建文件管理系统、创建备份实用程序&#xff0c;还是出于任何原因仅需要复制文档&#xff0c;都需要高效的文件处理和复制机制。在这篇博文中&#xff0c;我们将引导您逐步完成在 C# 中复制文档的过程。在代…

pgsql分别获取日期中的年、月、日,并处理前台展示有小数点的情况

使用extract()函数 select extract(YEAR from 需要处理的日期字段) from tablename; --获取年份 select extract(MONTH from 需要处理的日期字段) from tablename; --获取月份 select extract(DAY from 需要处理的日期字段) from tablename; --获取日 实际应用&#xff1a;…

宠物网站的技术 SEO:完整指南

您是宠物行业网站的从业者吗&#xff1f;那么您一定知道&#xff0c;当人们寻找与宠物相关的资源时&#xff0c;在搜索引擎结果中排名靠前有多么重要。 这就是技术SEO的用武之地&#xff01;它正在调整您网站的后端代码和服务器配置&#xff0c;以在 SERP 中排名更高。 在此&…

Vue3-目录调整

默认生成的目录结构不满足我们的开发需求&#xff0c;所以这里需要做一些自定义改动。 主要是以下工作&#xff1a; 1.删除一些初始化的默认文件 2.修改剩余代码内容 3.新增调整我们需要的目录结构 在src文件夹下创建两个新文件夹&#xff0c;一个叫api&#xff08;请求模…

uniapp+微信小程序监听返回事件

代码附在最后 适用场景&#xff1a;uniapp开发微信小程序 需求是我点击列表进入数据信息的详情界面&#xff0c;点击详情界面的收藏&#xff0c;返回上一界面后&#xff0c;更新列表中的收藏情况。 目录 一、使用onUnload监听页面卸载 二、使用getCurrentPages()获取当前页…

UniApp项目中 使用微信小程序原生语言 进行开发

看效果 wxcomponents 下放的是微信小程序原生代码写的组件。我进行了封装 上干货 在你下uniApp 项目的根目录创建一个 wxcomponents 名字千万不要错 京东、支付宝灯参考下面图片 官方文档也有介绍 然后在你需要引入原生功能的页面里面引入你的组件&#xff08;我这里提前已经放…

深度学习之图像分类(十五)DINAT: Dilated Neighborhood Attention Transformer理论精简摘要(二)

Dilated Neighborhood Attention Transformer摘要 局部注意力机制&#xff1a;例如滑动窗口Neighborhood Attention&#xff08;NA&#xff09;或Swin Transformer的Shifted Window Self Attention。 优点&#xff1a;尽管在降低自注意力二次复杂性方面表现出色&#xff0c; …

【Web】SWPUCTF 2022 新生赛 个人复现

目录 ①webdog1__start ②ez_rce ③ez_sql ④ez_1zpop ⑤file_maste ⑥Power! 挑了部分题&#xff0c;太简单的就没选进来&#xff08;但选进来≠有难度&#xff09; ①webdog1__start 进来没啥东西&#xff0c;右键查看源码 对于0e215962017&#xff0c;md5后也是以…

ACM程序设计课内实验(1)数学问题

1.The Hardest Problem Ever Description Julius Caesar生活在一个危险而又充斥着阴谋的时代。Caesar面对的最难的情况关系着他的存亡。为了让自己生存&#xff0c;他决心去创造第一种加密方法之一。这个加密方法听起来是这样的令人难以置信&#xff0c;没有一个人可以指出它&a…

视频集中存储/磁盘阵列EasyCVR平台黑名单异常解决步骤是什么?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

figma 基础使用 —— 常用方法

一、 导入组件 分成两种方式 &#xff08;1&#xff09;离线的包导入&#xff08;iOS 常用组件.fig 直接拖拽到figma最近网页&#xff09; &#xff08;2&#xff09;在插件市场下载https://www.figma.com/community 二、figma中使用标尺 快捷键&#xff1a;shift R 三、插…

vue实现动态路由菜单!!!

目录 总结一、步骤1.编写静态路由编写router.jsmain.js注册 2.编写permisstions.js权限文件编写permisstions.jsaxios封装的APIstore.js状态库system.js Axios-APIrequest.js axios请求实例封装 3.编写菜单树组件MenuTree.vue 4.主页中使用菜单树组件 总结 递归处理后端响应的…