算法练习-组合总和【回溯算法】(思路+流程图+代码)

难度参考

        难度:困难

        分类:回溯算法

        难度与分类由我所参与的培训课程提供,但需 要注意的是,难度与分类仅供参考。且所在课程未提供测试平台,故实现代码主要为自行测试的那种,以下内容均为个人笔记,旨在督促自己认真学习。

题目

        给定一个无重复元素的数组candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合,candidates中的数字可以无限制重复被选取。

        说明:

        所有数字(包括target)都是正整数。

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

        示例1:

        输入:candidates =[2,3,6,7],target = 7,

        所求解集为:[[7],[2,2,3]]

        示例2:

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

        所求解集为:[[2,2,2,2],[2,3,3],[3,5]]

思路

        这个问题是一个典型的组合求和问题,可以通过回溯算法来解决。回溯算法基于试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其他的可能的分步解答再次尝试寻找问题的答案。

        对于这个特定问题,我们可以按照以下步骤来设计我们的算法:

        理解问题

给定一个无重复元素的数组candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。candidates中的数字可以无限制重复被选取。需要注意的是,解集不能包含重复的组合。

        设计策略

        排序(可选):首先,可以对candidates数组进行排序。这一步是可选的,但有助于优化,因为它可以让我们在某些情况下提前终止搜索。
回溯:使用回溯的方法来逐步构建组合,并且当组合的和等于目标值target时,将其添加到结果集中。
        选择路径:从candidates的开始,逐个尝试数组中的每个数,将其加入到当前组合中,并递归地调用回溯函数,继续尝试下一个数。每次递归调用时,目标值target减去当前选择的数。
撤销选择:如果当前组合的和大于target,或者已经找到一个有效的组合,就回溯,撤销最后的选择,尝试下一个数。
        实现细节

        使用一个辅助函数backtrack来进行回溯搜索。这个函数接受当前的组合combination,当前的开始搜索位置start,和当前的目标值target作为参数。
        当target减到0时,说明找到了一个有效的组合,将其添加到结果集中。
        对于candidates中的每个数,如果它不大于当前的target,就尝试将它加入到当前组合中,并递归地调用backtrack,同时将target减去这个数。每次递归调用后,需要撤销上一步的选择,以便尝试其他的数。
        通过这种方式,我们可以遍历所有可能的组合,找到所有和为target的组合。

示例

        假设我们有一个数组candidates = [2, 3, 6, 7]和一个目标数target = 7,我们需要找到所有组合,使得组合中数字的和为7。

        初始条件

        candidates = [2, 3, 6, 7]
        target = 7
        解决步骤

        开始搜索:

        从数组的第一个元素开始,即数字2。
        尝试数字2:
        将2加入到当前组合中,组合变为[2],target更新为7-2=5。
        再次尝试2,组合变为[2, 2],target更新为5-2=3。
        再次尝试2,组合变为[2, 2, 2],target更新为3-2=1。此时,再加2已经超过目标值,尝试下一个数字。
        尝试数字3:
        从组合[2, 2]开始,尝试加入3,组合变为[2, 2, 3],target更新为3-3=0。
        发现一个有效组合[2, 2, 3],因为它们的和等于原始target7。
        回溯到组合[2],尝试下一个数字。
        尝试数字3:
        从组合[2]开始,尝试加入3,组合变为[2, 3],target更新为5-3=2。
        此时再加3已经超过目标值,尝试下一个数字。
        尝试数字6:
        从组合[2]开始,尝试加入6,但发现2+6已经超过目标值7,因此不再继续尝试。
        尝试数字7:
        从空组合开始,尝试加入7,组合变为[7],target更新为7-7=0。
        发现一个有效组合[7]。
        搜索结束:最终找到的有效组合有[2, 2, 3]和[7]。
        结果

        有效组合1:[2, 2, 3]
        有效组合2:[7]

梳理

        回溯算法的核心在于尝试与撤销的过程,通过这种方式来遍历问题的所有可能解。在上述例子中,我们使用回溯算法来寻找所有和为特定target的组合。这一过程体现在以下几个方面:

        我们从candidates数组的开始尝试每一个数,将其加入到当前的组合中,并更新剩余的target(即target减去当前数的值)。这一步骤相当于在决策树上向下走一步。

        在加入一个数到组合后,我们递归地调用相同的函数来处理更新后的target和当前组合。这一过程相当于在探索决策树的一个分支,直到找到解或者该分支无法继续向下探索(即当前组合的和超过了target)。

        当我们发现当前的组合无法满足条件(和超过target)或者我们已经找到了一个有效的组合,我们需要撤销最后的选择(即从当前组合中移除最后加入的数),然后尝试下一个数。这相当于在决策树上回到上一个节点,并探索另一个分支。

        在上述例子中,当我们尝试组合[2, 2]并继续加入2时,发现和变为6,仍然小于target7。我们再次尝试加入2,此时和变为8,超过了target。于是,我们撤销最后一次加入2的操作,回到和为6的状态(即组合[2, 2]),然后尝试加入3。这个撤销操作就是回溯的体现,它让我们能够回到之前的状态,尝试其他可能的数,直到找到所有满足条件的组合。

        通过这种方式,回溯算法能够系统地探索所有可能的组合,直到找到所有满足条件的解。每当遇到一个死路,算法就会回溯到上一个节点,尝试其他的路径。这种策略确保了算法能够覆盖到决策树的每一个节点,从而找到所有可能的解。

