算法日记day 24(回溯之组合问题)

一、组合总和3

题目:

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

思路:

这里有两个限制条件,第一个只能找k个数,第二个这k个数相加必须等于n,因此我们可以在遍历一个数时同时存入一个数组中,同时计算他的和,如果等于n的值则返回所有数,这里需注意,有些情况并不需要进行判断,例如n=3的情况,这时我们遍历4之后的数字就将没有任何意义了,所以当遍历的值本身就大于n时就不需要再遍历了

 

代码:

class Solution {// 结果集,存储所有符合条件的组合List<List<Integer>> result = new ArrayList<>();// 当前路径,用于存储正在构建的组合LinkedList<Integer> path = new LinkedList<>();// 主方法,返回所有的组合public List<List<Integer>> combinationSum3(int k, int n) {// 调用递归方法开始求解combination(k, n, 1, 0);// 返回最终的结果集return result;}// 递归方法,用于生成组合public void combination(int k, int n, int startIndex, int sum) {// 剪枝,如果当前的和超过了目标值n,直接返回if (sum > n)return;// 如果当前路径的长度等于k,并且当前和等于n,找到一个有效组合,加入结果集if (path.size() == k) {if (sum == n)result.add(new ArrayList<>(path));return;}// 从startIndex开始尝试每个数字,确保不重复选择已经选过的数字for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {path.add(i); // 将当前数字i加入路径中sum += i; // 更新当前的和// 递归调用,继续向下选择下一个数字,注意startIndex设置为i + 1,避免重复选择当前数字icombination(k, n, i + 1, sum);path.removeLast(); // 回溯,移除路径中的最后一个数字,尝试下一个可能的数字sum -= i; // 回溯,更新当前的和,恢复到之前的状态}}
}
  1. 变量说明

    • result 是用来存储所有符合条件的组合的列表。
    • path 是一个 LinkedList<Integer>,用来存储当前正在构建的组合。
  2. 方法 combinationSum3

    • 在该方法中,调用 combination(k, n, 1, 0),从数字 1 开始尝试生成符合要求的组合。
  3. 方法 combination

    • 它接受四个参数:

      • k:还需要选择的数字个数。
      • n:目标和。
      • startIndex:当前可选择的起始数字。
      • sum:当前已选数字的和。
    • 方法的第一部分是两个基本的终止条件:

      • if (sum > n):如果当前已选数字的和超过了目标和 n,则直接返回,因为当前路径不符合条件。
      • if (path.size() == k):如果 path 中的数字个数达到了 k,则检查当前和 sum 是否等于 n。如果是,则将 path 的内容加入 result 中,表示找到了一个符合要求的组合。
    • 如果上述两个终止条件都不满足,说明需要继续选择数字来构建组合。这时候会进入 for 循环:

      • for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++):这个循环从 startIndex 开始,到 9 - (k - path.size()) + 1 结束。这个范围是为了确保即使选择了当前可能的最大值,也不会超过剩余需要选择的数目。
    • 在循环体内部:

      • path.add(i):将当前选择的数字 i 添加到 path 中。
      • sum += i:更新当前的和。
      • combination(k, n, i + 1, sum):递归调用 combination 方法,继续选择下一个数字。递归调用的参数中 startIndex 设置为 i + 1,确保不重复使用相同的数字。
      • path.removeLast():回溯,将刚刚添加的数字 i 移除,准备尝试下一个数字。
      • sum -= i:回溯,将之前添加的数字 i 从当前的和中减去,恢复到之前的状态。

二、电话号码的字母组合 

题目:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

思路:

首先需要定义一个字符串对应电话中每一个数字所对应的字母,用一个String类型的字符串保存所需的第几个数字所对应的字母,遍历当前字母,然后递归处理下一个数字,直到所有情况遍历完毕后,返回到一个结果数组中。

代码:

List<String> list = new ArrayList<>(); // 用于存储所有生成的字母组合的列表public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) {return list; // 如果输入为空,则直接返回空列表}// 数字键对应的字母字符串数组String[] numString = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };// 调用回溯方法开始生成所有可能的组合backTracking(digits, numString, 0);return list; // 返回生成的所有字母组合列表
}StringBuilder sb = new StringBuilder(); // 用于构建当前正在生成的字母组合的字符串public void backTracking(String digits, String[] numString, int num) {// 如果当前处理的数字索引等于输入数字串的长度,说明已经生成了一个有效组合if (num == digits.length()) {list.add(sb.toString()); // 将当前的StringBuilder内容转换为String并加入到结果列表中return; // 返回结束当前递归分支}// 获取当前数字对应的字母字符串String str = numString[digits.charAt(num) - '0'];// 遍历当前数字对应的所有可能字母for (int i = 0; i < str.length(); i++) {sb.append(str.charAt(i)); // 将当前字母加入StringBuilder中backTracking(digits, numString, num + 1); // 递归处理下一个数字sb.deleteCharAt(sb.length() - 1); // 回溯,删除最后一个字符,准备处理下一个可能的字母}
}
  • list 是用来存储所有生成的字母组合的列表。
  • sb 是一个用于构建当前正在生成的字母组合的字符串。
  • 如果 digits 为 null 或者空字符串,则直接返回空列表 list
  • numString 是一个数组,用于存储每个数字键对应的可能字母的字符串。例如,数字 2 对应 "abc",数字 3 对应 "def",依此类推。
  • 调用 backTracking 方法开始生成所有可能的组合。
  • num 参数表示当前处理的 digits 的索引。
  • 如果 num 等于 digits.length(),说明已经构建完成一个有效的组合,将当前 sb 中的内容转换为字符串并加入 list 中。
  • 获取当前数字对应的字母字符串 str,例如,如果 digits.charAt(num) 是 '2',则 str 是 "abc"
  • 使用 for 循环遍历 str 中的每个字符,将其加入 sb 中,然后递归调用 backTracking 处理下一个数字。
  • 在递归调用之后,需要回溯,即删除 sb 的最后一个字符,以便进行下一次循环。

 

三、组合总和 

题目:

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

与组合总和3的区别是,该题中有可以重复组合的元素 

代码:

List<List<Integer>> result = new ArrayList<>(); // 存储所有满足条件的组合的列表
LinkedList<Integer> path = new LinkedList<>(); // 当前正在生成的一个组合public List<List<Integer>> combinationSum(int[] candidates, int target) {Arrays.sort(candidates); // 对candidates数组进行排序,方便后续处理backTracking(result, path, candidates, target, 0, 0); // 调用回溯算法开始生成组合return result; // 返回所有满足条件的组合列表
}public void backTracking(List<List<Integer>> result, LinkedList<Integer> path, int[] candidates, int target,int sum, int idx) {if (sum == target) { // 如果当前组合的和等于目标值result.add(new ArrayList<>(path)); // 将当前组合加入结果列表中return; // 结束当前分支的递归}for (int i = idx; i < candidates.length; i++) { // 遍历candidates数组,从当前索引idx开始if (sum + candidates[i] > target)break; // 如果加上当前元素后超过目标值,则跳出循环(因为数组已经排序)path.add(candidates[i]); // 将当前元素加入当前组合中backTracking(result, path, candidates, target, sum + candidates[i], i); // 递归调用,继续向下生成组合path.removeLast(); // 回溯,移除最后一个元素,准备生成下一个可能的组合//path.remove(path.size() - 1);}
}
  • result 是存储所有满足条件的组合的列表。
  • path 是一个链表,用于存储当前正在生成的一个组合。
  • combinationSum 方法首先对 candidates 数组进行排序,然后调用 backTracking 方法开始生成组合。
  • backTracking 方法是一个递归函数,用于生成所有可能的组合,其参数包括 result(结果列表)、path(当前正在生成的组合)、candidates(候选数组)、target(目标值)、sum(当前组合的和)、idx(当前处理的候选元素索引)。
  • 在 backTracking 方法中,如果当前组合的和等于目标值 target,则将当前 path 中的元素加入 result 中。
  • 然后使用循环遍历 candidates 数组,从索引 idx 开始,依次尝试加入数组中的元素到 path 中,如果加入后组合的和超过了 target,则跳出循环,因为数组已经排序。
  • 如果未超过 target,则递归调用 backTracking 方法,继续生成下一个可能的组合。
  • 每次递归完成后,执行 path.removeLast() 进行回溯,移除最后一个元素,以便尝试下一个可能的组合。

 

四、组合总和2 

题目:

给定一个候选人编号的集合 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]]

 

