LeetCode 40. Combination Sum II【回溯,剪枝】中等

news2024/11/28 20:58:10

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。


给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = `[10,1,2,7,6,1,5]`, target = `8`,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

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

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

解法 回溯+剪枝

由于我们需要求出所有和为 t a r g e t target target 的组合,并且每个数只能使用一次,因此我们可以使用递归 + 回溯的方法来解决这个问题:

我们用 d f s ( p o s , r e s t ) dfs(pos,rest) dfs(pos,rest) 表示递归的函数,其中 p o s pos pos 表示我们当前递归到了数组 c a n d i d a t e s candidates candidatess 中的第 p o s pos pos 个数,而 r e s t rest rest 表示我们还需要选择和为 r e s t rest rest 的数放入列表作为一个组合;

对于当前的第 p o s pos pos 个数,我们有两种方法:选或者不选。

  • 如果我们选了这个数,那么我们调用 d f s ( p o s + 1 , r e s t − c a n d i d a t e s [ p o s ] ) dfs(pos+1,rest−candidates[pos]) dfs(pos+1,restcandidates[pos]) 进行递归,注意这里必须满足 r e s t ≥ c a n d i d a t e s [ p o s ] rest≥candidates[pos] restcandidates[pos]
  • 如果我们不选这个数,那么我们调用 d f s ( p o s + 1 , r e s t ) dfs(pos+1,rest) dfs(pos+1,rest) 进行递归;

在某次递归开始前,如果 r e s t rest rest 的值为 0 0 0 ,说明我们找到了一个和为 t a r g e t target target 的组合,将其放入答案中。每次调用递归函数前,如果我们选了那个数,就需要将其放入列表的末尾,该列表中存储了我们选的所有数。在回溯时,如果我们选了那个数,就要将其从列表的末尾删除。

上述算法就是一个标准的递归 + 回溯算法,但是它并不适用于本题。这是因为题目描述中规定了解集不能包含重复的组合,而上述的算法中并没有去除重复的组合。例如当 c a n d i d a t e s = [ 2 , 2 ] candidates=[2,2] candidates=[2,2] t a r g e t = 2 target=2 target=2 时,上述算法会将列表 [ 2 ] [2] [2] 放入答案两次。

因此,我们需要改进上述算法,在求出组合的过程中就进行去重的操作。我们可以考虑将相同的数放在一起进行处理,也就是说,如果数 x x x 出现了 y y y 次,那么在递归时一次性地处理它们,即分别调用选择 0 , 1 , ⋯   , y 0,1,⋯ ,y 0,1,,y x x x 的递归函数。这样我们就不会得到重复的组合。具体地:

  1. 我们使用一个哈希映射(HashMap)统计数组 c a n d i d a t e s candidates candidates 中每个数出现的次数。在统计完成之后,我们将结果放入一个列表 f r e q freq freq 中,方便后续的递归使用。
  2. 列表 f r e q freq freq 的长度即为数组 c a n d i d a t e s candidates candidatess 中不同数的个数。其中的每一项对应着哈希映射中的一个键值对,即某个数以及它出现的次数。
  3. 在递归时,对于当前的第 p o s pos pos 个数,它的值为 f r e q [ p o s ] [ 0 ] freq[pos][0] freq[pos][0] ,出现的次数为 f r e q [ p o s ] [ 1 ] freq[pos][1] freq[pos][1] ,那么我们可以调用
    dfs ( pos + 1 , rest − i × freq [ pos ] [ 0 ] ) \textit{dfs}(\textit{pos} + 1, \textit{rest} - i \times \textit{freq}[\textit{pos}][0]) dfs(pos+1,resti×freq[pos][0])
    即我们选择了这个数 i i i 次。这里 i i i 不能大于这个数出现的次数,并且 i × freq [ pos ] [ 0 ] i \times \textit{freq}[\textit{pos}][0] i×freq[pos][0] 也不能大于 rest \textit{rest} rest 。同时,我们需要将 i i i freq [ pos ] [ 0 ] \textit{freq}[\textit{pos}][0] freq[pos][0] 放入列表中。

这样一来,我们就可以不重复地枚举所有的组合了。

我们还可以进行什么优化(剪枝)呢?一种比较常用的优化方法是,我们将 f r e q freq freq 根据数从小到大排序,这样我们在递归时会先选择小的数,再选择大的数(同[[LeetCode 39. Combination Sum【回溯,剪枝】中等]]的剪枝优化一样)。这样做的好处是,当我们递归到 d f s ( p o s , r e s t ) dfs(pos,rest) dfs(pos,rest) 时,如果 f r e q [ p o s ] [ 0 ] freq[pos][0] freq[pos][0] 已经大于 r e s t rest rest,那么后面还没有递归到的数也都大于 r e s t rest rest ,这就说明不可能再选择若干个和为 r e s t rest rest 的数放入列表了。此时,我们就可以直接回溯。

