40. 组合总和 II + 递归 + 回溯 + 记录路径
40. 组合总和 II
LeetCode_40
题目描述
题解分析
- 此题和 39. 组合总和 + 递归 + 回溯 + 存储路径很像,只不过题目修改了一下。
- 题解的关键是首先将候选数组进行排序,然后记录每个数的出现次数。
- 将去重后的数组当成是新的候选数组进行递归搜索。
- 回溯的时候注意是在最后将相同数字次数的数从列表中清除。
解法一
package com.walegarrett.interview;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author WaleGarrett
* @Date 2021/2/27 17:53
*/
/**
* 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
* candidates 中的每个数字在每个组合中只能使用一次。
*/
/**
* 解法:回溯法
*/
public class LeetCode_40 {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
List<int[]> map = new ArrayList<>();
Arrays.sort(candidates);
for(int num : candidates){
if(map.isEmpty() || num != map.get(map.size()-1)[0])
map.add(new int[]{num, 1});
else{
++map.get(map.size()-1)[1];
}
}
dfs(target, 0, list, result, map);
return result;
}
public void dfs(int target, int index, List<Integer> path, List<List<Integer>> result, List<int[]> map){
//找到一条路径
if(target == 0){
//注意:这里不能直接result.add(path),因为path是在回溯中会改变的,这样只存储了list的地址,地址是不变的。
result.add(new ArrayList<>(path));
return;
}
if(index == map.size() || target < map.get(index)[0])
return;
//跳过当前数
dfs(target, index+1, path, result, map);
//不跳过当前数
int ans = Math.min(map.get(index)[1], target/map.get(index)[0]);
for(int i=1; i<=ans; i++){
path.add(map.get(index)[0]);
dfs(target- i*map.get(index)[0], index+1, path, result, map);
}
for(int i=1; i<=ans; i++){
path.remove(path.size()-1);
}
}
}
解法二:回溯法 + 剪枝
class Solution {
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// 对数组进行排序
Arrays.sort(candidates);
dfs(candidates, 0, 0, target, new LinkedList<Integer>());
return result;
}
void dfs(int[] candidates, int pos, int sum, int target, LinkedList<Integer> path){
if(sum > target || pos > candidates.length){
return;
}
if(sum == target){
LinkedList<Integer> list = new LinkedList<>(path);
result.add(list);
return;
}
for(int i=pos; i<candidates.length; i++){
// 剪枝逻辑,值相同的树枝,只遍历第一条
if(i > pos && candidates[i] == candidates[i-1]){
continue;
}
int now = candidates[i];
path.add(now);
// 遍历下一层
dfs(candidates, i+1, sum + now, target, path);
path.remove(path.size()-1);
}
}
}
Either Excellent or Rusty
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了