思路:

与前两种组合总和问题不同,本题中传入的数据有重复元素,在遍历过程中可能会出现重复的情况,因此该题的重点在于如何去重。

代码:

List<List<Integer>> result = new ArrayList<>(); // 存储所有满足条件的唯一组合的列表
LinkedList<Integer> path = new LinkedList<>(); // 当前正在生成的一个组合
boolean[] used; // 记录每个元素是否被使用过的标记数组public List<List<Integer>> combinationSum2(int[] candidates, int target) {used = new boolean[candidates.length]; // 初始化used数组Arrays.fill(used, false);  //默认都为falseArrays.sort(candidates); // 对candidates数组进行排序,方便后续处理backTracking(candidates, target, 0, 0); // 调用回溯算法开始生成组合return result; // 返回所有满足条件的唯一组合列表
}public void backTracking(int[] candidates, int target, int sum, int startIndex) {if (sum == target) { // 如果当前组合的和等于目标值result.add(new ArrayList<>(path)); // 将当前组合加入结果列表中return; // 结束当前分支的递归}for (int i = startIndex; i < candidates.length; i++) { // 遍历candidates数组,从当前索引startIndex开始if (sum + candidates[i] > target)break; // 如果加上当前元素后超过目标值,则跳出循环(因为数组已经排序)if (i > startIndex && candidates[i] == candidates[i - 1] && !used[i - 1]) {continue; // 跳过重复的元素,确保每个元素只使用一次}used[i] = true; // 标记当前元素已被使用sum += candidates[i]; // 更新当前组合的和path.add(candidates[i]); // 将当前元素加入当前组合中backTracking(candidates, target, sum, i + 1); // 递归调用,继续向下生成组合used[i] = false; // 回溯,标记当前元素未使用sum -= candidates[i]; // 回溯,恢复当前组合的和path.removeLast(); // 回溯,移除最后一个元素,准备生成下一个可能的组合}
}
  • result 是存储所有满足条件的唯一组合的列表。
  • path 是一个链表,用于存储当前正在生成的一个组合。
  • used 是一个布尔数组,用于标记 candidates 数组中的元素是否被使用过。
  • combinationSum2 方法首先初始化 used 数组,并对 candidates 数组进行排序,然后调用 backTracking 方法开始生成组合。
  • backTracking 方法是一个递归函数,用于生成所有可能的组合,其参数包括 candidates(候选数组)、target(目标值)、sum(当前组合的和)、startIndex(当前处理的候选元素索引)。
  • 在 backTracking 方法中,如果当前组合的和等于目标值 target,则将当前 path 中的元素加入 result 中。
  • 然后使用循环遍历 candidates 数组,从索引 startIndex 开始,依次尝试加入数组中的元素到 path 中。
  • 在循环内部,通过判断 used 数组和当前元素是否与上一个相同的方式,确保每个元素只被使用一次,避免生成重复的组合。
  • 如果未超过 target,则递归调用 backTracking 方法,继续生成下一个可能的组合。
  • 每次递归完成后,执行相应的回溯操作,包括标记当前元素未使用、恢复当前组合的和以及移除最后一个元素,以便尝试下一个可能的组合。

其中的去重代码为:

if (i > startIndex && candidates[i] == candidates[i - 1] && !used[i - 1]) {continue; // 跳过重复的元素,确保每个元素只使用一次
}

 这里的具体解释为:

  • i > startIndex: 这个条件确保我们在处理候选数组时,只考虑从当前位置 startIndex 开始的元素。这是因为数组是经过排序的,如果一个元素与之前的元素相同,我们只需要考虑第一个出现的元素及其后续出现的。

  • candidates[i] == candidates[i - 1]: 这个条件检查当前元素 candidates[i] 是否与前一个元素 candidates[i - 1] 相同。这是为了避免重复的组合。在排序后的数组中,如果当前元素与前一个元素相同,并且前一个元素 used[i - 1] 已经被使用过(即为 true),说明我们已经考虑过将前一个元素加入组合了,所以当前元素不应该再被考虑。

  • !used[i - 1]: 这个条件确保前一个相同的元素 candidates[i - 1] 没有被使用过。如果前一个相同元素已经被使用过,即 used[i - 1]true,说明我们已经在之前的组合中考虑过这个元素了,因此不应该再考虑当前元素 candidates[i],以避免重复。

如果上述条件都满足,continue; 语句将跳过当前循环中的剩余部分,直接进入下一个循环迭代。这样做可以确保在生成组合时,每个元素只使用一次,从而保证了生成的组合是唯一的且不重复的。

今天的学习就到这里了 

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

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

相关文章

其他:trycatch模块捕获循环错误,避免中断程序

介绍 今天有位同事问我怎么在某次循环报错后仍然可以继续程序运行&#xff0c;最后我们使用了trycatch模块。 代码解读 任务&#xff1a;在循环中&#xff0c;如果某次循环的calc出现错误则跳过这次循环并重新赋值结果 res_list <- list() # 创建一个空列表来存储结果fo…

pdf压缩文件怎么压缩最小?8款实用PDF压缩软件,你值得拥有(2024)

pdf压缩文件怎么压缩最小&#xff1f;如今&#xff0c;无论在我们日常工作还是在日常学习中&#xff0c;pdf文件都无处不在。因此&#xff0c;如何压缩pdf文件&#xff0c;将其大小降至最小&#xff0c;既保证质量又使文件更易管理成为许多人迫切关心的问题。在本文中&#xff…

php 一个极简的类例子

https://andi.cn/page/621627.html

智慧矿山,安全先行:矿山风险预警视频智能监控系统的应用解析

随着科技的飞速发展&#xff0c;矿山行业作为国民经济的重要支柱之一&#xff0c;其安全生产问题日益受到社会各界的广泛关注。为了有效降低矿山作业中的风险&#xff0c;提升安全管理水平&#xff0c;矿山风险预警视频智能监控系统应运而生。该系统集成了高清视频监控、人工智…

工具(1)—截屏和贴图工具snipaste

演示和写代码文档的时候&#xff0c;总是需要用到截图。在之前的流程里面&#xff0c;一般是打开WX或者QQ&#xff0c;找到截图工具。但是尴尬的是&#xff0c;有时候&#xff0c;微信没登录&#xff0c;而你这个时候就在写文档。为了截个图&#xff0c;还需要启动微信&#xf…

OnlyOffice在线部署

部署服务环境&#xff1a;Centos7.6 curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash 安装yum-utils工具 yum install yum-utils 添加nginx.repo源(Nginx官网有最新版&#xff0c;直接copy即可) vim /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx st…

ElasticSearch父子索引实战

关于父子索引 ES底层是Lucene,由于Lucene实际上是不支持嵌套类型的,所有文档都是以扁平的结构存储在Lucene中,ES对父子文档的支持,实际上也是采取了一种投机取巧的方式实现的. 父子文档均以独立的文档存入,然后添加关联关系,且父子文档必须在同一分片,由于父子类型文档并没有…

木马后门实验

实验拓扑 实验步骤 防火墙 配置防火墙—充当边界NAT路由器 边界防火墙实现内部 DHCP 分配和边界NAT需求&#xff0c;其配置如下 登录网页 编辑接口 配置e0/0 配置e0/1 编辑策略 测试&#xff1a;内部主机能获得IP&#xff0c;且能与外部kali通信 kali 接下来开启 kali 虚…

Teamcenter用本地胖客户端启动时,可以看到插件的菜单项,但是用Eclipse启动时看不到

用本地胖客户端启动时&#xff0c;可以看到定制包的插件菜单项&#xff0c;但是用Eclipse启动时&#xff0c;看不到&#xff1f; 原因&#xff1a; 是因为Eclipse启动下&#xff0c;是采用 JAVA1.8 来运行的。但是本机的胖客户端是采用JAVA 11来运行的 解决办法&#xff1a;…

表现力丰富的肖像动画框架;结合本地LLM和GraphRAG的多代理RAG超级机器人;支持向量和图路径查询RAG;

✨ 1: Follow-Your-Emoji Follow-Your-Emoji 是一个基于扩散模型的精细控制与表现力丰富的肖像动画框架。 Follow-Your-Emoji 是一种基于扩散模型的肖像动画生成框架&#xff0c;可以通过目标标志序列动画化参考肖像。其核心优势在于能够实现精细可控和富有表现力的自由风格肖…

快速幂的求解方法(位运算)

需要求解幂运算的解法&#xff0c;可以将需要运算的内容进行判别&#xff0c;众所周知&#xff0c;幂就是指数&#xff0c;就是将底数乘以自身完成n次自相乘&#xff0c;那么就可以幻化为他的幂的简化计算&#xff1b; 以二进制为例&#xff0c;你要求&#xff0c;即可以看作是…

[CISCN2019 华北赛区 Day1 Web5]CyberPunk 1

目录 题目分析功能点分析伪协议读取源码search.phpchange.phpdelete.phpconfirm.php 代码分析 解法一解法二 题目分析 功能点分析 看到查询界面&#xff0c;第一时间想到了xss&#xff0c;经过测试存在xss&#xff0c;但没用 然后想到了sql注入&#xff0c;注册的时候在地址的…

nginx实战与负载均衡

一、nginx实战 1、nginx 反向代理配置 &#xff08;1&#xff09;概述 反向代理&#xff1a;⽤户直接访问反向代理服务器就可以获得⽬标服务器&#xff08;后端服务器&#xff09;的资源。 &#xff08;2&#xff09;修改配置 [rootserver2 ~]# vim /usr/local/nginx/conf/ng…

学习日记:排序

目录 1.选择排序 2.冒泡排序 3.插入排序 4.查找 4.1 二分查找 1.选择排序 思想&#xff1a;给合适的位置选择合适的数&#xff08;用后面的数依次跟指定位置上的数比较&#xff0c;如果后面的书比指定位置的数小&#xff0c;就交换两个数&#xff0c;依次重复&#xff0c;一…

API 接口自动化测试的基本原理及实战教程

常用API接口协议介绍 HTTP协议 超文本传输协议 它是用来在Internet上传送超文本的传送协议&#xff0c;运行在TCP/IP协议族之上&#xff0c;它可以使浏览器更加高效&#xff0c;使网络传输减少。 任何服务器除了包括HTML文件以外&#xff0c;还有一个HTTP驻留程序&#xf…

(day28)leecode——有效括号

描述 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))","(()())","(())()","()(())","…