class Solution {
public:
    vector<pair<int, int>> freq;
    vector<vector<int>> ans;
    vector<int> seq;
    void dfs(int pos, int rest) {
        if (rest == 0) {
            ans.push_back(seq);
            return;
        }
        if (pos == freq.size() || rest < freq[pos].first) return;
        // 直接跳过
        dfs(pos + 1, rest);
        int most = min(rest / freq[pos].first, freq[pos].second);
        for (int i = 1; i <= most; ++i) {
            seq.push_back(freq[pos].first);
            dfs(pos + 1, rest - i * freq[pos].first);
        }
        for (int i = 1; i <= most; ++i) seq.pop_back();
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        for (int num : candidates) {
            if (freq.empty() || num != freq.back().first)
                freq.emplace_back(num, 1);
            else ++freq.back().second;
        }
        dfs(0, target);
        return ans;
    }
};

复杂度分析:

  • 时间复杂度: O ( 2 n × n ) O(2^n \times n) O(2n×n) ,其中 n n n 是数组 c a n d i d a t e s candidates candidates 的长度。在大部分递归 + 回溯的题目中,我们无法给出一个严格的渐进紧界,故这里只分析一个较为宽松的渐进上界。在最坏的情况下,数组中的每个数都不相同,那么列表 f r e q freq freq 的长度同样为 n n n 。在递归时,每个位置可以选或不选,如果数组中所有数的和不超过 t a r g e t target target ,那么 2 n 2^n 2n 种组合都会被枚举到;在 t a r g e t target target 小于数组中所有数的和时,我们并不能解析地算出满足题目要求的组合的数量,但我们知道每得到一个满足要求的组合,需要 O ( n ) O(n) O(n) 的时间将其放入答案中,因此我们将 O ( 2 n ) O(2^n) O(2n) O ( n ) O(n) O(n) 相乘,即可估算出一个宽松的时间复杂度上界。由于 O ( 2 n × n ) O(2^n \times n) O(2n×n) 在渐进意义下大于排序的时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,因此后者可以忽略不计。
  • 空间复杂度: O ( n ) O(n) O(n) 。除了存储答案的数组外,我们需要 O ( n ) O(n) O(n) 的空间存储列表 f r e q freq freq 、递归中存储当前选择的数的列表、以及递归需要的栈。

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

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

相关文章

Loopback for Mac:专业级的音频处理能力

Loopback for Mac是一款功能强大的虚拟音频设备&#xff0c;它能够将应用程序的音频输出路由到其他应用程序的输入&#xff0c;从而实现音频数据的传输和交互。以下是Loopback for Mac的一些主要功能和特色介绍&#xff1a; 创建虚拟音频设备&#xff1a;Loopback可以创建虚拟…

