Skip to content

Commit 91681d3

Browse files
committed
✨feat: add 741
1 parent 151b2f2 commit 91681d3

4 files changed

+120
-11
lines changed

Index/线性 DP.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| [650. 只有两个键的键盘](https://leetcode-cn.com/problems/2-keys-keyboard/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/2-keys-keyboard/solution/gong-shui-san-xie-yi-ti-san-jie-dong-tai-f035/) | 中等 | 🤩🤩🤩🤩 |
1616
| [678. 有效的括号字符串](https://leetcode-cn.com/problems/valid-parenthesis-string/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/valid-parenthesis-string/solution/gong-shui-san-xie-yi-ti-shuang-jie-dong-801rq/) | 中等 | 🤩🤩🤩🤩🤩 |
1717
| [688. 骑士在棋盘上的概率](https://leetcode-cn.com/problems/knight-probability-in-chessboard/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/knight-probability-in-chessboard/solution/gong-shui-san-xie-jian-dan-qu-jian-dp-yu-st8l/) | 中等 | 🤩🤩🤩🤩🤩 |
18+
| [741. 摘樱桃](https://leetcode.cn/problems/cherry-pickup/) | [LeetCode 题解链接](https://leetcode.cn/problems/cherry-pickup/solution/by-ac_oier-pz7i/) | 困难 | 🤩🤩🤩🤩 |
1819
| [978. 最长湍流子数组](https://leetcode-cn.com/problems/longest-turbulent-subarray/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/longest-turbulent-subarray/solution/xiang-jie-dong-tai-gui-hua-ru-he-cai-dp-3spgj/) | 中等 | 🤩🤩🤩🤩 |
1920
| [1137. 第 N 个泰波那契数](https://leetcode-cn.com/problems/n-th-tribonacci-number/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/n-th-tribonacci-number/solution/gong-shui-san-xie-yi-ti-si-jie-die-dai-d-m1ie/) | 简单 | 🤩🤩🤩🤩 |
2021
| [1220. 统计元音字母序列的数目](https://leetcode-cn.com/problems/count-vowels-permutation/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/count-vowels-permutation/solution/gong-shui-san-xie-yi-ti-shuang-jie-xian-n8f4o/) | 困难 | 🤩🤩🤩🤩 |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[741. 摘樱桃](https://leetcode.cn/problems/cherry-pickup/solution/by-ac_oier-pz7i/)** ,难度为 **困难**
4+
5+
Tag : 「线性 DP」
6+
7+
8+
9+
一个$N \times N$ 的网格( `grid`) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示:
10+
11+
* $0$ 表示这个格子是空的,所以你可以穿过它。
12+
* $1$ 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。
13+
* $-1$ 表示这个格子里有荆棘,挡着你的路。
14+
15+
你的任务是在遵守下列规则的情况下,尽可能的摘到最多樱桃:
16+
17+
* 从位置 $(0, 0)$ 出发,最后到达 $(N-1, N-1)$ ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为 $0$ 或者 $1$ 的格子);
18+
* 当到达 $(N-1, N-1)$ 后,你要继续走,直到返回到 $(0, 0)$ ,只能向上或向左走,并且只能穿越有效的格子;
19+
* 当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为0);
20+
* 如果在 $(0, 0)$ 和 $(N-1, N-1)$ 之间不存在一条可经过的路径,则没有任何一个樱桃能被摘到。
21+
22+
示例 1:
23+
```
24+
输入: grid =
25+
[[0, 1, -1],
26+
[1, 0, -1],
27+
[1, 1, 1]]
28+
29+
输出: 5
30+
31+
解释:
32+
玩家从(0,0)点出发,经过了向下走,向下走,向右走,向右走,到达了点(2, 2)。
33+
在这趟单程中,总共摘到了4颗樱桃,矩阵变成了[[0,1,-1],[0,0,-1],[0,0,0]]。
34+
接着,这名玩家向左走,向上走,向上走,向左走,返回了起始点,又摘到了1颗樱桃。
35+
在旅程中,总共摘到了5颗樱桃,这是可以摘到的最大值了。
36+
```
37+
38+
说明:
39+
* `grid` 是一个 $N \times N$ 的二维数组,N的取值范围是 $1 <= N <= 50$
40+
* 每一个 `grid[i][j]` 都是集合 `{-1, 0, 1}` 其中的一个数
41+
* 可以保证起点 `grid[0][0]` 和终点 `grid[N-1][N-1]` 的值都不会是 $-1$
42+
43+
---
44+
45+
### 线性 DP
46+
47+
为了方便,我们令 `grid``g`,同时调整矩阵横纵坐标从 $1$ 开始。
48+
49+
原问题为先从左上角按照「只能往下 + 只能往右」的规则走到右下角,然后再按照「只能往上 + 只能往左」的规则走回左上角,途径的值为 $1$ 的格子得一分(只能得分一次,得分后置零),同时不能经过值为 $-1$ 的格子。
50+
51+
其中第二趟的规则等价于按照第一趟的规则从左上角到右下角再走一遍,再结合每个位置的只能得分一次,可以将原问题等价于:两个点从左上角开始同时走,最终都走到右下角的最大得分。
52+
53+
定义 $f[k][i1][i2]$ 为当前走了 $k$ 步(横纵坐标之和),且第一个点当前在第 $i1$ 行,第二点在第 $i2$ 行时的最大得分,最终答案为 $f[2n][n][n]$,同时有 $f[2][1][1] = g[0][0]$ 的起始状态。
54+
55+
由于两个点是同时走(都走了 $k$ 步),结合「只能往下 + 只能往右」的规则,可直接算得第一个点所在的列为 $j1 = k - i1$,第二点所在的列为 $j2 = k - i2$。
56+
57+
不失一般性考虑 $f[k][i1][i2]$ 该如何转移,两个点均有可能走行或走列,即有 $4$ 种前驱状态:$f[k - 1][i1 - 1][i2]$、$f[k - 1][i1 - 1][i2 - 1]$、$f[k - 1][i1][i2 - 1]$ 和 $f[k - 1][i1][i2]$,在四者中取最大值,同时当前位置 $(i1, j1)$ 和 $(i2, j2)$ 的得分需要被累加,假设两者得分别为 $A$ 和 $B$,若两个位置不重叠的话,可以同时累加,否则只能累加一次。
58+
59+
一些细节:为了防止从值为 $-1$ 的格子进行转移影响正确性,我们需要先将所有 $f[k][i1][i2]$ 初始化为负无穷。
60+
61+
代码:
62+
```Java
63+
class Solution {
64+
static int N = 55, INF = Integer.MIN_VALUE;
65+
static int[][][] f = new int[2 * N][N][N];
66+
public int cherryPickup(int[][] g) {
67+
int n = g.length;
68+
for (int k = 0; k <= 2 * n; k++) {
69+
for (int i1 = 0; i1 <= n; i1++) {
70+
for (int i2 = 0; i2 <= n; i2++) {
71+
f[k][i1][i2] = INF;
72+
}
73+
}
74+
}
75+
f[2][1][1] = g[0][0];
76+
for (int k = 3; k <= 2 * n; k++) {
77+
for (int i1 = 1; i1 <= n; i1++) {
78+
for (int i2 = 1; i2 <= n; i2++) {
79+
int j1 = k - i1, j2 = k - i2;
80+
if (j1 <= 0 || j1 > n || j2 <= 0 || j2 > n) continue;
81+
int A = g[i1 - 1][j1 - 1], B = g[i2 - 1][j2 - 1];
82+
if (A == -1 || B == -1) continue;
83+
int a = f[k - 1][i1 - 1][i2], b = f[k - 1][i1 - 1][i2 - 1], c = f[k - 1][i1][i2 - 1], d = f[k - 1][i1][i2];
84+
int t = Math.max(Math.max(a, b), Math.max(c, d)) + A;
85+
if (i1 != i2) t += B;
86+
f[k][i1][i2] = t;
87+
}
88+
}
89+
}
90+
return f[2 * n][n][n] <= 0 ? 0 : f[2 * n][n][n];
91+
}
92+
}
93+
```
94+
* 时间复杂度:状态数量级为 $n^3$,每个状态转移复杂度为 $O(1)$。整体复杂度为 $O(n^3)$
95+
* 空间复杂度:$O(n^3)$
96+
97+
---
98+
99+
### 最后
100+
101+
这是我们「刷穿 LeetCode」系列文章的第 `No.741` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
102+
103+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
104+
105+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
106+
107+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
108+

LeetCode/741-750/743. 网络延迟时间(中等).md

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Tag : 「最短路」、「图」、「优先队列(堆)」
66

77

88

9-
n 个网络节点,标记为 1 到 n
9+
$n$ 个网络节点,标记为 $1$ 到 $n$
1010

1111
给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。
1212

@@ -34,13 +34,13 @@ Tag : 「最短路」、「图」、「优先队列(堆)」
3434
```
3535

3636
提示:
37-
* 1 <= k <= n <= 100
38-
* 1 <= times.length <= 6000
39-
* times[i].length == 3
40-
* 1 <= ui, vi <= n
41-
* ui != vi
42-
* 0 <= wi <= 100
43-
* 所有 (ui, vi) 对都 互不相同(即,不含重复边)
37+
* $1 <= k <= n <= 100$
38+
* $1 <= times.length <= 6000$
39+
* $times[i].length == 3$
40+
* $1 <= u_i, v_i <= n$
41+
* $u_i != v_i$
42+
* $0 <= w_i <= 100$
43+
* 所有 $(u_i, v_i)$ 对都 互不相同(即,不含重复边)
4444

4545
---
4646

@@ -467,7 +467,7 @@ class Solution {
467467
}
468468
}
469469
```
470-
* 时间复杂度:$O(n*m)$
470+
* 时间复杂度:$O(n \times m)$
471471
* 空间复杂度:$O(m)$
472472

473473
---
@@ -547,7 +547,7 @@ class Solution {
547547
}
548548
}
549549
```
550-
* 时间复杂度:$O(n*m)$
550+
* 时间复杂度:$O(n \times m)$
551551
* 空间复杂度:$O(m)$
552552

553553

LeetCode/871-880/873. 最长的斐波那契子序列的长度(中等).md

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Tag : 「序列 DP」、「哈希表」、「动态规划」
4242

4343
定义 $f[i][j]$ 为使用 $arr[i]$ 为斐波那契数列的最后一位,使用 $arr[j]$ 为倒数第二位(即 $arr[i]$ 的前一位)时的最长数列长度。
4444

45-
不失一般性考虑 $f[i][j]$ 该如何计算,首先根据斐波那契数列的定义,我们可以直接算得 $arr[j]$ 前一位的值为 $arr[i] - arr[j]$,而快速得知 $arr[i] - arr[j]$ 值的坐标 $t$,可以利用 `arr` 的严格单调递增性质,使用「哈希表」对坐标进行转存,若坐标 $t$ 存在,并且符合 $t < j$,说明此时至少凑成了长度为 $3$ 的斐波那契数列,同时结合状态定义,可以使用 $f[t][j]$ 来更新 $f[i][j]$,即有状态转移方程:
45+
不失一般性考虑 $f[i][j]$ 该如何计算,首先根据斐波那契数列的定义,我们可以直接算得 $arr[j]$ 前一位的值为 $arr[i] - arr[j]$,而快速得知 $arr[i] - arr[j]$ 值的坐标 $t$,可以利用 `arr` 的严格单调递增性质,使用「哈希表」对坐标进行转存,若坐标 $t$ 存在,并且符合 $t < j$,说明此时至少凑成了长度为 $3$ 的斐波那契数列,同时结合状态定义,可以使用 $f[j][t]$ 来更新 $f[i][j]$,即有状态转移方程:
4646

4747
$$
4848
f[i][j] = \max(3, f[j][t] + 1)

0 commit comments

Comments
 (0)