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

Commit db80e20

Browse files
update content
1 parent bd16c2e commit db80e20

26 files changed

+163
-77
lines changed

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ PDF 共两本,一本《labuladong 的算法秘籍》类似教材,帮你系
179179
* [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
180180
* [东哥带你刷二叉树(思路篇)](https://labuladong.github.io/article/fname.html?fname=二叉树系列1)
181181
* [东哥带你刷二叉树(构造篇)](https://labuladong.github.io/article/fname.html?fname=二叉树系列2)
182-
* [东哥带你刷二叉树(序列化篇)](https://labuladong.github.io/article/fname.html?fname=二叉树的序列化)
183182
* [东哥带你刷二叉树(后序篇)](https://labuladong.github.io/article/fname.html?fname=二叉树系列3)
183+
* [东哥带你刷二叉树(序列化篇)](https://labuladong.github.io/article/fname.html?fname=二叉树的序列化)
184184
* [归并排序详解及应用](https://labuladong.github.io/article/fname.html?fname=归并排序)
185185
* [东哥带你刷二叉搜索树(特性篇)](https://labuladong.github.io/article/fname.html?fname=BST1)
186186
* [东哥带你刷二叉搜索树(基操篇)](https://labuladong.github.io/article/fname.html?fname=BST2)

‎动态规划系列/动态规划设计:最长递增子序列.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ int lengthOfLIS(int[] nums) {
147147
}
148148
```
149149

150+
<visual slug='longest-increasing-subsequence'/>
151+
150152
至此,这道题就解决了,时间复杂度 `O(N^2)`。总结一下如何找到动态规划的状态转移关系:
151153

152154
1、明确 `dp` 数组的定义。这一步对于任何动态规划问题都很重要,如果不得当或者不够清晰,会阻碍之后的步骤。
@@ -223,6 +225,8 @@ int lengthOfLIS(int[] nums) {
223225
}
224226
```
225227

228+
<visual slug='mydata-lis'/>
229+
226230
至此,二分查找的解法也讲解完毕。
227231

228232
这个解法确实很难想到。首先涉及数学证明,谁能想到按照这些规则执行,就能得到最长递增子序列呢?其次还有二分查找的运用,要是对二分查找的细节不清楚,给了思路也很难写对。
@@ -289,6 +293,8 @@ int lengthOfLIS(int[] nums) {
289293
}
290294
```
291295

296+
<visual slug='russian-doll-envelopes'/>
297+
292298
为了清晰,我将代码分为了两个函数, 你也可以合并,这样可以节省下 `height` 数组的空间。
293299

294300
由于增加了测试用例,这里必须使用二分搜索版的 `lengthOfLIS` 函数才能通过所有测试用例。这样的话算法的时间复杂度为 `O(NlogN)`,因为排序和计算 LIS 各需要 `O(NlogN)` 的时间,加到一起还是 `O(NlogN)`;空间复杂度为 `O(N)`,因为计算 LIS 的函数中需要一个 `top` 数组。

‎动态规划系列/单词拼接.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ class Solution {
9090
}
9191
}
9292
}
93-
9493
```
9594

9695
给这个函数输入 `nums = [1,2,3]`,输出是 3^3 = 27 种可能的组合:
@@ -342,6 +341,8 @@ class Solution {
342341

343342
```
344343

344+
<visual slug='word-break'/>
345+
345346
这个解法能够通过所有测试用例,我们根据 [算法时空复杂度使用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度) 来算一下它的时间复杂度:
346347

347348
因为有备忘录的辅助,消除了递归树上的重复节点,使得递归函数的调用次数从指数级别降低为状态的个数 `O(N)`,函数本身的复杂度还是 `O(N^2)`,所以总的时间复杂度是 `O(N^3)`,相较回溯算法的效率有大幅提升。
@@ -460,6 +461,8 @@ class Solution {
460461
}
461462
```
462463

464+
<visual slug='word-break-ii'/>
465+
463466
这个解法依然用备忘录消除了重叠子问题,所以 `dp` 函数递归调用的次数减少为 `O(N)`,但 `dp` 函数本身的时间复杂度上升了,因为 `subProblem` 是一个子集列表,它的长度是指数级的。再加上 Java 中用 `+` 拼接字符串的效率并不高,且还要消耗备忘录去存储所有子问题的结果,所以这个算法的时间复杂度并不比回溯算法低,依然是指数级别。
464467

465468
综上,我们处理排列组合问题时一般使用回溯算法去「遍历」回溯树,而不用「分解问题」的思路去处理,因为存储子问题的结果就需要大量的时间和空间,除非重叠子问题的数量较多的极端情况,否则得不偿失。

‎动态规划系列/编辑距离.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ int min(int a, int b, int c) {
299299
}
300300
```
301301

302+
<visual slug='edit-distance'/>
303+
302304
### 三、扩展延伸
303305

304306
一般来说,处理两个字符串的动态规划问题,都是按本文的思路处理,建立 DP table。为什么呢,因为易于找出状态转移的关系,比如编辑距离的 DP table:

‎动态规划系列/背包问题.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ int knapsack(int W, int N, int[] wt, int[] val) {
150150
}
151151
```
152152

153+
<visual slug='mydata-knapsack'/>
154+
153155
> note:其实函数签名中的物品数量 `N` 就是 `wt` 数组的长度,所以实际上这个参数 `N` 是多此一举的。但为了体现原汁原味的 0-1 背包问题,我就带上这个参数 `N` 了,你自己写的话可以省略。
154156

155157
至此,背包问题就解决了,相比而言,我觉得这是比较简单的动态规划问题,因为状态转移的推导比较自然,基本上你明确了 `dp` 数组的定义,就可以理所当然地确定状态转移了。

‎动态规划系列/贪心算法之区间调度问题.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ int eraseOverlapIntervals(int[][] intervals) {
129129
}
130130
```
131131

132+
<visual slug='non-overlapping-intervals'/>
133+
132134
再说说力扣第 452 题「用最少的箭头射爆气球」,我来描述一下题目:
133135

134136
假设在二维平面上有很多圆形的气球,这些圆形投影到 x 轴上会形成一个个区间对吧。那么给你输入这些区间,你沿着 x 轴前进,可以垂直向上射箭,请问你至少要射几箭才能把这些气球全部射爆呢?
@@ -169,6 +171,8 @@ int findMinArrowShots(int[][] intvs) {
169171
}
170172
```
171173

174+
<visual slug='minimum-number-of-arrows-to-burst-balloons'/>
175+
172176
接下来可阅读:
173177

174178
* [贪心算法之跳跃游戏](https://labuladong.github.io/article/fname.html?fname=跳跃游戏)

‎动态规划系列/魔塔.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,10 @@ class Solution {
238238
return memo[i][j];
239239
}
240240
}
241-
242241
```
243242

243+
<visual slug='dungeon-game'/>
244+
244245
这就是自顶向下带备忘录的动态规划解法,参考前文 [动态规划套路详解](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶) 很容易就可以改写成 `dp` 数组的迭代解法,这里就不写了,读者可以尝试自己写一写。
245246

246247
这道题的核心是定义 `dp` 函数,找到正确的状态转移方程,从而计算出正确的答案。

‎数据结构系列/BST1.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ void traverse(TreeNode root, int k) {
9797
}
9898
```
9999

100+
<visual slug='kth-smallest-element-in-a-bst'/>
101+
100102
这道题就做完了,不过呢,还是要多说几句,因为这个解法并不是最高效的解法,而是仅仅适用于这道题。
101103

102104
我们前文 [高效计算数据流的中位数](https://labuladong.github.io/article/fname.html?fname=数据流中位数) 中就提过今天的这个问题:
@@ -223,6 +225,8 @@ void traverse(TreeNode root) {
223225
}
224226
```
225227

228+
<visual slug='convert-bst-to-greater-tree'/>
229+
226230
这道题就解决了,核心还是 BST 的中序遍历特性,只不过我们修改了递归顺序,降序遍历 BST 的元素值,从而契合题目累加树的要求。
227231

228232
简单总结下吧,BST 相关的问题,要么利用 BST 左小右大的特性提升算法效率,要么利用中序遍历的特性满足题目的要求,也就这么些事儿吧。

‎数据结构系列/BST2.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) {
9898
}
9999
```
100100

101+
<visual slug='validate-binary-search-tree'/>
102+
101103
我们通过使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,这也是二叉树算法的一个小技巧吧。
102104

103105
### 在 BST 中搜索元素
@@ -146,6 +148,8 @@ TreeNode searchBST(TreeNode root, int target) {
146148
}
147149
```
148150

151+
<visual slug='search-in-a-binary-search-tree'/>
152+
149153
### 在 BST 中插入一个数
150154

151155
对数据结构的操作无非遍历 + 访问,遍历就是「找」,访问就是「改」。具体到这个问题,插入一个数,就是先找到插入位置,然后进行插入操作。
@@ -167,6 +171,8 @@ TreeNode insertIntoBST(TreeNode root, int val) {
167171
}
168172
```
169173

174+
<visual slug='insert-into-a-binary-search-tree'/>
175+
170176
### 三、在 BST 中删除一个数
171177

172178
这个问题稍微复杂,跟插入操作类似,先「找」再「改」,先把框架写出来再说:
@@ -257,6 +263,8 @@ TreeNode getMin(TreeNode node) {
257263
}
258264
```
259265

266+
<visual slug='delete-node-in-a-bst'/>
267+
260268
这样,删除操作就完成了。注意一下,上述代码在处理情况 3 时通过一系列略微复杂的链表操作交换 `root``minNode` 两个节点:
261269

262270
```java

‎数据结构系列/二叉树系列2.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ TreeNode build(int[] nums, int lo, int hi) {
140140
}
141141
```
142142

143+
<visual slug='maximum-binary-tree'/>
144+
143145
至此,这道题就做完了,还是挺简单的对吧,下面看两道更困难一些的。
144146

145147
### 通过前序和中序遍历结果构造二叉树
@@ -454,6 +456,8 @@ TreeNode build(int[] inorder, int inStart, int inEnd,
454456
}
455457
```
456458

459+
<visual slug='construct-binary-tree-from-inorder-and-postorder-traversal'/>
460+
457461
有了前一题的铺垫,这道题很快就解决了,无非就是 `rootVal` 变成了最后一个元素,再改改递归函数的参数而已,只要明白二叉树的特性,也不难写出来。
458462

459463
### 通过后序和前序遍历结果构造二叉树
@@ -551,6 +555,8 @@ class Solution {
551555
}
552556
```
553557

558+
<visual slug='construct-binary-tree-from-preorder-and-postorder-traversal'/>
559+
554560
代码和前两道题非常类似,我们可以看着代码思考一下,为什么通过前序遍历和后序遍历结果还原的二叉树可能不唯一呢?
555561

556562
关键在这一句:

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /