回溯算法08-组合总数II(Java/组合去重的两种方法)

8.组合总数II

  • 题目描述

给定一个候选人编号的集合 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]
]
  • 题目分析
相较于之前的组合总数问题,本题会出现重复组合的问题,比如说candidates=[1,2,7,6,1,5] target = 8时
以1为第一个元素的时候,组合中会出现满足条件的[125]2为第一个元素的时候,组合中会出现满足条件的[2, 1, 5]
不难发现这两个是重复的,因此需要去重。

为什么会出现这种情况?

这是因为当第一个元素取1的时候就已经包含了[1,2,5]这组集合元素,再2为第一个元素时就会导致重复,而且我们发现当且只有在同一数层之上会出现这一种情况,那么如何避免呢?这里可以通过标记的方法解决这一问题
创建used元素用于记录元素的使用情况
i > 0 && candidates[i] == candidates[i - 1] && used[i] == 1 && used[i - 1] == 0
i > 0: 确保当前遍历的数字不是数组的第一个元素,因为第一个元素无法和前一个元素比较。
candidates[i] == candidates[i - 1]: 检查当前数字是否和前一个数字相同,如果相同则表示存在重复的元素。
used[i] == 1 && used[i - 1] == 0: 确保当前数字已经被使用过,而前一个相同的数字没有被使用过。这个条件是为了避免重复的组合。
综合起来,这个条件的含义是:如果当前数字和前一个数字相同,并且当前数字已经被使用,而前一个数字没有被使用,则应该跳过当前数字,以避免生成重复的组合。

image-20240309205454619

  • Java代码实现
LinkedList<Integer> path = new LinkedList<>(); // 用于存储当前组合的路径
List<List<Integer>> result = new ArrayList<>(); // 用于存储最终结果的列表/*** 寻找给定候选数组中和为目标值的所有不重复组合* @param candidates 候选数组* @param target 目标值* @return 所有符合条件的组合列表*/
public List<List<Integer>> combinationSum2(int[] candidates, int target) {int[] used = new int[candidates.length]; // 用于标记候选数组中数字是否被使用过Arrays.sort(candidates); // 对候选数组进行排序backtrack(candidates, used, 0, target, 0); // 调用回溯函数开始搜索return result; // 返回最终结果
}/*** 回溯函数,用于搜索符合条件的组合* @param candidates 候选数组* @param used 标记数组* @param startIndex 当前搜索的起始位置* @param target 目标值* @param sum 当前累计和*/
private void backtrack(int[] candidates, int[] used, int startIndex, int target, int sum) {if (sum > target) return; // 如果当前累计和已经超过目标值,则返回if (sum == target) { // 如果当前累计和等于目标值result.add(new ArrayList<>(path)); // 将当前路径加入最终结果return;}for (int i = startIndex; i < candidates.length; i++) {used[i] = 1; // 标记当前数字为已使用if (i > 0 && candidates[i] == candidates[i - 1] && used[i] == 1 && used[i - 1] == 0) {used[i] = 0; // 如果出现重复数字且没有按顺序使用,则跳过当前数字continue;}path.add(candidates[i]); // 将当前数字加入路径sum += candidates[i]; // 更新累计和backtrack(candidates, used, i + 1, target, sum); // 递归调用下一层搜索used[i] = 0; // 恢复当前数字为未使用path.removeLast(); // 移除当前数字sum -= candidates[i]; // 恢复累计和}
}
  • 不用used数组标记的做法
1.在 combinationSum2 方法中,去除了不必要的参数 used,因为在优化后的方法中并未使用到该参数。同时,对 startIndex 和 sum 也进行了调整,这两个参数被通过 path 和 result 的状态来推导,避免了传递过多参数。2.在 backtrack 方法中,剪枝操作的位置进行了调整。将重复元素的剪枝操作放在了循环体内的开头,这样可以在进入递归之前就剔除掉重复的情况,减少了不必要的递归次数,提高了效率。3.循环的起始位置进行了调整,并增加了对重复元素的判断。在每次循环时,如果当前元素与前一个元素相同,则直接跳过,避免了重复计算,进一步提高了效率。4.将 sum 的计算放在了循环体内,使得每次递归只需要计算当前元素的值,而不需要重复计算之前元素的值。

核心修改

