当前位置: 首页 > news >正文

LeetCode算法日记 - Day 58: 目标和、数组总和

目录

1. 目标和

1.1 题目解析

1.2 解法

1.3 代码实现

2. 数组总和

2.1 题目解析

2.2 解法

2.3 代码实现


1. 目标和

https://leetcode.cn/problems/target-sum/

给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

1.1 题目解析

题目本质
对每个元素二选一(加号/减号),本质是二叉决策树上的全路径计数问题:从左到右依次决定符号,统计最终累加和等于 target 的路径条数。

常规解法
直接 DFS/回溯:在位置 pos 同时尝试 +nums[pos] 和 -nums[pos],直到用完所有元素,命中目标就计数。

问题分析
决策树有 2^n 条路径,时间复杂度 O(2^n),空间为递归深度 O(n)。在 n<=20 的题目规模下可接受,但存在大量重复子问题,若数据更大或多测试会显得慢。

思路转折
若要进一步稳定到多组数据,可改成等价的 0/1 背包计数(子集和),这里只点到为止。

1.2 解法

算法思想
• 定义递归 dfs(pos, curSum):处理到下标 pos,当前累加和为 curSum。
• 分支:进入 dfs(pos+1, curSum + nums[pos]) 与 dfs(pos+1, curSum - nums[pos])。
• 终止:当 pos==len,若 curSum==target,答案计数 +1。
• 最终答案为根结点到叶子的“命中”路径条数。

i)预处理并保存 len 与 target(代码里用 t)。

ii)从 pos=0, curSum=0 调用 dfs。

iii)在 dfs 中:

  • 若 pos==len,判断是否命中,命中则 result++;返回。

  • 否则递归两条分支:+nums[pos] 与 -nums[pos]。

iv)递归结束后返回全局 result。

易错点

  • 递归基与返回:pos==len 时务必只判断一次并立即返回,避免重复计数。

  • int 安全性:本题和的范围 [−1000,1000][-1000,1000][−1000,1000],int 足够;若拓展到大数,可考虑 long

1.3 代码实现

class Solution {int result;  // 记录命中 target 的表达式数量int len;     // 数组长度int t;       // 目标 targetpublic int findTargetSumWays(int[] nums, int target) {this.t = target;this.len = nums.length;result = 0;dfs(nums, 0, 0);return result;}// 回溯:在位置 pos 选择 +nums[pos] 或 -nums[pos]private void dfs(int[] nums, int pos, int curSum) {if (pos == len) {if (curSum == t) result++;return;}dfs(nums, pos + 1, curSum + nums[pos]); // 选择 +dfs(nums, pos + 1, curSum - nums[pos]); // 选择 -}
}

复杂度分析

  • 时间复杂度:最坏 O(2^n),每个元素都有加/减两种选择。

  • 空间复杂度:递归栈深度 O(n)。

2. 数组总和

https://leetcode.cn/problems/combination-sum/description/

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

2.1 题目解析

题目本质
在“元素可重复取、组合无序”的前提下,寻找所有和为 target 的组合。这是一个组合型回溯问题:用起始下标 pos 控制只向右选,天然去重。

常规解法
直接回溯
从 pos 开始循环,选入 candidates[i] 后递归到下一层;因为允许重复使用,下一层仍从 i 开始;当累加和到达 target 即收集路径。

问题分析
朴素回溯会产生大量无效分支(当前和已超过 target 仍在探索),复杂度指数级,虽然题目保证解的数量 < 150,但无剪枝时仍浪费显著搜索。

思路转折
在有写法的框架内(ret/path/t + dfs(nums, pos, cur) 不变)做两点小优化即可显著提速:
i)排序 + 单行剪枝:先对 candidates 升序;在循环中一旦 cur + nums[i] > t,因后续更大,直接 break,整层提前结束。。
ii)起始下标去重:保持“下一层仍从 i 开始”(不是 i+1),既支持重复取数,又自然避免 [2,3,2] 这类排列重复。

2.2 解法

解法

算法思想
• 定义 dfs(pos, cur):在区间 [pos..n) 内继续选,使当前和为 cur。
• 若 cur == t:把 path 复制进 ret,返回一条解。
• 枚举 i 从 pos 到 n-1:
 — 若 cur + nums[i] > t 且数组已升序,直接 break;
 — 选入 nums[i],递归 dfs(i, cur + nums[i])(允许重复使用);回溯弹出。