代码

#include <vector> // 包含 vector 头文件
#include <iostream> // 包含输入输出流头文件
#include <algorithm> // 包含算法头文件using namespace std; // 使用标准命名空间void backtrack(vector<int>& candidates, int target, vector<vector<int>>& res, vector<int>& combination, int start) {
if (target == 0) { // 如果目标值为0,找到一个有效的组合
res.push_back(combination); // 将组合添加到结果中
return; // 结束此次递归
}for (int i = start; i < candidates.size() && target >= candidates[i]; ++i) {combination.push_back(candidates[i]);  // 选择当前数字backtrack(candidates, target - candidates[i], res, combination, i);  // 继续探索combination.pop_back();  // 撤销选择
}
}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res; // 存储结果的二维向量
vector<int> combination; // 存储单个组合的一维向量
sort(candidates.begin(), candidates.end()); // 先对候选数字进行排序,有助于提前终止无效的探索
backtrack(candidates, target, res, combination, 0); // 调用回溯函数进行组合求解
return res; // 返回结果
}int main() {
vector<int> candidates1 = {2, 3, 6, 7}; // 候选数字序列
int target1 = 7; // 目标和
vector<vector<int>> res1 = combinationSum(candidates1, target1); // 调用组合求解函数得到结果
cout << “Example 1:” << endl;
for (const auto& combination : res1) { // 遍历每一个组合
for (int num : combination) { // 遍历组合中的每一个数字
cout << num << " "; // 输出数字
}
cout << endl; // 换行
}vector<int> candidates2 = {2, 3, 5};  // 候选数字序列
int target2 = 8;  // 目标和
vector<vector<int>> res2 = combinationSum(candidates2, target2);  // 调用组合求解函数得到结果
cout << "Example 2:" << endl;
for (const auto& combination : res2) {  // 遍历每一个组合for (int num : combination) {  // 遍历组合中的每一个数字cout << num << " ";  // 输出数字}cout << endl;  // 换行
}return 0;
}

        时间复杂度:0(2^n),n是数组candidates的长度。

        空间复杂度:o(target)

打卡

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

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

相关文章

软件测试人员的基本功包括些什么?

软件测试人员的基本功包括哪些呢&#xff1f;接下来该问题的阐述结构如下&#xff1a; 1、一看软件测试基本流程 2、明确软件测试的基本功有哪些 3、如何牢固掌握这些基本功 软件测试基本流程 上图就是软件测试的基本流程 1&#xff09;需求评审 2&#xff09;计划编写 …

stm32利用CubeMX实现外部中断触发数码管加减数

首先打开proteus绘制电路图&#xff0c;如下&#xff1a; 然后打开CubeMX&#xff0c;配置晶振和GPIO&#xff1a; 接下来就是生成keil工程文件&#xff0c;用keil打开。 新建一个desplay.h文件&#xff1a;下面是全部代码 #ifndef __DESPLAY_H #define __DESPLAY_H #endif#i…

【C++】多态概念(入门)

介绍&#xff1a; 多态的概念&#xff1a;通俗来说&#xff0c;多态就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。比如扫红包操作&#xff0c;同样是扫码动作&#xff0c;不同的用户扫 得到的不一样的红包&#xff0…

五.AV Foundation 视频播放 - 标题和字幕

引言 本篇博客主要介绍使用AV Foundation加载视频资源的时候&#xff0c;如何获取视频标题&#xff0c;获取字幕并让其显示到播放界面。 设置标题 资源标题的元数据内容&#xff0c;我们需要从资源的commonMetadata中获取&#xff0c;在加载AVPlayerItem的时候我们已经指定了…

03|JOIN关联查询优化

1. mysql关联算法 1.1 嵌套循环连接 Nested-Loop Join(NLJ) 算法 先去t2表&#xff08;驱动表&#xff09;拿一行数据,然后去t1表&#xff08;被驱动表&#xff09;做关联, 关联之后把结果集存下来最后返回. 1.2 基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法 1.把 t…

Vulnhub靶机:DC8

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;DC8&#xff08;10.0.2.61&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/dc-8,367/…

Linux字符设备驱动中同类型多设备节点的创建---一个驱动程序支持多个同类型设备

