Skip to content

Commit 945c794

Browse files
author
lucifer
committed
2 parents 6c1b81a + 4ee85e2 commit 945c794

14 files changed

+105
-11
lines changed

problems/1.two-sum.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ https://leetcode-cn.com/problems/two-sum
2121

2222
- 哈希表
2323

24+
## 公司
25+
26+
- 字节、百度、腾讯
27+
2428
## 思路
2529

2630
最容易想到的就是暴力枚举,我们可以利用两层 for 循环来遍历每个元素,并查找满足条件的目标元素。不过这样时间复杂度为 O(N^2),空间复杂度为 O(1),时间复杂度较高,我们要想办法进行优化。我们可以增加一个 Map 记录已经遍历过的数字及其对应的索引值。这样当遍历一个新数字的时候去 Map 里查询,target 与该数的差值是否已经在前面的数字中出现过。如果出现过,那么已经得出答案,就不必再往下执行了。

problems/11.container-with-most-water.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ https://leetcode-cn.com/problems/container-with-most-water/description/
3030

3131
- 双指针
3232

33+
## 公司
34+
35+
- 字节
36+
3337
## 思路
3438

3539
题目中说`找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。` ,因此符合直觉的解法就是固定两个端点,计算可以承载的水量, 然后不断更新最大值,最后返回最大值即可。这种算法,需要两层循环,时间复杂度是 $O(n^2)$。

problems/124.binary-tree-maximum-path-sum.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ Output: 42
3535

3636
- 递归
3737

38+
## 公司
39+
40+
- 字节
41+
3842
## 思路
3943

4044
这道题目的path让我误解了,然后浪费了很多时间来解这道题

problems/146.lru-cache.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ cache.get(4); // returns 4
3232

3333
- 队列
3434

35+
## 公司
36+
37+
- 百度、字节
38+
3539
## 思路
3640

3741
`本题已被收录到我的新书中,敬请期待~`

problems/15.3-sum.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ A solution set is:
2929
- 双指针
3030
- 分治
3131

32+
## 公司
33+
34+
- 阿里、字节
35+
3236
## 思路
3337

3438
我们采用`分治`的思想. 想要找出三个数相加等于 0,我们可以数组依次遍历,

problems/2.add-two-numbers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Explanation: 342 + 465 = 807.
2121

2222
- 链表
2323

24+
## 公司
25+
26+
- 阿里、百度、腾讯
27+
2428
## 思路
2529

2630
设立一个表示进位的变量 carried,建立一个新链表,

problems/206.reverse-linked-list.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ A linked list can be reversed either iteratively or recursively. Could you imple
1818

1919
- 链表
2020

21+
## 公司
22+
23+
- 阿里、百度、腾讯
24+
2125
## 思路
2226

2327
这个就是常规操作了,使用一个变量记录前驱 pre,一个变量记录后继 next.

problems/21.merge-two-sorted-lists.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ https://leetcode-cn.com/problems/merge-two-sorted-lists
1919
- 递归
2020
- 链表
2121

22+
## 公司
23+
24+
- 阿里、字节、腾讯
25+
2226
## 思路
2327

2428
使用递归来解题,将两个链表头部较小的一个与剩下的元素合并,并返回排好序的链表头,当两条链表中的一条为空时终止递归。

problems/3.longest-substring-without-repeating-characters.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer
2121
- 哈希表
2222
- [滑动窗口](https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md)
2323

24+
## 公司
25+
26+
- 阿里、字节、腾讯
27+
2428
## 思路
2529

2630
用一个 hashmap 来建立字符和其出现位置之间的映射。同时维护一个滑动窗口,窗口内的都是没有重复的字符,去尽可能的扩大窗口的大小,窗口不停的向右滑动。

problems/312.burst-balloons.md

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ https://leetcode-cn.com/problems/burst-balloons/
2626
## 前置知识
2727

2828
- 回溯法
29+
- 动态规划
2930

3031
### 思路
3132

3233
#### 回溯法
3334