i)将 candidates 升序排序。

ii)初始化全局 ret(答案集合)、path(当前路径)、t(目标)。

iii)从 dfs(0, 0) 启动回溯。

iv)在 dfs 中命中 cur == t 时收集并返回;否则循环枚举:做“超目标即 break”剪枝;选入→递归→回溯。

v)返回 ret。

易错点

  • 剪枝一定配合排序:只有升序时 cur + nums[i] > t 才能用 break(不是 continue)。

  • 重复使用与去重:允许重复使用元素,因此下一层起点是 i 而不是 i+1

  • 命中即返回:cur == t 立即收集并返回,避免无用分支。

  • 回溯弹栈:递归后必须 remove 最后加入的元素,防止污染其他分支。

2.3 代码实现

import java.util.*;class Solution {List<List<Integer>> ret;List<Integer> path;int t;public List<List<Integer>> combinationSum(int[] c, int _t) {ret = new ArrayList<>();path = new ArrayList<>();t = _t;Arrays.sort(c); // 升序:启用“超目标即剪枝”的 breakdfs(c, 0, 0);return ret;}// 回溯:从下标 pos 开始选择,当前和为 curpublic void dfs(int[] nums, int pos, int cur) {if (cur == t) {                 // 命中目标,收集一份解ret.add(new ArrayList<>(path));return;}for (int i = pos; i < nums.length; i++) {int v = nums[i];if (cur + v > t) break;     // 关键剪枝:已排序,后面的更大,直接停止本层循环path.add(v);dfs(nums, i, cur + v);      // 可重复使用同一元素,因此下一层仍从 i 开始path.remove(path.size() - 1); // 回溯}}
}

复杂度分析

  • 时间复杂度:由空间决定的指数级,但“排序 + break 剪枝”能大幅减少无效分支,在“解数 < 150”的约束下运行稳定。

  • 空间复杂度:递归深度上界约为 target / min(candidates),路径与调用栈合计 O(深度)。

http://www.dtcms.com/a/426633.html

相关文章:

  • 在不同开发语言与场景下设计模式的使用
  • 服务机构电子商务网站有哪些软件外包公司开发流程
  • 微软 2025 年 8 月更新:对固态硬盘与电脑功能有哪些潜在的影响
  • VB6 ADO没有轻量级内存数据库吗?类似SQLITE
  • 微软Windows原罪不可原谅
  • 微软警示AI驱动的钓鱼攻击:LLM生成的SVG文件绕过邮件安全检测
  • 使用Java将Excel转换为Text
  • 智源 RoboBrain-X0 开源,打破机器人跨本体泛化困境
  • ITK-基于欧拉变换与质心对齐的二维刚性配准算法
  • 2025-2031年全球箱体与盒体搬运机器人行业全景报告(含市场规模、竞争格局及投资潜力)
  • 苍穹外卖项目面试总结话术
  • 【3D图像技术讨论】3A游戏场景重建实战指南:从数据采集到实时渲染的开源方案
  • Kanass入门到实战(6) - 如何进行缺陷管理
  • 湛江建网站网页界面设计内容
  • 打印设备T型非晶磁环——高频抗干扰的核心元件|深圳维爱普
  • pg_resetwal 使用简介
  • Spring Boot 集成 Redis 缓存解决方案
  • 微服务核心组件解析:注册中心与负载均衡(Eureka/Nacos/Ribbon)
  • GNS3环境下静态路由配置实例与分析(管理距离、度量值)
  • 充值网站建设建设银行 公户 该网站使用过期的
  • 【VMware】虚拟机软件安装报硬盘不够,扩容未生效解决办法
  • LSTM的一个计算例子
  • javaEE 网络原理(TCP UDP)
  • 惠阳住房和建设局网站自学做网站
  • 中国能源建设集团招聘网站网站建设哪家好知道万维科技
  • 智慧寄件新体验:快递小程序如何简化日常生活
  • 小程序原生导航栏返回键实现
  • 基于开源AI智能名片的S2B2C商城小程序中搜索联想功能的优化策略研究
  • 精读C++20设计模式——行为型设计模式:迭代器模式
  • 短剧小程序系统开发:构建便捷高效的影视观看平台