- Leetcode 39. 组合总和
- 题目
- 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
- candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
- 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
- 1 <= candidates.length <= 30
- 2 <= candidates[i] <= 40
- candidates 的所有元素 互不相同
- 1 <= target <= 40
- 解法
- 完全背包:标准完全背包问题,只是存储需要使用 List 数组,
- 定义状态(子问题):定义 dp[i][j] 为前 i 个元素能组成 j 的所有组合
- 状态转移方程:dp[i][j] = add dp[i-1][j](不添加),add dp[i-1][j-candidates[i]](上一轮后添加 i 元素),add dp[i][j-candidates[i]](本轮可添加多个 i 元素)
- 空间压缩:由于 i 仅与 i-1 相关,因此可以仅使用 dp[j],循环 i 即可
- 注意:由于每个元素均不相同、所以组合时不需要去重,需要 List 双重集合、必须深拷贝才能避免添加元素相互影响
- 定义 candidates.length 为 n,还需要循环组合数,时间复杂度:O(ntarget组合数(< 150)),空间复杂度:O(ntarget组合数(< 150))
- 代码
private List<List<Integer>> solution(int[] candidates, int target) {
if (candidates == null || candidates.length <= 0 || target < 0) {
return null;
}
List[] dp = new ArrayList[target + 1];
int dpLen = dp.length;
for (int i = 0; i < dpLen; i++) {
dp[i] = new ArrayList<List<Integer>>(149);
}
int candidatesLen = candidates.length;
dp[0].add(new ArrayList<>());
for (int i = 0; i < candidatesLen; i++) {
for (int j = candidates[i]; j < dpLen; j++) {
List<List<Integer>> candidatePreLists = dp[j - candidates[i]];
List<List<Integer>> candidateCurLists = dp[j];
if (candidatePreLists.size() > 0) {
List<List<Integer>> candidatePreTmpLists = new ArrayList<>(149);
for (List<Integer> candidatePreList : candidatePreLists) {
List<Integer> candidatePreTmpList = new ArrayList<>(candidatesLen);
candidatePreTmpList.addAll(candidatePreList);
candidatePreTmpLists.add(candidatePreTmpList);
}
for (List<Integer> candidatePreTmpList : candidatePreTmpLists) {
candidatePreTmpList.add(candidates[i]);
}
candidateCurLists.addAll(candidatePreTmpLists);
}
}
}
return dp[target];
}