34-
分析一下这道题,就是要截破所有的气球,获得硬币的最大数量,然后左右两边的气球相邻了。那就截呗,我的第一反应就是暴力,回溯法;但是肯定会超时,为什么呢?因为题目给的气球数量有点多,最多 500 个;500 的阶乘,会超时爆栈;但是我们依然写一下代码,找下突破口,小伙伴们千万不要看不起暴力,暴力是优化的突破口;如果小伙伴对回溯法不太熟悉,我建议你记住下面的模版,也可以看我之前写的文章,回溯法基本可以使用以下的模版写。回溯法省心省力,0 智商负担,懂的朋友都懂,QAQ。
35+
分析一下这道题,就是要戳破所有的气球,获得硬币的最大数量,然后左右两边的气球相邻了。我的第一反应就是暴力,回溯法。
36+
37+
但是肯定会超时,为什么呢?因为题目给的气球数量有点多,最多 500 个;500 的阶乘,会超时爆栈;但是我们依然写一下代码,找下突破口,小伙伴们千万不要看不起暴力,暴力是优化的突破口;
38+
39+
如果小伙伴对回溯法不太熟悉,我建议你记住下面的模版,也可以看我之前写的文章,回溯法基本可以使用以下的模版写。回溯法省心省力,0 智商负担。
3540

3641
#### 代码
3742

@@ -65,25 +70,31 @@ var maxCoins = function (nums) {
6570

6671
#### 动态规划
6772

68-
回溯法的缺点也很明显,复杂度很高,对应本题截气球;小伙伴们可以脑补一下执行过程的状态树,这里我偷个懒就不画了;通过仔细观察这个状态树,我们会发现这个状态树的【选择】上,会有一些重复的选择分支;很明显存在了重复子问题;自然我就想到了能不能用动态规划来解决;
73+
回溯法的缺点也很明显,复杂度很高,对应本题戳气球;小伙伴们可以脑补一下执行过程的状态树,这里我偷个懒就不画了;通过仔细观察这个状态树,我们会发现这个状态树的【选择】上,会有一些重复的选择分支;很明显存在了重复子问题;自然我就想到了能不能用动态规划来解决;
6974

70-
判读能不能用动态规划解决,还有一个问题,就是必须存在最优子结构;什么意思呢?其实就是根据局部最优,推导出答案;假设我们截破第 k 个气球是最优策略的最后一步,和上一步有没有联系呢?根据题目意思,截破第 k 个,前一个和后一个就变成相邻的了,看似是会有联系,其实是没有的。因为截破第 k 个和 k-1 个是没有联系的,脑补一下回溯法的状态树就更加明确了;
75+
判读能不能用动态规划解决,还有一个问题,就是必须存在最优子结构;什么意思呢?其实就是根据局部最优,推导出答案;假设我们戳破第 k 个气球是最优策略的最后一步,和上一步有没有联系呢?根据题目意思,戳破第 k 个,前一个和后一个就变成相邻的了,看似是会有联系,其实是没有的。因为戳破第 k 个和 k-1 个是没有联系的,脑补一下回溯法的状态树就更加明确了;
7176

7277
既然用动态规划,那就老套路了,把动态规划的三个问题想清楚定义好;然后找出题目的【状态】和【选择】,然后根据【状态】枚举,枚举的过程中根据【选择】计算递推就能得到答案了。
7378

74-
那本题的【选择】是什么呢?就是截哪一个气球。那【状态】呢?就是题目给的气球数量。
79+
那本题的【选择】是什么呢?就是戳哪一个气球。那【状态】呢?就是题目给的气球数量。
7580

7681
1. 定义状态
7782

78-
- 这里有个细节,就是题目说明有两个虚拟气球,nums[-1] = nums[n] = 1;如果当前截破的气球是最后一个或者第一个,前面/后面没有气球了,不能乘以 0,而是乘以 1。
79-
- 定义状态的最关键两个点,往子问题(问题规模变小)想,最后一步最优策略是什么;我们假设最后截破的气球是 k,截破 k 获得最大数量的银币就是 nums[i] _ nums[k] _ nums[j] 再加上前面截破的最大数量和后面的最大数量,即:nums[i] _ nums[k] _ nums[j] + 前面最大数量 + 后面最大数量,就是答案。
80-
- 而如果我们不考虑两个虚拟气球而直接定义状态,截到最后两个气球的时候又该怎么定义状态来避免和前面的产生联系呢?这两个虚拟气球就恰到好处了,太细节了;这也是本题的一个难点之一。
81-
- 那我们可以这样来定义状态,dp[i][j] = x 表示,戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球,可以获得的最大硬币数为 x。为什么开区间?因为不能和已经计算过的产生联系,我们这样定义之后,利用两个虚拟气球,截到最后两个气球的时候就完美的避开了所有状态的联系,太细节了。
83+
- 这里有个细节,就是题目说明有两个虚拟气球,nums[-1] = nums[n] = 1;如果当前戳破的气球是最后一个或者第一个,前面/后面没有气球了,不能乘以 0,而是乘以 1。
84+
85+
- 定义状态的最关键两个点,往子问题(问题规模变小)想,最后一步最优策略是什么;我们假设最后戳破的气球是 k,戳破 k 获得最大数量的银币就是 nums[i] * nums[k] * nums[j] 再加上前面戳破的最大数量和后面的最大数量,即:nums[i] * nums[k] * nums[j] + 前面最大数量 + 后面最大数量,就是答案。
86+
87+
> 注意 i 不一定是 k - 1,同理 j 也不一定是 k + 1,因此可能 i - 1 和 i + 1 已经被戳破了。
88+
89+
- 而如果我们不考虑两个虚拟气球而直接定义状态,戳到最后两个气球的时候又该怎么定义状态来避免和前面的产生联系呢?这两个虚拟气球就恰到好处了,这也是本题的一个难点之一。
90+
91+
- 那我们可以这样来定义状态,dp[i][j] = x 表示戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球,可以获得的最大硬币数为 x。为什么开区间?因为不能和已经计算过的产生联系,我们这样定义之后,利用两个虚拟气球,戳到最后两个气球的时候就完美的避开了所有状态的联系。
8292

8393
2. 状态转移方程
8494

85-
- 而对于 dp[i][j],i 和 j 之间会有很多气球,到底该截哪个先呢?我们直接设为 k,枚举选择最优的 k 就可以了。
86-
- 所以,最终的状态转移方程为:dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[k] + nums[i] + nums[j])
95+
- 而对于 dp[i][j],i 和 j 之间会有很多气球,到底该戳哪个先呢?我们直接设为 k,枚举选择最优的 k 就可以了。
96+
- 1。
97+
- 所以,最终的状态转移方程为:dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[k] * nums[i] * nums[j])。由于是开区间,因此 k 为 i + 1, i + 2... j - 1。
8798

