Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit64a885d

Browse files
Update
1 parenta15a13e commit64a885d

9 files changed

+225
-47
lines changed

‎README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
*[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)
136136
*[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)
137137
*[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
138+
*[本周小结!(回溯算法系列一)](https://mp.weixin.qq.com/s/m2GnTJdkYhAamustbb6lmw)
139+
*[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)
138140

139141
(持续更新中....)
140142

@@ -303,6 +305,7 @@
303305
|[0039.组合总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0039.组合总和.md)|数组/回溯|中等|**回溯**|
304306
|[0040.组合总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0040.组合总和II.md)|数组/回溯|中等|**回溯**|
305307
|[0042.接雨水](https://github.com/youngyangyang04/leetcode/blob/master/problems/0042.接雨水.md)|数组/栈/双指针|困难|**双指针****单调栈****动态规划**|
308+
|[0045.跳跃游戏II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0045.跳跃游戏II.md)|贪心|困难|**贪心**|
306309
|[0046.全排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0046.全排列.md)|回溯|中等|**回溯**|
307310
|[0047.全排列II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0047.全排列II.md)|回溯|中等|**回溯**|
308311
|[0051.N皇后](https://github.com/youngyangyang04/leetcode/blob/master/problems/0051.N皇后.md)|回溯|困难|**回溯**|

‎pics/39.组合总和1.png

-3.57 KB
Loading

‎pics/40.组合总和II.png

77.7 KB
Loading

‎pics/40.组合总和II1.png

289 KB
Loading

‎pics/45.跳跃游戏II.png

40.1 KB
Loading

‎problems/0039.组合总和.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,6 @@ private:
177177
vector<vector<int>> result;
178178
vector<int> path;
179179
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
180-
if (sum > target) {
181-
return;
182-
}
183180
if (sum == target) {
184181
result.push_back(path);
185182
return;

‎problems/0040.组合总和II.md

Lines changed: 144 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,173 @@
1-
#第40题. 组合总和
1+
>这篇可以说是全网把组合问题如何去重,讲的最清晰的了!
2+
3+
4+
#40.组合总和II
5+
6+
题目链接:https://leetcode-cn.com/problems/combination-sum-ii/
7+
28
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
39

410
candidates 中的每个数字在每个组合中只能使用一次。
511

6-
说明:
12+
说明:
13+
所有数字(包括目标数)都是正整数。
14+
解集不能包含重复的组合。 
715

8-
所有数字(包括目标数)都是正整数。
9-
解集不能包含重复的组合。 
16+
示例 1:
17+
输入: candidates = [10,1,2,7,6,1,5], target = 8,
18+
所求解集为:
19+
[
20+
[1, 7],
21+
[1, 2, 5],
22+
[2, 6],
23+
[1, 1, 6]
24+
]
25+
26+
示例 2:
27+
输入: candidates = [2,5,2,1,2], target = 5,
28+
所求解集为:
29+
[
30+
 [1,2,2],
31+
 [5]
32+
]
1033

11-
示例 1:
34+
#思路
1235

13-
输入: candidates = [10,1,2,7,6,1,5], target = 8,
14-
所求解集为:
15-
[
16-
[1, 7],
17-
[1, 2, 5],
18-
[2, 6],
19-
[1, 1, 6]
20-
]
36+
这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别:
2137

22-
示例 2:
38+
1. 本题candidates 中的每个数字在每个组合中只能使用一次。
39+
2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是无重复元素的数组candidates
2340

24-
输入: candidates = [2,5,2,1,2], target = 5,
25-
所求解集为:
26-
[
27-
 [1,2,2],
28-
 [5]
29-
]
41+
最后本题和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求一样,解集不能包含重复的组合。
3042

31-
#思想
43+
**本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合**
3244

33-
这道题目和[0039.组合总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0039.组合总和.md) 区别就是要去重。
45+
一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
3446

35-
很多同学在去重上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
47+
所以要在搜索的过程中就去掉重复组合。
3648

37-
这个去重为什么很难理解呢,**所谓去重,其实就是使用过的元素不能重复选取。** 这么一说好像很简单!
49+
很多同学在去重的问题上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
3850

51+
这个去重为什么很难理解呢,**所谓去重,其实就是使用过的元素不能重复选取。** 这么一说好像很简单!
3952

4053
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。**
4154

42-
所以要明确我们要去重的是同一树层上的“使用过”。
55+
那么问题来了,我们是要同一树层上使用过,还是统一树枝上使用过呢?
56+
57+
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
58+
59+
**所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重**
4360

4461
为了理解去重我们来举一个例子,candidates =[1, 1, 2], target = 3,(方便起见candidates已经排序了)
4562

46-
选择过程如图所示
63+
选择过程树形结构如图所示
4764

4865
<imgsrc='../pics/40.组合总和II.png'width=600> </img></div>
4966

67+
可以看到图中,每个节点相对于[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组,这个used数组下面会重点介绍。
68+
69+
##回溯三部曲
70+
71+
***递归函数参数**
72+
73+
[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
74+
75+
这个集合去重的重任就是used来完成的。
76+
77+
代码如下:
5078

51-
理解了“同一树枝使用过”和“同一树层使用过” 之后,我们在拉看如下代码实现,关键地方已经注释,大家应该就理解了
79+
```
80+
vector<vector<int>> result; // 存放组合集合
81+
vector<int> path; // 符合条件的组合
82+
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
83+
```
84+
85+
***递归终止条件**
86+
87+
[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)相同,终止条件为`sum > target``sum == target`
5288

53-
#C++代码
89+
代码如下:
90+
91+
```
92+
if (sum > target) { // 这个条件其实可以省略
93+
return;
94+
}
95+
if (sum == target) {
96+
result.push_back(path);
97+
return;
98+
}
99+
```
100+
101+
`sum > target` 这个条件其实可以省略,因为和在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
102+
103+
***单层搜索的逻辑**
104+
105+
这里与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)最大的不同就是要去重了。
106+
107+
前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
108+
109+
**如果`candidates[i] == candidates[i - 1]` 并且`used[i - 1] == false`,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]**
110+
111+
此时for循环里就应该做continue的操作。
112+
113+
这块比较抽象,如图:
114+
115+
<imgsrc='../pics/40.组合总和II1.png'width=600> </img></div>
116+
117+
我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:
118+
119+
* used[i - 1] == true,说明同一树支candidates[i - 1]使用过
120+
* used[i - 1] == false,说明同一树层candidates[i - 1]使用过
121+
122+
**这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!**
123+
124+
那么单层搜索的逻辑代码如下:
125+
126+
```
127+
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
128+
// used[i - 1] == true,说明同一树支candidates[i - 1]使用过
129+
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
130+
// 要对同一树层使用过的元素进行跳过
131+
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
132+
continue;
133+
}
134+
sum += candidates[i];
135+
path.push_back(candidates[i]);
136+
used[i] = true;
137+
backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1:这里是i+1,每个数字在每个组合中只能使用一次
138+
used[i] = false;
139+
sum -= candidates[i];
140+
path.pop_back();
141+
}
142+
```
143+
144+
**注意sum + candidates[i] <= target为剪枝操作,在[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)有讲解过!**
145+
146+
##C++代码
147+
148+
回溯三部曲分析完了,整体C++代码如下:
54149

55150
```
56151
class Solution {
57152
private:
58153
vector<vector<int>> result;
59154
vector<int> path;
60155
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
61-
if (sum > target) {
62-
return;
63-
}
64156
if (sum == target) {
65157
result.push_back(path);
66158
return;
67159
}
68-
69-
// 每个组合中只能使用一次 所以用 startindex
70-
// 给定一个数组 candidates 默认有重复项,解集不能包含重复的组合。 所以使用if这一套
71-
for (int i = startIndex; i < candidates.size(); i++) {
72-
// 这里理解used[i - 1]非常重要
73-
// used[i - 1] == true,说明同一树支candidates[i - 1]使用过
160+
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
161+
// used[i - 1] == true,说明同一树支candidates[i - 1]使用过
74162
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
75-
//而我们要对同一树层使用过的元素进行跳过
76-
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
163+
//要对同一树层使用过的元素进行跳过
164+
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
77165
continue;
78166
}
79167
sum += candidates[i];
80168
path.push_back(candidates[i]);
81169
used[i] = true;
82-
backtracking(candidates, target, sum, i + 1, used); //关键点在这里,不用i+1了
170+
backtracking(candidates, target, sum, i + 1, used); //和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
83171
used[i] = false;
84172
sum -= candidates[i];
85173
path.pop_back();
@@ -89,11 +177,25 @@ private:
89177
public:
90178
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
91179
vector<bool> used(candidates.size(), false);
92-
// 首先把给candidates排序,让其相同的元素都挨在一起。
180+
path.clear();
181+
result.clear();
182+
// 首先把给candidates排序,让其相同的元素都挨在一起。
93183
sort(candidates.begin(), candidates.end());
94184
backtracking(candidates, target, 0, 0, used);
95185
return result;
96-
97186
}
98187
};
188+
99189
```
190+
191+
#总结
192+
193+
本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)难度提升了不少。
194+
195+
**关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可**
196+
197+
所以Carl有必要把去重的这块彻彻底底的给大家讲清楚,**就连“树层去重”和“树枝去重”都是我自创的词汇,希望对大家理解有帮助!**
198+
199+
**就酱,如果感觉「代码随想录」诚意满满,就帮Carl宣传一波吧,感谢啦!**
200+
201+

‎problems/0045.跳跃游戏II.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
##题目链接
2+
https://leetcode-cn.com/problems/jump-game-ii/
3+
4+
##思路
5+
6+
本题相对于[0055.跳跃游戏](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md)还是难了不少。
7+
8+
本题要计算最大步数,那么就要想清楚什么时候步数加一?
9+
10+
**这里需要统计两个距离,当前可移动距离和下一步最远距离**
11+
12+
如果移动范围超过当前可移动距离,那么就必须再走一步来达到增加可移动距离的目的。
13+
14+
如图:
15+
16+
<imgsrc='../pics/45.跳跃游戏II.png'width=600> </img></div>
17+
18+
###方法一
19+
20+
这里还是有个特殊情况需要考虑,如果当前可移动距离的终点就是是集合终点,那么就不用增加步数了,因为不能再往后走了。
21+
22+
详情可看代码(详细注释)
23+
24+
```
25+
// 版本一
26+
class Solution {
27+
public:
28+
int jump(vector<int>& nums) {
29+
if (nums.size() == 1) return 0;
30+
int curDistance = 0; // 当前可移动距离
31+
int ans = 0; // 记录走的最大步数
32+
int nextDistance = 0; // 下一步最远距离
33+
for (int i = 0; i < nums.size(); i++) {
34+
nextDistance = max(nums[i] + i, nextDistance);
35+
if (i == curDistance) { // 遇到当前可移动距离的终点
36+
if (curDistance != nums.size() - 1) { // 如果当前可移动距离的终点不是集合终点
37+
ans++; // 需要走下一步
38+
curDistance = nextDistance; // 更新下一步最远距离的范围
39+
if (nextDistance >= nums.size() - 1) break; // 下一步最远距离已经可以达到终点,结束循环
40+
} else break; // 当前可移动距离的终点是集合终点
41+
}
42+
}
43+
return ans;
44+
}
45+
};
46+
```
47+
48+
###方法二
49+
50+
依然是贪心,思路和方法一差不多,代码可以简洁一些。
51+
52+
在方法一种,处理 当前可移动距离的终点 是不是集合终点 来判断ans是否要做相应的加一操作。
53+
54+
其实可以用 for循环遍历的时候i < nums.size() - 1,这样就是默认最后一步,一定是可以到终点的。
55+
56+
代码如下:
57+
58+
```
59+
60+
class Solution {
61+
public:
62+
int jump(vector<int>& nums) {
63+
int curDistance = 0;
64+
int ans = 0; // 记录走的最大步数,初始为0
65+
int nextDistance = 0; // 每走一步获得的跳跃范围
66+
for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1
67+
nextDistance = max(nums[i] + i, nextDistance);
68+
if (i == curDistance) { // 遇到本次跳跃范围的终点
69+
curDistance = nextDistance;
70+
ans++;
71+
}
72+
}
73+
return ans;
74+
}
75+
};
76+
```

‎problems/0349.两个数组的交集.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/
2222

2323
这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。
2424

25-
可以发现,貌似用数组做哈希表可以解决这道题目,把nums1的元素,映射到哈希数组的下表上,然后在遍历nums2的时候,判断是否出现过就可以了。
25+
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)[0383.赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ)
2626

27-
但是要注意,**使用数据来做哈希的题目,都限制了数值的大小,例如[哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)题目中只有小写字母,或者数值大小在[0- 10000] 之内等等**
27+
但是要注意,**使用数据来做哈希的题目,都限制了数值的大小。**
2828

2929
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
3030

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp