LeetCode 39.组合总和:回溯法与剪枝优化的完美结合

一、问题本质与形式化定义

1.1 题目形式化描述

  • 输入:无重复整数数组candidates、目标值target
  • 输出:所有和为target的组合集合,满足:
    • 元素可重复使用
    • 组合内元素非降序(避免重复解)
    • 解集无重复组合

1.2 问题本质分析

属于无界组合问题(元素可重复选),与0-1组合问题的核心区别在于:

  • 0-1组合:每个元素只能选0或1次(对应背包问题中的0-1背包)
  • 无界组合:每个元素可选0到多次(对应完全背包问题)

二、回溯法核心实现与状态机模型

2.1 回溯算法的标准框架

void backtracking(参数) {if (终止条件) {处理结果;return;}for (选择:本层可选择的所有选项) {做出选择;backtracking(下一层参数);撤销选择;}
}

本问题中对应的关键映射:

  • 选择:将candidates[i]加入当前组合
  • 终止条件:sum == targetsum > target
  • 下一层参数:start=i(允许重复选当前元素)

2.2 状态空间树可视化

candidates=[2,3,6,7], target=7为例,状态树结构:

          根(0)/   |   \2     3     6(剪枝)    7/ | \   |2  3  6(剪) 3/ |     |
2  3(7) 6(剪)
  • 树的深度:最大为target/最小元素(本例中为7/2=3层)
  • 分支数:每层最多为candidates.length - start

三、代码逐行解析与关键技术点

3.1 初始化与排序处理

Arrays.sort(candidates); // 核心优化点

排序的三大作用:

  1. 剪枝基础:确保后续元素递增,可通过sum + candidates[i] > target提前终止
  2. 去重保证:固定组合内元素顺序(如[2,3]不会出现[3,2])
  3. 逻辑简化:使递归过程中的选择具有有序性

3.2 回溯函数深度解析

void backtracking(int[] candidates, int target, int start, int sum) {// 终止条件1:找到合法组合if (sum == target) {res.add(new ArrayList<>(temp)); // 注意深拷贝return;}// 终止条件2:超过目标和(剪枝点1)if (sum > target) {return;}// 本层选择遍历(从start开始)for (int i = start; i < candidates.length; i++) {// 剪枝点2:当前选择必超目标和,后续更大元素直接跳过if (sum + candidates[i] > target) break;// 状态变更:选择当前元素sum += candidates[i];temp.add(candidates[i]);// 递归:允许重复选当前元素(i不变)backtracking(candidates, target, i, sum);// 状态回溯:撤销选择sum -= candidates[i];temp.removeLast();}
}
3.2.1 深拷贝的必要性
  • 为什么不能直接res.add(temp)
    • temp是引用类型,后续回溯会修改其内容
    • 示例:当找到[2,2,3]后,temp会被回溯清空,若不拷贝则结果集存储的是空列表
3.2.2 start参数的核心作用
  • 控制组合唯一性的关键:
    • start=i时,同一层不会重复选前面的元素
    • 例如:第一层选2后,第二层只能从i=0(即2)开始选,保证组合内元素非降序

四、剪枝策略的数学证明与效率分析

4.1 剪枝点的数学推导

定理:若数组已排序,当sum + candidates[i] > target时,后续元素candidates[j] (j>i)必然满足sum + candidates[j] > target

  • 证明:
    • 因数组排序,故candidates[j] >= candidates[i]
    • sum + candidates[j] >= sum + candidates[i] > target
  • 推论:此时可直接break退出循环,避免无效递归

4.2 时间复杂度精确分析

  • 最坏情况(所有元素为1,target=n):
    • 组合数为C(n-1,0)+C(n-1,1)+…+C(n-1,n-1)=2^(n-1)
    • 每层递归时间复杂度为O(1)(不考虑深拷贝)
    • 总时间复杂度:O(2^target)
  • 优化后时间复杂度:
    • 设数组最小元素为m,则递归深度最大为target/m
    • 实际复杂度:O(k * 2^(target/m)),k为平均分支因子

五、边界情况与扩展问题

5.1 特殊输入处理

  • candidates为空或所有元素都大于target时:
    • 直接返回空列表,无需递归
  • target=0时:
    • 需特殊处理空组合是否合法(本题中target≥1)

5.2 扩展问题:含重复元素的组合总和

candidates含重复元素(如[1,1,2]),需增加去重逻辑:

for (int i = start; i < candidates.length; i++) {// 同一层中跳过相同元素(去重核心)if (i > start && candidates[i] == candidates[i-1]) continue;// 后续逻辑不变
}

原理:保证同一层中相同元素只处理一次,避免[1,2][1,2]这样的重复组合

六、算法执行过程动态演示

6.1 示例输入完整流程

candidates=[2,3,6,7], target=7为例,递归调用栈变化:

  1. 初始调用:backtracking([2,3,6,7],7,0,0)
  2. i=0(元素2):
    • sum=0+2=2 ≤7,加入temp→[2]
    • 递归调用:backtracking([...],7,0,2)
  3. 第二层i=0:
    • sum=2+2=4 ≤7,加入temp→[2,2]
    • 递归调用:backtracking([...],7,0,4)
  4. 第三层i=0:
    • sum=4+2=6 ≤7,加入temp→[2,2,2]
    • 递归调用:backtracking([...],7,0,6)
  5. 第四层i=0:
    • sum=6+2=8 >7,回溯→sum=6,temp→[2,2,2]
    • i=1(元素3):sum=6+3=9>7,回溯
    • i=2(元素6):sum=6+6=12>7,回溯
    • i=3(元素7):sum=6+7=13>7,回溯
    • 回到第三层,sum=4,temp→[2,2]
  6. 第三层i=1(元素3):
    • sum=4+3=7=target,记录组合[2,2,3]
    • 回溯→sum=4,temp→[2,2]
    • 后续i=2、3均超7,回到第二层
  7. 第二层i=1(元素3):
    • sum=2+3=5 ≤7,加入temp→[2,3]
    • 递归调用:backtracking([...],7,1,5)
    • 后续选择3时sum=5+3=8>7,回溯
    • 选择6、7均超,回到第一层
  8. 第一层i=1(元素3):
    • sum=0+3=3 ≤7,加入temp→[3]
    • 递归调用中选择3+3+3=9>7,最终无合法组合
  9. 第一层i=3(元素7):
    • sum=0+7=7=target,记录组合[7]

七、同类问题拓展与解题模板

7.1 组合问题通用解题模板

List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
void backtrack(int[] nums, int target, int start) {if (target == 0) {res.add(new ArrayList<>(path));return;}if (target < 0) return;for (int i = start; i < nums.length; i++) {// 剪枝条件(根据题目调整)if (nums[i] > target) break;path.add(nums[i]);backtrack(nums, target - nums[i], i); // 可重复选:i不变// backtrack(nums, target - nums[i], i+1); // 不可重复选:i+1path.remove(path.size() - 1);}
}

7.2 相关LeetCode题目拓展

  • 39.组合总和(本题,元素可重复)
  • 40.组合总和II(元素不可重复,含重复元素)
  • 216.组合总和III(选k个不同数字)
  • 377.组合总和IV(组合顺序不同算不同解)

八、常见误区与调试技巧

8.1 典型错误分析

  1. 重复组合问题

    • 错误原因:未控制start参数,导致[2,3][3,2]同时出现
    • 解决方案:确保递归时传递i而非i+1,并通过排序固定顺序
  2. 深拷贝遗漏

    • 错误现象:结果集所有组合最终都为空
    • 原因:直接存储temp引用,后续回溯修改了其内容

8.2 调试技巧

  1. 打印递归轨迹
    System.out.println("当前组合:" + temp + ", 和:" + sum + ", 起始位置:" + start);
    
  2. 可视化状态树
    • 使用递归深度和当前选择绘制树状图
    • 标记剪枝发生的节点位置

九、总结:回溯法解决组合问题的核心要素

  1. 状态定义

    • path记录当前组合,sum记录当前和
    • start参数控制组合唯一性
  2. 递归设计

    • 终止条件:和为target或超过target
    • 选择逻辑:当前层可选择的元素范围
  3. 优化策略

    • 排序+剪枝减少搜索空间
    • 深拷贝避免结果污染

掌握这三个核心要素,即可高效解决各类组合搜索问题,从基础的组合总和到复杂的排列组合问题均能迎刃而解。

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

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

相关文章

windows11安装编译QtMvvm

windows11安装编译QtMvvm 1 从github下载代码2 官方的Download/Installtion3 自行构建编译QtMvvm遇到的问题3.1 `qmake`问题执行命令报错原因分析qmake报错:找不到编译器 cl解决方案3.2 `make qmake_all`问题执行命令报错原因分析make命令未识别解决方案3.3 缺少`perl`问题执行…

unix/linux source 命令,其历史争议、兼容性、生态、未来展望

现在把目光投向unix/linux source命令的历史争议、兼容性、生态和未来展望,这能让我们更全面地理解一个技术点在更广阔的图景中所处的位置。 一、历史争议与设计权衡 虽然 source (或 .) 命令功能强大且不可或缺,但在其发展和使用过程中,也存在一些微妙的争议或设计上的权衡…

开发时如何通过Service暴露应用?ClusterIP、NodePort和LoadBalancer类型的使用场景分别是什么?

一、Service核心概念 Service通过标签选择器&#xff08;Label Selector&#xff09;关联Pod&#xff0c;为动态变化的Pod集合提供稳定的虚拟IP和DNS名称&#xff0c;主要解决&#xff1a; 服务发现负载均衡流量路由 二、Service类型详解 1. ClusterIP&#xff08;默认类型…

从线性代数到线性回归——机器学习视角

真正不懂数学就能理解机器学习其实是个神话。我认为&#xff0c;AI 在商业世界可以不懂数学甚至不懂编程也能应用&#xff0c;但对于技术人员来说&#xff0c;一些基础数学是必须的。本文收集了我认为理解学习本质所必需的数学基础&#xff0c;至少在概念层面要掌握。毕竟&…

华为IP(7)

端口隔离技术 产生的背景 1.以太交换网络中为了实现报文之间的二层隔离&#xff0c;用户通常将不同的端口加入不同的VLAN&#xff0c;实现二层广播域的隔离。 2.大型网络中&#xff0c;业务需求种类繁多&#xff0c;只通过VLAN实现二层隔离&#xff0c;会浪费有限的VLAN资源…

Docker Desktop无法在windows低版本进行安装

问题描述 因工作需要&#xff0c;现在一台低版本的window系统进行Docker Desktop的安装&#xff0c;但是安装过程当中出现了报错信息 系统版本配置 原因分析&#xff1a; 关于本机查看了系统的版本号&#xff0c;版本号如下为1909,但是docker Desktop要求的最低的win10版本…

深入理解 Maven 循环依赖问题及其解决方案

在 Java 开发领域&#xff0c;Maven 作为主流构建工具极大简化了依赖管理和项目构建。然而**循环依赖&#xff08;circular dependency&#xff09;**问题仍是常见挑战&#xff0c;轻则导致构建失败&#xff0c;重则引发类加载异常和系统架构混乱。 本文将从根源分析循环依赖的…

Git 全平台安装指南:从 Linux 到 Windows 的详细教程

目录 一、Git 简介 二、Linux 系统安装指南 1、CentOS/RHEL 系统安装 2、Ubuntu/Debian 系统安装 3、Windows 系统安装 四、安装后配置&#xff08;后面会详细讲解&#xff0c;现在了解即可&#xff09; 五、视频教程参考 一、Git 简介 Git 是一个开源的分布式版本控制系…

微服务-Sentinel

目录 背景 Sentinel使用 Sentinel控制台 Sentinel控制规则 Sentinel整合OpenFeign 背景 在微服务项目架构中&#xff0c;存在多个服务相互调用场景&#xff0c;在某些情况下某个微服务不可用时&#xff0c;上游调用者若一直等待&#xff0c;会产生资源的消耗&#xff0c;极端情…

智慧零工平台前端开发实战:从uni-app到跨平台应用

智慧零工平台前端开发实战:从uni-app到跨平台应用 本文将详细介绍我如何使用uni-app框架开发一个支持微信小程序和H5的零工平台前端应用,包含技术选型、架构设计、核心功能实现及部署经验。 前言 在当今移动互联网时代,跨平台开发已成为提高开发效率的重要手段。本次我选择…

Qt实现csv文件按行读取的方式

Qt实现csv文件按行读取的方式 场景:我有一个保存数据的csv文件,文件内保存的是按照行保存的数据,每行数据是以逗号为分隔符分割的文本数据。如下图所示: 现在,我需要按行把这些数据读取出来。 一、使用QTextStream文本流的方式读取 #include <QFile>void readfil…

day17 leetcode-hot100-34(链表13)

23. 合并 K 个升序链表 - 力扣&#xff08;LeetCode&#xff09; 1.数组排序 思路 &#xff08;1&#xff09;将全部的节点存储到数组中 &#xff08;2&#xff09;对数组进行排序 &#xff08;3&#xff09;最后创建一个全新的链表 具体代码 /*** Definition for singly…

docker运行程序Killed异常排查

问题描述 我最近开发了一个C 多线程程序&#xff0c;测试没有问题&#xff0c;封装docker测试也没有问题&#xff0c;然后提交给客户了&#xff0c;然后在他那边测试有问题&#xff0c;不定时、不定位置异常中断&#xff0c;以前一直认为只要封装了docker就万事大吉&#xff0…

爬虫的几种方式(使用什么技术来进行一个爬取数据)

在网页数据爬取中&#xff0c;确实存在多种数据呈现和获取形式&#xff0c;远不止静态HTML解析和简单JS渲染。理解这些形式对于应对不同的反爬机制至关重要&#xff1a; 主要数据获取形式与应对策略 纯静态HTML (基础形式) 特点&#xff1a; 数据直接嵌入在服务器返回的初始HT…

MyBatis-Plus高级用法:最优化持久层开发

MyBatis-Plus 是 MyBatis 的增强工具&#xff0c;旨在简化开发、提高效率并保持 MyBatis 的灵活性。本文将详细介绍 MyBatis-Plus 的高级用法&#xff0c;帮助开发者最优化持久层开发。 一、MyBatis-Plus 简介 MyBatis-Plus 是一个 ORM 框架&#xff0c;提供了 CRUD 接口、条…

【C++/Linux】TinyWebServer前置知识之IP协议详解

目录 IPv4地址 分类 IP数据报分片 IP 协议在传输数据报时&#xff0c;将数据报分为若干分片&#xff08;小数据报&#xff09;后进行传输&#xff0c;并在目的系统中进行重组&#xff0c;这一过程称为分片&#xff08;Fragmentation&#xff09;。 IP模块工作流程​编辑 I…

【办公类-22-05】20250601Python模拟点击鼠标上传CSDN12篇

、 背景需求: 每周为了获取流量券,每天上传2篇,获得1500流量券,每周共上传12篇,才能获得3000和500的券。之前我用UIBOT模拟上传12篇。 【办公类-22-04】20240418 UIBOT模拟上传每天两篇,获取流量券,并删除内容_csdn 每日任务流量券-CSDN博客文章浏览阅读863次,点赞18…

由浅入深一文详解同余原理

由浅入深一文详解同余原理 一、同余原理的基本概念1.1 同余的定义1.2 剩余类与完全剩余系 二、同余原理的基本性质2.1 自反性2.2 对称性2.3 传递性2.4 加减性2.5 乘性2.6 幂性 三、同余原理的运算与应用3.1 同余运算在计算中的应用3.2 密码学中的应用3.3 日期与周期问题 四、案…

ArcGIS Pro 创建渔网格网过大,只有几个格网的解决方案

之前用ArcGIS Pro创建渔网的时候&#xff0c;发现创建出来格网过大&#xff0c;只有几个格网。 后来查阅资料&#xff0c;发现是坐标不对&#xff0c;导致设置格网大小时单位为度&#xff0c;而不是米&#xff0c;因此需要进行坐标系转换&#xff0c;网上有很多资料讲了ArcGIS …

【MFC】初识MFC

目录 01 模态和非模态对话框 02 静态文本 static text 01 模态和非模态对话框 首先我们需要知道模态对话框和非模态对话框的区别&#xff1a; 模态对话框是一种阻塞时对话框&#xff0c;它会阻止用户与应用程序的其他部分进行交互&#xff0c;直到用户与该对话框进行交互并关…