文章目录 前言1 代码解析1.1 驱动层1.2 应用层 2 运行结果总结 前言 本期分享的内容相对比较简单&#xff0c;那就是同时注册多个同类型的字符设备驱动&#xff0c;那么这样我们就可以同时支持多个同类型的设备了&#xff01;下面来带大家看一下&#xff1a; 1 代码解析 1.1 …

基于springboot+vue的精准扶贫管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

从Unity到Three.js(outline 模型描边功能)

指定模型高亮功能&#xff0c;附带设置背景颜色&#xff0c;获取随机数方法。 百度查看说是gltf格式的模型可以携带PBR材质信息&#xff0c;如果可以这样&#xff0c;那就完全可以在blender中配置好材质导出了&#xff0c;也就不需要像在unity中调整参数了。 import * as THRE…

Autosar 开篇

背景 AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;是一个跨汽车行业的标准化软件架构&#xff0c;旨在促进汽车电子系统的开发和部署。下面是AUTOSAR发展的一些关键点&#xff1a; 起源和背景&#xff1a; AUTOSAR最初于2003年由汽车制造商宝马、戴姆…

使用GPT生成python图表

首先&#xff0c;生成一脚本&#xff0c;读取到所需的excel表格 import xlrddata xlrd.open_workbook(xxxx.xls) # 打开xls文件 table data.sheet_by_index(0) # 通过索引获取表格# 初始化奖项字典 awards_dict {"一等奖": 0,"二等奖": 0,"三等…

MCU多核异构通信原理

摘要&#xff1a; 本文结合瑞萨RZ/G2L 多核处理器&#xff0c;给大家讲述一下多核异构设计及通信的原理。 随着电子技术的不断发展&#xff0c;以及市场需求的日益增长&#xff0c;嵌入式系统不仅要求执行复杂的控制任务&#xff0c;还需要实时地采集和处理数据。 为了满足这…

HarmonyOS开发行业前景就业分析与实例解析

HarmonyOS的简介 鸿蒙系统&#xff08;HarmonyOS&#xff09;是华为公司自主研发的一种全场景分布式操作系统&#xff0c;旨在为各种设备提供统一的开发和运行环境。它的编程基础主要建立在多种技术和语言之上&#xff0c;包括鸿蒙系统的核心框架和应用程序开发框架。 本章将…

Easy-Jmeter: 性能测试平台

目录 写在开始1 系统架构2 表结构设计3 测试平台生命周期4 分布式压测5 压力机管理6 用例管理6.1 新增、编辑用例6.2 调试用例6.3 启动测试6.4 动态控量6.5 测试详情6.6 环节日志6.7 实时数据6.8 测试结果 7 测试记录7 用例分析8 系统部署8.1普通部署8.2容器化部署 写在最后 写…

【技术分享】使用nginx完成动静分离➕集成SpringSession➕集成sentinel➕集成seata

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于技术点的相关分享吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 一、 使用nginx完成动静分离 1.下载…

【数据集】世界水评估方案指标:灌溉面积/灌溉用水等

世界水评估方案指标 概述(Overview)数据下载(Data Download)案例1:F. Irrigated lands案例2:G. Irrigated water use参考World Water Development Report II-Indicators for World Water Assessment Programme 概述(Overview) 在关于全球环境变化和可持续发展的辩论…

微信小程序(1)- 小程序开发工具

1. 小程序开发工具下载 地址&#xff1a;官网 微信小程序账号只要开发者满足开发资质都可以进行注册&#xff0c;并且会获得对应的 开发者 ID。一个完整的开发者 ID 由 小程序 ID&#xff08;AppID&#xff09;和一个 小程序密钥&#xff08;AppSecret&#xff09;组成。小程…

JAVA算法和数据结构

一、Arrays类 1.1 Arrays基本使用 我们先认识一下Arrays是干什么用的&#xff0c;Arrays是操作数组的工具类&#xff0c;它可以很方便的对数组中的元素进行遍历、拷贝、排序等操作。 下面我们用代码来演示一下&#xff1a;遍历、拷贝、排序等操作。需要用到的方法如下 public…

嵌入式学习第二十天!(进程)

进程基本概念&#xff1a; 1. 进程&#xff1a; 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程&#xff0c;包括进程的创建、进程的调度、进程的消亡 2. 进程相关命令&#xff1a; 1. top: 动态查看当前系统中的所有进程信息…

HarmonyOS—添加/删除Module

Module是应用/服务的基本功能单元&#xff0c;包含了源代码、资源文件、第三方库及应用/服务配置文件&#xff0c;每一个Module都可以独立进行编译和运行。一个HarmonyOS应用/服务通常会包含一个或多个Module&#xff0c;因此&#xff0c;可以在工程中创建多个Module&#xff0…