【Unity动画】Animation Sequencer:动画制作的革新工具

在Unity游戏开发中&#xff0c;动画是提升玩家体验的关键因素。传统的动画制作方式往往耗时且复杂&#xff0c;但有了Animation Sequencer&#xff0c;这一过程将变得更加直观和高效。本文将介绍Animation Sequencer这一视觉工具&#xff0c;探讨其如何帮助开发者在Unity编辑器…

案例分享-国外轻松感UI设计赏析

国外UI设计倾向于采用简洁的布局、清晰的排版和直观的交互方式&#xff0c;减少用户的认知负担&#xff0c;从而营造出轻松的使用体验。这种设计风格让用户能够快速找到所需信息&#xff0c;降低操作难度&#xff0c;提升整体满意度。 在注重美观的同时&#xff0c;更加重视用户…

技术详解:互联网医院系统源码与医保购药APP的整合开发策略

本篇文章&#xff0c;小编将从系统架构、数据安全、用户体验和技术实现等方面详细探讨互联网医院系统与医保购药APP的整合开发策略。 一、系统架构 1.模块化设计 互联网医院系统与医保购药APP的整合需要采用模块化设计。 2.微服务架构 每个功能模块作为一个独立的微服务&am…

成为git砖家(9): git checkout <commit> <file> 的含义

文章目录 1. 目的2. 官方文档解释3. Tower 的解释4. References 1. 目的 git checkout 命令承载了非常多的功能&#xff0c; 想要一次全弄懂&#xff0c;不太现实&#xff1b; 这次白鱼带领大家学习 git checkout <file> 的用法。 老规矩&#xff0c;先查看 git checko…