8899
3. 初始值和边界
89100

@@ -92,15 +103,21 @@ var maxCoins = function (nums) {
92103

93104
4. 如何枚举状态
94105

95-
- 因为我们最终要求的答案是 dp[0][n + 1]就是截破虚拟气球之间的所有气球获得的最大值
106+
- 因为我们最终要求的答案是 dp[0][n + 1]就是戳破虚拟气球之间的所有气球获得的最大值
96107
- 当 i == j 时,i 和 j 之间是没有气球的,所以枚举的状态很明显是 dp table 的左上部分,也就是 j 大于 i,如下图所示,只给出一部分方便思考。
97108

98109
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfckn1vngxj30lk0aodgf.jpg)
110+
(图有错误。图中 dp[k][i] 应该是 dp[i][k],dp[j][k] 应该是 dp[k][j]
99111

100112
> 从上图可以看出,我们需要从下到上,从左到右进行遍历。
101113
102114
#### 代码
103115

116+
117+
代码支持: JS, Python
118+
119+
JS Code:
120+
104121
```js
105122
var maxCoins = function (nums) {
106123
let n = nums.length;
@@ -123,6 +140,31 @@ var maxCoins = function (nums) {
123140
};
124141
```
125142

143+
Python Code:
144+
145+
```py
146+
class Solution:
147+
def maxCoins(self, nums: List[int]) -> int:
148+
n = len(nums)
149+
points = [1] + nums + [1]
150+
dp = [[0] * (n + 2) for _ in range(n + 2)]
151+
152+
for i in range(n, -1, -1):
153+
for j in range(i + 1, n + 2):
154+
for k in range(i + 1, j):
155+
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[k] * points[j])
156+
return dp[0][-1]
157+
```
158+
159+
**复杂度分析**
160+
- 时间复杂度:$O(N ^ 3)$
161+
- 空间复杂度:$O(N ^ 2)$
162+
126163
### 总结
127164

128165
简单的 dp 题目会直接告诉你怎么定义状态,告诉你怎么选择计算,你只需要根据套路判断一下能不能用 dp 解题即可,而判断能不能,往往暴力就是突破口。而困难点的 dp,我觉的都是细节问题了,要注意的细节太多了。感觉力扣加加,路西法大佬,把我领进了动态规划的大门,共勉。
166+
167+
168+
更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。
169+
170+
关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

0 commit comments

Comments
 (0)