基于Python开发的Excel数据分析系统(源码+可执行程序+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python开发的Excel数据分析系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0…

源码编译risc-v虚拟机和编译器 riscv-gnu-toolchain 和 riscv-tools 在ubuntu 22.04

1. 编译 riscv-gnu-toolchain 1.1 预备环境 $ sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev 1.2 下载源代码 http…

Linux tcpdump抓包命令

1.tcpdump抓包命令 -c 指定抓取包的数量&#xff0c;即最后显示的数量 -i 指定tcpdump监听的端口。未指定&#xff0c;选择系统中最小的以配置端口。-i any:监听所有网络端口 -i lo:监听lookback接口。-nn 对监听地址以数字方式呈现&#xff0c;且对端口也以数字方式呈现。…

西门子S7-1200F或1500F系列安全PLC的组态步骤和基础编程(一)

西门子S7-1200F或1500F系列安全PLC的组态步骤和基础编程(一) 第一部分:组态配置 具体步骤可参考以下内容: 如下图所示,新建一个项目后,添加一个安全型PLC,这里以1516F-3 PN/DP为例进行说明, 如下图所示,添加CPU完成后,可以看到左侧的项目树中比普通的PLC多了几个选项…

el-date-picker 封装一个简单的日期组件, 主要是禁用日期

子组件 <template><div><el-date-pickerv-model"dateModel"type"datetimerange":picker-options"pickerOptions"range-separator"至"ref"picker"start-placeholder"开始日期"end-placeholder&quo…

深度学习-消融实验

深度学习中消融实验的目的 深度学习中&#xff0c;消融实验是一种用于理解和评估神经网络模型的技术。它的主要目的是通过逐步删除神经网络的某些组件或功能&#xff0c;来研究它们对模型性能的影响。通过这种方式&#xff0c;我们可以深入了解模型的工作原理、探索模型的鲁棒性…

第31章_瑞萨MCU零基础入门系列教程之WIFI蓝牙模块驱动实验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

【毕业设计】基于SSM的仓库进存销系统

前言 &#x1f525;本系统可以选作为毕业设计&#xff0c;运用了现在主流的SSM框架&#xff0c;采用Maven来帮助我们管理依赖&#xff0c;所选结构非常合适大学生所学的技术&#xff0c;本系统结构简单&#xff0c;容易理解&#xff01;本系统功能结构完整&#xff0c;非常高适…

下载HTMLTestRunner并修改

目录 一. 下载HTMLTestRunner 二. 修改HTMLTestRunner 1. 修改内容 2. 修改原因 一. 下载HTMLTestRunner 下载报告模板地址:http://tungwaiyip.info/software/HTMLTestRunner.html 下载模块&#xff1a; 二. 修改HTMLTestRunner 将修改后的模块放到python安装目录下的..…

【Mysql学习笔记】关键字顺序以及执行顺序

关键字顺序&#xff08;如上&#xff09;&#xff1a; SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ...关键字执行顺序&#xff08;如上&#xff09;&#xff1a; FROM > WHERE > GROUP BY > HAVING > SELECT的字段 > DISTINCT > ORD…

解决java.io.IOException: Network error

解决java.io.IOException: Network error 解决java.io.IOException: Network error摘要引言正文1. 理解异常的根本原因2. 处理网络连接问题3. 处理连接超时4. 处理协议错误或不匹配5. 异常处理 总结参考资料 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#…

Redis从入门到精通(二:数据类型)

数据存储类型介绍 Redis 数据类型&#xff08;5种常用&#xff09; string hash list set sorted_set/zset&#xff08;应用性较低&#xff09; redis 数据存储格式 redis 自身是一个 Map&#xff0c;其中所有的数据都是采用 key : value 的形式存储 数据类型指的是存储的数据…

使用html展示中秋节快乐的脚本

#【中秋征文】程序人生&#xff0c;中秋共享# html代码如下所示&#xff1a; <!DOCTYPE html> <html> <head> <title>中秋节</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; } h1 { c…

AutoSAR配置与实践(实践篇)13.6 如何添加一个NVM BLOCK (PIM类型)

AutoSAR配置与实践&#xff08;实践篇&#xff09;13.1 如何添加一个NVM BLOCK 如何添加一个NVM BLOCK &#xff08;PIM类型&#xff09;一、PIM&#xff08;PerInstanceMemory&#xff09;简介二、PIM添加步骤2.1 总体思路2.2 DEV工程步骤2.3 CFG工程步骤 如何添加一个NVM BLO…

用 Github Codespaces 免费搭建本地开发测试环境

如何丝滑地白嫖一个本地开发环境&#xff1f;怎么新建一个代码空间&#xff1f; 1&#xff1a;通过Github网页新建2&#xff1a;通过VSCode插件新建 为代码创建相应的开发测试环境 如何丝滑地白嫖一个本地开发环境&#xff1f; 使用Codespaces为开发者解决这样的痛点&#xf…

安装chromadb遇到的问题与python3升级

环境&#xff1a; python 3.10 &#xff0c; centos 7.x 使用 pip3 install chromadb 时&#xff0c;遇到以下问题。 问题1: gcc note: This error originates from a subprocess, and is likely not a problem with pip. ERROR: Failed building wheel for chroma-hnswlib F…

《PostgreSQL与MySQL:详细对比与分析》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

【Java 基础篇】Java 比较器排序:精通自定义对象排序

在 Java 编程中&#xff0c;排序是一个非常常见且重要的操作。Java 提供了多种排序机制&#xff0c;其中之一就是使用比较器&#xff08;Comparator&#xff09;进行排序。比较器允许您自定义对象的排序方式&#xff0c;使您能够实现各种排序需求&#xff0c;从简单的对象排序到…

stm32---定时器输入捕获

一、输入捕获介绍 在定时器中断实验章节中我们介绍了通用定时器具有多种功能&#xff0c;输入捕获就是其中一种。 STM32F1除了基本定时器TIM6和TIM7&#xff0c;其他定时器都具有输入捕获功能 。输入捕获可以对输入的信号的上升沿&#xff0c;下降沿或者双边沿进行捕获&#xf…