Skip to content

Commit f53e2b2

Browse files
Update
1 parent 80cb4d9 commit f53e2b2

10 files changed

+335
-61
lines changed

pics/39.组合总和.png

2.74 KB
Loading

pics/39.组合总和1.png

8.91 KB
Loading

problems/0039.组合总和.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ candidates 中的数字可以无限制重复被选取。
3939

4040
本题搜索的过程抽象成树形结构如下:
4141

42-
![39.组合总和](https://img-blog.csdnimg.cn/20201123202227835.png)
43-
42+
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png)
4443
注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
4544

4645
而在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) 中都可以知道要递归K层,因为要取k个元素的组合。
@@ -75,7 +74,7 @@ void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
7574

7675
在如下树形结构中:
7776

78-
![39.组合总和](https://img-blog.csdnimg.cn/20201123202227835.png)
77+
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png)
7978

8079
从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
8180

@@ -148,7 +147,7 @@ public:
148147

149148
在这个树形结构中:
150149

151-
![39.组合总和](https://img-blog.csdnimg.cn/20201123202227835.png)
150+
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png)
152151

153152
以及上面的版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。
154153

@@ -160,8 +159,8 @@ public:
160159

161160
如图:
162161

163-
![39.组合总和1](https://img-blog.csdnimg.cn/20201123202349897.png)
164162

163+
![39.组合总和1](https://img-blog.csdnimg.cn/20201223170809182.png)
165164

166165
for循环剪枝代码如下:
167166

problems/0056.合并区间.md

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,61 @@
1-
## 题目链接
2-
https://leetcode-cn.com/problems/merge-intervals/
1+
> 「代码随想录」出品,毕竟精品!
32
4-
## 思路
5-
这道题目看起来就是一道模拟类的题,但其实是一道贪心题目!
3+
# 56. 合并区间
64

7-
按照左区间排序之后,每次合并都取最大的右区间,这样就可以合并更多的区间了。
5+
题目链接:https://leetcode-cn.com/problems/merge-intervals/
86

9-
那有同学问了,这不是废话么? 当然要取最大的右区间啊
7+
给出一个区间的集合,请合并所有重叠的区间
108

11-
**是的,一想就是这么个道理,但它就是贪心的思想,局部最优推导出整体最优**
9+
示例 1:
10+
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
11+
输出: [[1,6],[8,10],[15,18]]
12+
解释: 区间 [1,3][2,6] 重叠, 将它们合并为 [1,6].
1213

13-
这也就是为什么很多同学刷题的时候都没有发现自己用了贪心。
14+
示例 2:
15+
输入: intervals = [[1,4],[4,5]]
16+
输出: [[1,5]]
17+
解释: 区间 [1,4][4,5] 可被视为重叠区间。
18+
注意:输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。
1419

15-
合并思路:如果 `intervals[i][0] < intervals[i - 1][1]` 即intervals[i]起始位置 < intervals[i - 1]终止位置,则一定有重复,需要合并。
20+
提示:
1621

17-
如图所示:
22+
* intervals[i][0] <= intervals[i][1]
1823

19-
<img src='../pics/56.合并区间.png' width=600> </img></div>
24+
# 思路
25+
26+
大家应该都感觉到了,此题一定要排序,那么按照左边界排序,还是右边界排序呢?
27+
28+
都可以!
29+
30+
那么我按照左边界排序,排序之后局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了,整体最优:合并所有重叠的区间。
31+
32+
局部最优可以推出全局最优,找不出反例,试试贪心。
33+
34+
那有同学问了,本来不就应该合并最大右边界么,这和贪心有啥关系?
35+
36+
有时候贪心就是常识!哈哈
37+
38+
按照左边界从小到大排序之后,如果 `intervals[i][0] < intervals[i - 1][1]` 即intervals[i]左边界 < intervals[i - 1]右边界,则一定有重复,因为intervals[i]的左边界一定是大于等于intervals[i - 1]的左边界。
39+
40+
即:intervals[i]的左边界在intervals[i - 1]左边界和右边界的范围内,那么一定有重复!
41+
42+
这么说有点抽象,看图:(**注意图中区间都是按照左边界排序之后了**
43+
44+
![56.合并区间](https://img-blog.csdnimg.cn/20201223200632791.png)
45+
46+
知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢?
47+
48+
其实就是用合并区间后左边界和右边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把原区间加入到result数组。
2049

2150
C++代码如下:
2251

23-
```
52+
```C++
2453
class Solution {
2554
public:
26-
// 按照区间左边界排序
55+
// 按照区间左边界从小到大排序
2756
static bool cmp (const vector<int>& a, const vector<int>& b) {
2857
return a[0] < b[0];
2958
}
30-
3159
vector<vector<int>> merge(vector<vector<int>>& intervals) {
3260
vector<vector<int>> result;
3361
if (intervals.size() == 0) return result;
@@ -36,14 +64,15 @@ public:
3664
int length = intervals.size();
3765

3866
for (int i = 1; i < length; i++) {
39-
int start = intervals[i - 1][0];
40-
int end = intervals[i - 1][1];
67+
int start = intervals[i - 1][0]; // 初始为i-1区间的左边界
68+
int end = intervals[i - 1][1]; // 初始i-1区间的右边界
4169
while (i < length && intervals[i][0] <= end) { // 合并区间
42-
end = max(end, intervals[i][1]);
43-
if (i == length - 1) flag = true; // 最后一个区间也合并了
44-
i++;
70+
end = max(end, intervals[i][1]); // 不断更新右区间
71+
if (i == length - 1) flag = true; // 最后一个区间也合并了
72+
i++; // 继续合并下一个区间
4573
}
46-
result.push_back({start, end});
74+
// start和end是表示intervals[i - 1]的左边界右边界,所以最优intervals[i]区间是否合并了要标记一下
75+
result.push_back({start, end});
4776
}
4877
// 如果最后一个区间没有合并,将其加入result
4978
if (flag == false) {
@@ -53,3 +82,47 @@ public:
5382
}
5483
};
5584
```
85+
86+
当然以上代码有冗余一些,可以优化一下,如下:(思路是一样的)
87+
88+
```C++
89+
class Solution {
90+
public:
91+
vector<vector<int>> merge(vector<vector<int>>& intervals) {
92+
vector<vector<int>> result;
93+
if (intervals.size() == 0) return result;
94+
// 排序的参数使用了lamda表达式
95+
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});
96+
97+
result.push_back(intervals[0]);
98+
for (int i = 1; i < intervals.size(); i++) {
99+
if (result.back()[1] >= intervals[i][0]) { // 合并区间
100+
result.back()[1] = max(result.back()[1], intervals[i][1]);
101+
} else {
102+
result.push_back(intervals[i]);
103+
}
104+
}
105+
return result;
106+
}
107+
};
108+
```
109+
110+
* 时间复杂度:O(nlogn) ,有一个快排
111+
* 空间复杂度:O(1),我没有算result数组(返回值所需容器占的空间)
112+
113+
114+
# 总结
115+
116+
对于贪心算法,很多同学都是:**如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了**
117+
118+
跟着「代码随想录」刷题的录友应该感受过,贪心难起来,真的难。
119+
120+
那应该怎么办呢?
121+
122+
正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。
123+
124+
「代码随想录」会把贪心常见的经典题目覆盖到,大家只要认真学习打卡就可以了。
125+
126+
就酱,学算法,就在「代码随想录」,值得介绍给身边的朋友同学们!
127+
128+

problems/0070.爬楼梯.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,43 @@
11

2+
# 思路
3+
4+
本题大家多举一个例子,就发现这其实就是斐波那契数列。
5+
6+
题目509. 斐波那契数中的代码初始化部分稍加改动,就可以过了本题。
7+
8+
C++代码如下:
9+
```
10+
class Solution {
11+
public:
12+
int climbStairs(int n) {
13+
if (n <= 1) return n;
14+
vector<int> dp(n + 1);
15+
dp[0] = 1;
16+
dp[1] = 1;
17+
for (int i = 2; i <= n; i++) {
18+
dp[i] = dp[i - 1] + dp[i - 2];
19+
}
20+
return dp[n];
21+
22+
}
23+
};
24+
```
25+
26+
既然这么简单为什么还要讲呢,其实本题稍加改动就是一道面试好题,如果每次可以爬 1 或 2或3或者m 个台阶呢,走到楼顶有几种方法?
27+
28+
29+
30+
* 确定dp数组以及下标的含义
31+
32+
dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法
33+
34+
* 确定递推公式
35+
36+
dp[i]有几种来源,dp[i - 1],dp[i - 2]
37+
38+
* dp数组如何初始化
39+
* 确定遍历顺序
40+
241
dp里求排列,1 2 步 和 2 1 步都是上三个台阶,但不一样!
342

443
这是求排列
@@ -17,3 +56,19 @@ public:
1756
}
1857
};
1958
```
59+
60+
# 总结
61+
62+
如果我来面试的话,我就会想给候选人出一个 本题原题,看其表现,如果顺利写出来,进而在要求每次可以爬[1 - m]个台阶应该怎么写。
63+
64+
顺便再考察一下两个for循环的嵌套顺序,为什么target放外面,nums放里面。这就能反馈出对背包问题本质的掌握程度,是不是刷题背公式,一眼就看出来。
65+
66+
这么一连套下来,如果候选人都能答出来,相信任何一位面试官都是非常满意的。
67+
68+
**本题代码不长,题目也很普通,当稍稍一进阶就可以考察本质问题,而且题目进阶的内容在leetcode上并没有,一定程度上就可以排除掉刷题党了,简直是面试题目的绝佳选择!**
69+
70+
相信通过这道简单的斐波那契数列题目,大家能感受到大厂面试官最喜欢什么样的面试题目了,并不是手撕红黑树!
71+
72+
73+
所以本题是一道非常好的题目。
74+

0 commit comments

Comments
 (0)