【力扣】组合总和系列
Leetcode 0039 组合总和
题目描述:Leetcode 0039 组合总和
分析
-
本题的考点:递归回溯。
-
直接暴搜即可。
代码
- C++
class Solution {
public:
vector<vector<int>> ans;
vector<vector<int>> combinationSum(vector<int>& c, int target) {
vector<int> path;
dfs(c, target, 0, path);
return ans;
}
void dfs(vector<int> &c, int target, int u, vector<int> path) {
// 每次递归path都会新创建一个,因此最后不用恢复现场
if (target == 0) {
ans.push_back(path);
return;
}
if (u == c.size()) return;
for (int i = 0; c[u] * i <= target; i++) {
dfs(c, target - c[u] * i, u + 1, path);
path.push_back(c[u]);
}
}
};
class Solution {
public:
vector<vector<int>> ans;
vector<int> path; // 区别,这种写法的效率远远高于上一种
vector<vector<int>> combinationSum(vector<int>& c, int target) {
dfs(c, target, 0);
return ans;
}
void dfs(vector<int> &c, int target, int u) {
// 每次递归path用的是同一个,因此需要恢复现场
if (target == 0) {
ans.push_back(path);
return;
}
if (u == c.size()) return;
for (int i = 0; c[u] * i <= target; i++) { // 枚举放入c[u]的个数
dfs(c, target - c[u] * i, u + 1);
path.push_back(c[u]);
}
for (int i = 0; c[u] * i <= target; i++) path.pop_back(); // 区别,恢复现场
}
};
- Java
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] c, int target) {
LinkedList<Integer> path = new LinkedList<>();
dfs(c, target, 0, path);
return ans;
}
// 当前考察到c[u], 当前方案存储在path中
private void dfs(int[] c, int target, int u, LinkedList<Integer> path) {
// 每次递归path用的是同一个,因此需要恢复现场
if (target == 0) {
ans.add((List<Integer>) path.clone());
return;
}
if (u == c.length) return;
for (int i = 0; c[u] * i <= target; i++) { // 枚举放入c[u]的个数
dfs(c, target - c[u] * i, u + 1, path);
path.add(c[u]);
}
for (int i = 0; c[u] * i <= target; i++) path.removeLast(); // 恢复现场
}
}
- Python
class Solution:
def combinationSum(self, c: List[int], target: int) -> List[List[int]]:
ans = []
path = []
self.dfs(c, target, 0, path, ans)
return ans
def dfs(self, c, target, u, path, ans):
if target == 0:
ans.append(path.copy())
return
if u == len(c):
return
i = 0
while c[u] * i <= target:
self.dfs(c, target - c[u] * i, u + 1, path, ans)
path.append(c[u])
i += 1
i = 0
while c[u] * i <= target:
path.pop(len(path) - 1)
i += 1
时空复杂度分析
-
时间复杂度:指数级别。
-
空间复杂度:和递归深度有关。
Leetcode 0040 组合总和II
题目描述:Leetcode 0040 组合总和II
分析
-
本题的考点:递归回溯。
-
不同于Leetcode 0039 组合总和,这一题
candidates
中可以有重复元素,并且candidates
中的每个元素只能使用一次。 -
首先我们要对数组进行排序,这样方便我们统计每个数据出现的次数,比如某个数据出现2次,则最多选取这个数据2次。
-
类似于Leetcode 0039 组合总和的做法,直接暴搜即可,不过在循环某个数据可以放入几个时需要在循环中加入限制条件。
代码
- C++
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& c, int target) {
sort(c.begin(), c.end()); // 方便后面统计数据出现次数
dfs(c, target, 0);
return ans;
}
void dfs(vector<int> &c, int target, int u) {
if (target == 0) {
ans.push_back(path);
return;
}
if (u == c.size()) return;
// 统计c[u]出现次数
int k = u + 1;
while (k < c.size() && c[k] == c[u]) k++;
int cnt = k - u;
for (int i = 0; c[u] * i <= target && i <= cnt; i++) {
dfs(c, target - c[u] * i, k);
path.push_back(c[u]);
}
for (int i = 0; c[u] * i <= target && i <= cnt; i++) path.pop_back();
}
};
- Java
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] c, int target) {
Arrays.sort(c);
LinkedList<Integer> path = new LinkedList<>();
dfs(c, target, 0, path);
return ans;
}
// 当前考察到c[u], 当前方案存储在path中
private void dfs(int[] c, int target, int u, LinkedList<Integer> path) {
// 每次递归path用的是同一个,因此需要恢复现场
if (target == 0) {
ans.add((List<Integer>) path.clone());
return;
}
if (u == c.length) return;
int k = u + 1;
while (k < c.length && c[k] == c[u]) k++;
int cnt = k - u;
for (int i = 0; c[u] * i <= target && i <= cnt; i++) { // 枚举放入c[u]的个数
dfs(c, target - c[u] * i, k, path);
path.add(c[u]);
}
for (int i = 0; c[u] * i <= target && i <= cnt; i++) path.removeLast(); // 恢复现场
}
}
- Python
class Solution:
def combinationSum2(self, c: List[int], target: int) -> List[List[int]]:
c.sort()
ans = []
path = []
self.dfs(c, target, 0, path, ans)
return ans
def dfs(self, c, target, u, path, ans):
if target == 0:
ans.append(path.copy())
return
if u == len(c):
return
k = u + 1
while k < len(c) and c[k] == c[u]:
k += 1
cnt = k - u
i = 0
while i <= cnt and c[u] * i <= target:
self.dfs(c, target - c[u] * i, k, path, ans)
path.append(c[u])
i += 1
i = 0
while i <= cnt and c[u] * i <= target:
path.pop(len(path) - 1)
i += 1
时空复杂度分析
-
时间复杂度:指数级别。
-
空间复杂度:和递归深度有关。
Leetcode 0216 组合总和III
分析
-
本题的考点:递归回溯。
-
类似于Leetcode 0077 组合,我们可以枚举每个位置放置哪个数据;这里的写法类似于LC77中的
Java
写法。 -
注意:当
k
大于9的话,无解,可以直接返回;或者n
大于10-k ~ 9
所有数据之和,无解,也可以直接返回。
代码
- C++
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
if (k > 9 || n > (19 - k) * k / 2) return ans;
dfs(1, n, k);
return ans;
}
// 还有u个位置需要放置数据
void dfs(int start, int n, int k) {
if (!n) {
if (!k) ans.push_back(path);
} else if (k) {
for (int i = start; i <= 9; i++)
if (n >= i) {
path.push_back(i);
dfs(i + 1, n - i, k - 1);
path.pop_back();
}
}
}
};
- Java
class Solution {
List<List<Integer>> ans = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
if (k > 9 || n > (19 - k) * k / 2) return ans;
dfs(1, n, k);
return ans;
}
// 还有u个位置需要放置数据
private void dfs(int start, int n, int k) {
if (n == 0) {
if (k == 0) ans.add(new LinkedList<>(path));
} else if (k != 0) {
for (int i = start; i <= 9; i++)
if (n >= i) {
path.add(i);
dfs(i + 1, n - i, k - 1);
path.removeLast();
}
}
}
}
时空复杂度分析
- 时间复杂度:指数级别。
- 空间复杂度:和递归深度有关。
Leetcode 0377 组合总和 Ⅳ
题目描述:Leetcode 0377 组合总和 Ⅳ
分析
-
本题的考点:动态规划。
-
这一题和完全背包问题十分类似,关于背包问题可以参考:背包九讲。但是和完全背包不同,不同点有两点:(1)这一题要求所选数据之和正好为
target
;(2)这一题数据相同但是排列不同的序列认为是不同的结果,例如1, 3
和3, 1
认为是不同的结果。 -
本题的分析如下:
代码
- C++
class Solution {
public:
int combinationSum4(vector<int>& nums, int m) {
int n = nums.size();
vector<long long> f(m + 1); // 中间过程int会溢出
f[0] = 1; // 什么都不选也是一种方案
for (int i = 0; i <= m; i++)
for (int v : nums)
if (i >= v)
f[i] = (f[i] + f[i - v]) % INT_MAX;
return f[m];
}
};
- Java
class Solution {
public int combinationSum4(int[] nums, int m) {
int n = nums.length;
int[] f = new int[m + 1]; // 不需要考虑溢出问题, lc提交能通过
f[0] = 1;
for (int i = 0; i <= m; i++)
for (int v : nums)
if (i >= v)
f[i] += f[i - v];
return f[m];
}
}
时空复杂度分析
-
时间复杂度: O ( n × m ) O(n \times m) O(n×m),
n
为数组长度,m
为需要组合出的数。 -
空间复杂度: O ( m ) O(m) O(m),
m
为需要组合出的数。
下一题是为了和 Leetcode 0377 组合总和 Ⅳ 对比:
Leetcode 0518 零钱兑换 II
分析
-
本题的考点:背包问题。
-
本题将
amout
简记为m
。问题是:给我们一个容量为m
的背包,每个物品的体积由coins
给出,每个物品可以使用无数次,问恰好装满背包的方案数。 -
本题就是一个完全背包问题。关于背包问题可以参考:背包问题(背包九讲)。
-
注意本题和Leetcode 0322 零钱兑换的区别,
LC322
让求的是体积恰好是m
需要用的最少硬币数,本题求得是体积恰好是m
的方案数,还有一类最常规的让求最大价值(体积不超过m
的最大价值,体积恰好是m
的最大价值,体积不小于m
的最小耗费)。 -
另外注意本题和Leetcode 0377 组合总和 Ⅳ,和本题的唯一区别在于,认为
1,2
和2,1
是不同的方案。因此LC377
先循环体积,再循环物品,这样对于每个体积,可以根据最后一个放入的数据分为n
类,因此可以实现不同顺序是不同方案。完全背包最初的分析(见背包九讲的分析),是每个物品放置几个数据,因此不同顺序认为是相同方案。
代码
- C++
class Solution {
public:
int change(int m, vector<int>& coins) {
vector<int> f(m + 1);
f[0] = 1;
for (int v : coins)
for (int j = v; j <= m; j++)
f[j] += f[j - v];
return f[m];
}
};
- Java
class Solution {
public int change(int m, int[] coins) {
int[] f = new int[m + 1];
f[0] = 1;
for (int v : coins)
for (int j = v; j <= m; j++)
f[j] += f[j - v];
return f[m];
}
}
- Python
class Solution:
def change(self, m: int, coins: List[int]) -> int:
f = [0 for _ in range(m + 1)]
f[0] = 1
for v in coins:
for j in range(v, m + 1):
f[j] += f[j - v];
return f[m]
时空复杂度分析
-
时间复杂度: O ( n × m ) O(n \times m) O(n×m),
n
为数组长度,m
是题目中的amout
。 -
空间复杂度: O ( n × m ) O(n \times m) O(n×m)。