if (i > startIndex && candidates[i] == candidates[i - 1]) {continue; // 如果当前数字和前一个数字相同,则跳过当前数字,避免重复组合}解释:在回溯过程中,候选数组已经经过排序,如果当前数字和前一个数字相同,那么意味着在上一层的递归中已经考虑过这个数字,为了避免生成相同的组合,我们需要跳过当前数字。
i > startIndex:这个条件确保我们只考虑从当前位置开始的数字,而不是之前已经被考虑过的数字。
candidates[i] == candidates[i - 1]:这个条件判断当前数字是否和前一个数字相同。
continue:如果当前数字和前一个数字相同,我们使用continue语句跳过当前数字的处理,直接进入下一次循环。
  • Java代码实现
LinkedList<Integer> path = new LinkedList<>(); // 用于存储当前组合的路径
List<List<Integer>> result = new ArrayList<>(); // 用于存储最终结果的列表/*** 寻找给定候选数组中和为目标值的所有不重复组合* @param candidates 候选数组* @param target 目标值* @return 所有符合条件的组合列表*/
public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates); // 对候选数组进行排序backtrack(candidates, 0, target); // 调用回溯函数开始搜索return result; // 返回最终结果
}/*** 回溯函数,用于搜索符合条件的组合* @param candidates 候选数组* @param startIndex 当前搜索的起始位置* @param target 目标值*/
private void backtrack(int[] candidates, int startIndex, int target) {if (target == 0) { // 如果目标值为0,表示当前组合符合条件result.add(new ArrayList<>(path)); // 将当前路径加入最终结果return;}for (int i = startIndex; i < candidates.length; i++) {if (i > startIndex && candidates[i] == candidates[i - 1]) {continue; // 如果当前数字和前一个数字相同,则跳过当前数字,避免重复组合}int num = candidates[i]; // 获取当前数字if (num > target) {break; // 如果当前数字已经大于目标值,结束循环}path.add(num); // 将当前数字加入路径backtrack(candidates, i + 1, target - num); // 递归调用下一层搜索path.removeLast(); // 移除当前数字}
}

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

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

相关文章

STM32标准库——(21)Flash闪存

1.简介 第一个用途&#xff0c;对于我们这个C8T6芯片来说&#xff0c;它的程序存储器容量是64K&#xff0c;一般我们写个简单的程序&#xff0c;可能就只占前面的很小一部分空间&#xff0c;剩下的大片空余空间我们就可以加以利用&#xff0c;比如存储一些我们自定义的数据&…

作业 字符数组-统计和加密

字串中数字个数 描述 输入一行字符&#xff0c;统计出其中数字字符的个数。 输入 一行字符串&#xff0c;总长度不超过255。 输出 输出为1行&#xff0c;输出字符串里面数字字符的个数。 样例 #include <iostream> #include<string.h> using namespace std; int m…

Solidity攻击合约:重入攻击与危害分析

以太坊智能合约开发中&#xff0c;重入攻击是一种常见的安全漏洞。这种攻击通常发生在合约的递归调用中&#xff0c;攻击者通过构造恶意交易&#xff0c;使得原本合约在执行过程中不断调用自身或其他合约&#xff0c;从而耗尽合约的Gas&#xff08;交易费用&#xff09;&#x…

qsort函数的用法及参数的讲解

第一种用法展示&#xff1a;&#xff08;整形数组的qsort&#xff09; 一&#xff0c;qsort函数的定义&#xff1a; qsort 函数的定义&#xff1a;void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*)); 使用其需要包含头文件&#x…

Apps分类:深度解析多维度标准与应用领域

引言 随着智能手机的广泛普及&#xff0c;移动应用已经成为我们日常生活中不可或缺的一部分。这个数字化时代&#xff0c;我们依赖于各种各样的应用&#xff0c;从社交媒体到生产力工具&#xff0c;再到娱乐和健康应用&#xff0c;移动应用为我们提供了方便、娱乐和信息获取的…

波奇学Linux: 信号捕捉

sigaction:修改信号对应的handler方法 act输入型参数&#xff0c;oldact输出型参数 void (*sa_handler) (int) //修改的自定义函数 sigset_t sa_mask // void handler(int signo) {cout<<"catch a signal, signal number: "<<signo<<endl; } int …

函数柯里化:JavaScript中的高级技巧

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CraxsRat7.4 安卓手机远程管理软件

CRAXSRAT 7.4 最新视频 https://v.douyin.com/iFjrw2aD/ 官方网站下载 http://craxsrat.cn/ 不要问我是谁&#xff0c;我是活雷锋。 http://craxsrat.cn/ CraxsRat CraxsRat7 CraxsRat7.1 CraxsRat7.2 CraxsRat7.3 CraxsRat7.4

【AI视野·今日NLP 自然语言处理论文速览 第八十四期】Thu, 7 Mar 2024

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 7 Mar 2024 Totally 52 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers The Heuristic Core: Understanding Subnetwork Generalization in Pretrained Language Models Authors Adith…

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读 Abstract1. Introduction2. Related Work3. Methodology3.1. Architecture3.1.1 Autoencoder3.1.2 Temporal Pseudo Anomaly Synthesizer 3.2. Training3.3. Anomaly Score 4. Experiments4.1.…

深度学习:如何面对隐私和安全方面的挑战

深度学习技术的广泛应用推动了人工智能的快速发展&#xff0c;但同时也引发了关于隐私和安全的深层次担忧。如何在保护用户隐私的同时实现高效的模型训练和推理&#xff0c;是深度学习领域亟待解决的问题。差分隐私、联邦学习等技术的出现&#xff0c;为这一挑战提供了可能的解…

Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344)(A~C)

A - Spoiler 竖线里面的不要输出&#xff0c;竖线只有一对&#xff0c;且出现一次。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(int (i)(j);(i)>(k);--(i)) #define debug(a) cou…