231012刷题记录
参考代码代码随想录来刷的
关键词:回溯
1 77. 组合
力扣题目链接
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
上面实例,可以用两层for循环。那么假如k等于100,要写100层for循环吗?显然仅仅是for循环嵌套是写不出来的,因为不知道要写多少层。
那么究竟该嵌套多少层呢?这就引出了回溯,回溯也是暴力法。
我对回溯的理解:当某一层到达终止条件后,会将结果加入结果集,然后返回到上一层,上一层会撤销递归前的操作,然后对上一层的兄弟结点进行操作。
朴素回溯代码,没有剪枝。
class Solution {
private List<Integer> path;
private List<List<Integer>> result;
public List<List<Integer>> combine(int n, int k) {
path = new ArrayList<>();
result = new ArrayList<>();
backtracking(n, k, 1);
return result;
}
private void backtracking(int n, int k, int start) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= n; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.remove(path.size() - 1);
}
}
}
剪枝优化
该题需要返回k 个数的组合,那么当不够k个数的时候就没必要往下走了。
例如当n=4,k=4时,只能从1往下走。
因为2、3、4开头的组合,个数必定小于k。
即要往下走的条件是,
(
n
−
i
+
1
)
+
p
a
t
h
.
s
i
z
e
(
)
>
=
k
(n-i+1)+path.size()>=k
(n−i+1)+path.size()>=k
(n-i+1)
是从start开始,可用数的总数。
class Solution {
private List<Integer> path;
private List<List<Integer>> result;
public List<List<Integer>> combine(int n, int k) {
path = new ArrayList<>();
result = new ArrayList<>();
backtracking(n, k, 1);
return result;
}
private void backtracking(int n, int k, int start) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= n - k + path.size() + 1; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.remove(path.size() - 1);
}
}
}
2 216.组合总和III
力扣题目链接
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
此题和上面那道题类似,只是结果集变了,结果集是k个和等于n的数的集合。
注意:我最开始是对path这个list中的各个元素求和来判断,这种方式效率比较低。可以在递归函数增加一个参数 sum,表示当前和。
这样就不用累加求和了。
未剪枝版本。
class Solution {
private List<Integer> path;
private List<List<Integer>> result;
public List<List<Integer>> combinationSum3(int k, int n) {
path = new ArrayList<>();
result = new ArrayList<>();
backtracking(n, k, 1);
return result;
}
private void backtracking(int n, int k, int start) {
if (path.size() == k) {
if (path.stream().mapToInt(item -> item).sum() == n)
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= 9; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.remove(path.size() - 1);
}
}
}
这道题有两个地方可以剪枝
- 和上道题一样,个数不足k便不进行递归。
- 当前和已经大于目标值了,便不用往下进行了。
剪枝后的版本
class Solution {
private List<Integer> path;
private List<List<Integer>> result;
public List<List<Integer>> combinationSum3(int k, int n) {
path = new ArrayList<>();
result = new ArrayList<>();
backtracking(n, k, 1, 0);
return result;
}
private void backtracking(int n, int k, int start, int sum) {
if (sum > n)
return;
if (path.size() == k) {
if (sum == n)
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= path.size() - k + 10; i++) {
path.add(i);
backtracking(n, k, i + 1, sum + i);
path.remove(path.size() - 1);
}
}
}
3 17.电话号码的字母组合
力扣题目链接
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
这道题也和上面的类似,主要是要理清楚每一层是什么东西。
每层不应该是数字,应当是数字对应的字母。
class Solution {
private String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
private List<String> result;
private StringBuilder path;
public List<String> letterCombinations(String digits) {
result = new ArrayList<>();
if (digits == null || digits.length() == 0)
return result;
path = new StringBuilder();
backtracking(digits, 0);
return result;
}
private void backtracking(String digits, int startIndex) {
if (startIndex == digits.length()) {
result.add(path.toString());
return;
}
String str = map[digits.charAt(startIndex) - '0'];
for (int i = 0; i < str.length(); i++) {
path.append(str.charAt(i));
backtracking(digits, startIndex + 1);
path.deleteCharAt(path.length() - 1);
}
}
}
将char类型转为int类型:将char类型变量 - ‘0’
即可