From 68d6053bc405813dba1b62aac98e996344ba3c07 Mon Sep 17 00:00:00 2001
From: labuladong
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+引用本文的文章
+
+ - [动态规划之子序列问题解题模板](https://labuladong.github.io/article/fname.html?fname=子序列问题模板)
+ - [经典动态规划:编辑距离](https://labuladong.github.io/article/fname.html?fname=编辑距离)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [97. Interleaving String](https://leetcode.com/problems/interleaving-string/?show=1) | [97. 交错字符串](https://leetcode.cn/problems/interleaving-string/?show=1) |
+| - | [剑指 Offer II 095. 最长公共子序列](https://leetcode.cn/problems/qJnOS7/?show=1) |
+
+
+引用本文的文章
+
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [滑动窗口算法延伸:Rabin Karp 字符匹配算法](https://labuladong.github.io/article/fname.html?fname=rabinkarp)
+
+
+
+
+
+
+
**_____________**
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
-
+引用本文的文章
+
+ - [贪心算法之区间调度问题](https://labuladong.github.io/article/fname.html?fname=贪心算法之区间调度问题)
+
+
-读到这里的朋友应该能理解算法解决博弈问题的套路了。学习算法,一定要注重算法的模板框架,而不是一些看起来牛逼的思路,也不要奢求上来就写一个最优的解法。不要舍不得多用空间,不要过早尝试优化,不要惧怕多维数组。dp 数组就是存储信息避免重复计算的,随便用,直到咱满意为止。
-希望本文对你有帮助。
**_____________**
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
-
+引用本文的文章
+
+ - [一个方法团灭 LeetCode 打家劫舍问题](https://labuladong.github.io/article/fname.html?fname=抢房子)
+ - [最优子结构原理和 dp 数组遍历方向](https://labuladong.github.io/article/fname.html?fname=最优子结构)
+
+
+
+
+
+
+
**_____________**
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
+![](https://labuladong.github.io/algo/images/souyisou2.png)
-
+引用本文的文章
-所以我们可以写出如下代码:
+ - [最优子结构原理和 dp 数组遍历方向](https://labuladong.github.io/article/fname.html?fname=最优子结构)
+ - [经典动态规划:编辑距离](https://labuladong.github.io/article/fname.html?fname=编辑距离)
-```cpp
-int m = s.size(), n = p.size();
+
-if (i == s.size()) {
- // 如果能匹配空串,一定是字符和 * 成对儿出现
- if ((n - j) % 2 == 1) {
- return false;
- }
- // 检查是否为 x*y*z* 这种形式
- for (; j + 1 < p.size(); j += 2) {
- if (p[j + 1] != '*') {
- return false;
- }
- }
- return true;
-}
-```
-根据以上思路,就可以写出完整的代码:
-```cpp
-/* 计算 p[j..] 是否匹配 s[i..] */
-bool dp(string& s, int i, string& p, int j) {
- int m = s.size(), n = p.size();
- // base case
- if (j == n) {
- return i == m;
- }
- if (i == m) {
- if ((n - j) % 2 == 1) {
- return false;
- }
- for (; j + 1 < n; j += 2) {
- if (p[j + 1] != '*') {
- return false;
- }
- }
- return true;
- }
- // 记录状态 (i, j),消除重叠子问题
- string key = to_string(i) + "," + to_string(j);
- if (memo.count(key)) return memo[key];
-
- bool res = false;
-
- if (s[i] == p[j] || p[j] == '.') {
- if (j < n - 1 && p[j + 1] == '*') {
- res = dp(s, i, p, j + 2)
- || dp(s, i + 1, p, j);
- } else {
- res = dp(s, i + 1, p, j + 1);
- }
- } else {
- if (j < n - 1 && p[j + 1] == '*') {
- res = dp(s, i, p, j + 2);
- } else {
- res = false;
- }
- }
- // 将当前结果记入备忘录
- memo[key] = res;
-
- return res;
-}
-```
+
+引用本文的题目
-代码中用了一个哈希表 `memo` 消除重叠子问题,因为正则表达算法的递归框架如下:
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
-```cpp
-bool dp(string& s, int i, string& p, int j) {
- dp(s, i, p, j + 2); // 1
- dp(s, i + 1, p, j); // 2
- dp(s, i + 1, p, j + 1); // 3
-}
-```
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer 19. 正则表达式匹配](https://leetcode.cn/problems/zheng-ze-biao-da-shi-pi-pei-lcof/?show=1) |
-那么,如果让你从 `dp(s, i, p, j)` 得到 `dp(s, i+2, p, j+2)`,至少有两条路径:`1 -> 2 -> 2` 和 `3 -> 3`,那么就说明 `(i+2, j+2)` 这个状态存在重复,这就说明存在重叠子问题。
+
+引用本文的文章
+
+ - [二分查找高效判定子序列](https://labuladong.github.io/article/fname.html?fname=二分查找判定子序列)
+ - [动态规划之子序列问题解题模板](https://labuladong.github.io/article/fname.html?fname=子序列问题模板)
+ - [动态规划解题套路框架](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶)
+ - [动态规划设计:最大子数组](https://labuladong.github.io/article/fname.html?fname=最大子数组)
+ - [动态规划问题的两种穷举视角](https://labuladong.github.io/article/fname.html?fname=动归两种视角)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [最优子结构原理和 dp 数组遍历方向](https://labuladong.github.io/article/fname.html?fname=最优子结构)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1425. Constrained Subsequence Sum](https://leetcode.com/problems/constrained-subsequence-sum/?show=1) | [1425. 带限制的子序列和](https://leetcode.cn/problems/constrained-subsequence-sum/?show=1) |
+| [256. Paint House](https://leetcode.com/problems/paint-house/?show=1)🔒 | [256. 粉刷房子](https://leetcode.cn/problems/paint-house/?show=1)🔒 |
+| - | [剑指 Offer II 091. 粉刷房子](https://leetcode.cn/problems/JEj789/?show=1) |
+
+
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [base case 和备忘录的初始值怎么定?](https://labuladong.github.io/article/fname.html?fname=备忘录等基础)
+ - [一个方法团灭 LeetCode 打家劫舍问题](https://labuladong.github.io/article/fname.html?fname=抢房子)
+ - [一个方法团灭 LeetCode 股票买卖问题](https://labuladong.github.io/article/fname.html?fname=团灭股票问题)
+ - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
+ - [两种思路解决单词拼接问题](https://labuladong.github.io/article/fname.html?fname=单词拼接)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [动态规划帮我通关了《辐射4》](https://labuladong.github.io/article/fname.html?fname=转盘)
+ - [动态规划帮我通关了《魔塔》](https://labuladong.github.io/article/fname.html?fname=魔塔)
+ - [动态规划设计:最长递增子序列](https://labuladong.github.io/article/fname.html?fname=动态规划设计:最长递增子序列)
+ - [动态规划问题的两种穷举视角](https://labuladong.github.io/article/fname.html?fname=动归两种视角)
+ - [如何运用贪心思想玩跳跃游戏](https://labuladong.github.io/article/fname.html?fname=跳跃游戏)
+ - [学习算法和刷题的框架思维](https://labuladong.github.io/article/fname.html?fname=学习数据结构和算法的高效方法)
+ - [对动态规划进行降维打击](https://labuladong.github.io/article/fname.html?fname=状态压缩技巧)
+ - [归并排序详解及应用](https://labuladong.github.io/article/fname.html?fname=归并排序)
+ - [当老司机学会了贪心算法](https://labuladong.github.io/article/fname.html?fname=老司机)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [旅游省钱大法:加权最短路径](https://labuladong.github.io/article/fname.html?fname=旅行最短路径)
+ - [最优子结构原理和 dp 数组遍历方向](https://labuladong.github.io/article/fname.html?fname=最优子结构)
+ - [算法学习和心流体验](https://labuladong.github.io/article/fname.html?fname=心流)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+ - [经典动态规划:0-1 背包问题](https://labuladong.github.io/article/fname.html?fname=背包问题)
+ - [经典动态规划:博弈问题](https://labuladong.github.io/article/fname.html?fname=动态规划之博弈问题)
+ - [经典动态规划:完全背包问题](https://labuladong.github.io/article/fname.html?fname=背包零钱)
+ - [经典动态规划:戳气球](https://labuladong.github.io/article/fname.html?fname=扎气球)
+ - [经典动态规划:最长公共子序列](https://labuladong.github.io/article/fname.html?fname=LCS)
+ - [经典动态规划:编辑距离](https://labuladong.github.io/article/fname.html?fname=编辑距离)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [112. Path Sum](https://leetcode.com/problems/path-sum/?show=1) | [112. 路径总和](https://leetcode.cn/problems/path-sum/?show=1) |
+| [115. Distinct Subsequences](https://leetcode.com/problems/distinct-subsequences/?show=1) | [115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/?show=1) |
+| [139. Word Break](https://leetcode.com/problems/word-break/?show=1) | [139. 单词拆分](https://leetcode.cn/problems/word-break/?show=1) |
+| [1696. Jump Game VI](https://leetcode.com/problems/jump-game-vi/?show=1) | [1696. 跳跃游戏 VI](https://leetcode.cn/problems/jump-game-vi/?show=1) |
+| [221. Maximal Square](https://leetcode.com/problems/maximal-square/?show=1) | [221. 最大正方形](https://leetcode.cn/problems/maximal-square/?show=1) |
+| [240. Search a 2D Matrix II](https://leetcode.com/problems/search-a-2d-matrix-ii/?show=1) | [240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/?show=1) |
+| [256. Paint House](https://leetcode.com/problems/paint-house/?show=1)🔒 | [256. 粉刷房子](https://leetcode.cn/problems/paint-house/?show=1)🔒 |
+| [62. Unique Paths](https://leetcode.com/problems/unique-paths/?show=1) | [62. 不同路径](https://leetcode.cn/problems/unique-paths/?show=1) |
+| [63. Unique Paths II](https://leetcode.com/problems/unique-paths-ii/?show=1) | [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/?show=1) |
+| [70. Climbing Stairs](https://leetcode.com/problems/climbing-stairs/?show=1) | [70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/?show=1) |
+| [91. Decode Ways](https://leetcode.com/problems/decode-ways/?show=1) | [91. 解码方法](https://leetcode.cn/problems/decode-ways/?show=1) |
+| - | [剑指 Offer 04. 二维数组中的查找](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/?show=1) |
+| - | [剑指 Offer 10- II. 青蛙跳台阶问题](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/?show=1) |
+| - | [剑指 Offer II 091. 粉刷房子](https://leetcode.cn/problems/JEj789/?show=1) |
+| - | [剑指 Offer II 097. 子序列的数目](https://leetcode.cn/problems/21dk04/?show=1) |
+| - | [剑指 Offer II 098. 路径的数目](https://leetcode.cn/problems/2AoeFn/?show=1) |
+| - | [剑指 Offer II 103. 最少的硬币数目](https://leetcode.cn/problems/gaM7Ch/?show=1) |
+
+> res = new LinkedList<>();
+LinkedList
> permuteRepeat(int[] nums) {
+ backtrack(nums);
+ return res;
+}
+
+// 回溯算法核心函数
+void backtrack(int[] nums) {
+ // base case,到达叶子节点
+ if (track.size() == nums.length) {
+ // 收集根到叶子节点路径上的值
+ res.add(new LinkedList(track));
+ return;
+ }
+
+ // 回溯算法标准框架
+ for (int i = 0; i < nums.length; i++) {
+ // 做选择
+ track.add(nums[i]);
+ // 进入下一层回溯树
+ backtrack(nums);
+ // 取消选择
+ track.removeLast();
+ }
+}
+```
+
+给这个函数输入 `nums = [1,2,3]`,输出是 3^3 = 27 种可能的组合:
+
+```java
+[
+ [1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],
+ [2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],
+ [3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]
+]
+```
+
+这段代码实际上就是遍历一棵高度为 `N + 1` 的满 `N` 叉树(`N` 为 `nums` 的长度),其中根到叶子的每条路径上的元素就是一个排列结果:
+
+![](https://labuladong.github.io/algo/images/单词拆分/1.jpeg)
+
+类比一下,本文讲的这道题也有异曲同工之妙,假设 `wordDict = ["a", "aa", "ab"], s = "aaab"`,想用 `wordDict` 中的单词拼出 `s`,其实也面对着类似的一棵 `M` 叉树,`M` 为 `wordDict` 中单词的个数,**你需要做的就是站在回溯树的每个节点上,看看哪个单词能够匹配 `s[i..]` 的前缀,从而判断应该往哪条树枝上走**:
+
+![](https://labuladong.github.io/algo/images/单词拆分/2.jpeg)
+
+然后,按照前文 [回溯算法框架详解](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版) 所说,你把 `backtrack` 函数理解成在回溯树上游走的一个指针,维护每个节点上的变量 `i`,即可遍历整棵回溯树,寻找出匹配 `s` 的组合。
+
+回溯算法解法代码如下:
+
+```java
+List
+引用本文的文章
-
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer 63. 股票的最大利润](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/?show=1) |
+
+
+引用本文的文章
-为啥这个问题要这样定义二维的 dp 数组呢?我们前文多次提到,**找状态转移需要归纳思维,说白了就是如何从已知的结果推出未知的部分**,这样定义容易归纳,容易发现状态转移关系。
+ - [动态规划设计:最长递增子序列](https://labuladong.github.io/article/fname.html?fname=动态规划设计:最长递增子序列)
+ - [如何判断回文链表](https://labuladong.github.io/article/fname.html?fname=判断回文链表)
+ - [对动态规划进行降维打击](https://labuladong.github.io/article/fname.html?fname=状态压缩技巧)
+ - [最优子结构原理和 dp 数组遍历方向](https://labuladong.github.io/article/fname.html?fname=最优子结构)
+ - [经典动态规划:最长公共子序列](https://labuladong.github.io/article/fname.html?fname=LCS)
-具体来说,如果我们想求 `dp[i][j]`,假设你知道了子问题 `dp[i+1][j-1]` 的结果(`s[i+1..j-1]` 中最长回文子序列的长度),你是否能想办法算出 `dp[i][j]` 的值(`s[i..j]` 中,最长回文子序列的长度)呢?
+
-![](../pictures/最长回文子序列/1.jpg)
-可以!这取决于 `s[i]` 和 `s[j]` 的字符:
-
-**如果它俩相等**,那么它俩加上 `s[i+1..j-1]` 中的最长回文子序列就是 `s[i..j]` 的最长回文子序列:
-
-![](../pictures/最长回文子序列/2.jpg)
-
-**如果它俩不相等**,说明它俩**不可能同时**出现在 `s[i..j]` 的最长回文子序列中,那么把它俩**分别**加入 `s[i+1..j-1]` 中,看看哪个子串产生的回文子序列更长即可:
-
-![](../pictures/最长回文子序列/3.jpg)
-
-以上两种情况写成代码就是这样:
-
-```java
-if (s[i] == s[j])
- // 它俩一定在最长回文子序列中
- dp[i][j] = dp[i + 1][j - 1] + 2;
-else
- // s[i+1..j] 和 s[i..j-1] 谁的回文子序列更长?
- dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
-```
-
-至此,状态转移方程就写出来了,根据 dp 数组的定义,我们要求的就是 `dp[0][n - 1]`,也就是整个 `s` 的最长回文子序列的长度。
-
-### 三、代码实现
-
-首先明确一下 base case,如果只有一个字符,显然最长回文子序列长度是 1,也就是 `dp[i][j] = 1 (i == j)`。
-
-因为 `i` 肯定小于等于 `j`,所以对于那些 `i > j` 的位置,根本不存在什么子序列,应该初始化为 0。
-
-另外,看看刚才写的状态转移方程,想求 `dp[i][j]` 需要知道 `dp[i+1][j-1]`,`dp[i+1][j]`,`dp[i][j-1]` 这三个位置;再看看我们确定的 base case,填入 dp 数组之后是这样:
-
-![](../pictures/最长回文子序列/4.jpg)
-
-**为了保证每次计算 `dp[i][j]`,左下右方向的位置已经被计算出来,只能斜着遍历或者反着遍历**:
-
-![](../pictures/最长回文子序列/5.jpg)
-
-我选择反着遍历,代码如下:
-
-```cpp
-int longestPalindromeSubseq(string s) {
- int n = s.size();
- // dp 数组全部初始化为 0
- vector
+引用本文的题目
-/* 返回一个大小为 2 的数组 arr
-arr[0] 表示不抢 root 的话,得到的最大钱数
-arr[1] 表示抢 root 的话,得到的最大钱数 */
-int[] dp(TreeNode root) {
- if (root == null)
- return new int[]{0, 0};
- int[] left = dp(root.left);
- int[] right = dp(root.right);
- // 抢,下家就不能抢了
- int rob = root.val + left[0] + right[0];
- // 不抢,下家可抢可不抢,取决于收益大小
- int not_rob = Math.max(left[0], left[1])
- + Math.max(right[0], right[1]);
-
- return new int[]{not_rob, rob};
-}
-```
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
-时间复杂度 O(N),空间复杂度只有递归函数堆栈所需的空间,不需要备忘录的额外空间。
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer II 089. 房屋偷盗](https://leetcode.cn/problems/Gu0c2T/?show=1) |
+| - | [剑指 Offer II 090. 环形房屋偷盗](https://leetcode.cn/problems/PzWKhm/?show=1) |
-你看他和我们的思路不一样,修改了递归函数的定义,略微修改了思路,使得逻辑自洽,依然得到了正确的答案,而且代码更漂亮。这就是我们前文「不同定义产生不同解法」所说过的动态规划问题的一个特性。
+
+引用本文的文章
+
+ - [一个方法团灭 LeetCode 股票买卖问题](https://labuladong.github.io/article/fname.html?fname=团灭股票问题)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [动态规划解题套路框架](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶)
+ - [经典动态规划:博弈问题](https://labuladong.github.io/article/fname.html?fname=动态规划之博弈问题)
+ - [经典动态规划:戳气球](https://labuladong.github.io/article/fname.html?fname=扎气球)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [115. Distinct Subsequences](https://leetcode.com/problems/distinct-subsequences/?show=1) | [115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/?show=1) |
+| [139. Word Break](https://leetcode.com/problems/word-break/?show=1) | [139. 单词拆分](https://leetcode.cn/problems/word-break/?show=1) |
+| [221. Maximal Square](https://leetcode.com/problems/maximal-square/?show=1) | [221. 最大正方形](https://leetcode.cn/problems/maximal-square/?show=1) |
+| [256. Paint House](https://leetcode.com/problems/paint-house/?show=1)🔒 | [256. 粉刷房子](https://leetcode.cn/problems/paint-house/?show=1)🔒 |
+| [63. Unique Paths II](https://leetcode.com/problems/unique-paths-ii/?show=1) | [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/?show=1) |
+| [91. Decode Ways](https://leetcode.com/problems/decode-ways/?show=1) | [91. 解码方法](https://leetcode.cn/problems/decode-ways/?show=1) |
+| - | [剑指 Offer II 091. 粉刷房子](https://leetcode.cn/problems/JEj789/?show=1) |
+| - | [剑指 Offer II 097. 子序列的数目](https://leetcode.cn/problems/21dk04/?show=1) |
+
+
- -
======其他语言代码====== ### python diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\203\214\345\214\205\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\203\214\345\214\205\351\227\256\351\242\230.md" index d5da15b..df66536 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\203\214\345\214\205\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\203\214\345\214\205\351\227\256\351\242\230.md" @@ -1,20 +1,21 @@ # 动态规划之背包问题 - -![](../pictures/souyisou.png) +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ **-----------** -本文有视频版:[0-1背包问题详解](https://www.bilibili.com/video/BV15B4y1P7X7/) +> 本文有视频版:[0-1背包问题详解](https://www.bilibili.com/video/BV15B4y1P7X7/) 后台天天有人问背包问题,这个问题其实不难啊,如果我们号动态规划系列的十几篇文章你都看过,借助框架,遇到背包问题可以说是手到擒来好吧。无非就是状态 + 选择,也没啥特别之处嘛。 @@ -22,6 +23,8 @@ 给你一个可装载重量为 `W` 的背包和 `N` 个物品,每个物品有重量和价值两个属性。其中第 `i` 个物品的重量为 `wt[i]`,价值为 `val[i]`,现在让你用这个背包装物品,最多能装的价值是多少? +![](https://labuladong.github.io/algo/images/knapsack/1.png) + 举个简单的例子,输入如下: ``` @@ -34,11 +37,11 @@ val = [4, 2, 3] 题目就是这么简单,一个典型的动态规划问题。这个题目中的物品不可以分割,要么装进包里,要么不装,不能说切成两块装一半。这就是 0-1 背包这个名词的来历。 -解决这个问题没有什么排序之类巧妙的方法,只能穷举所有可能,根据我们 [动态规划详解](https://labuladong.gitee.io/algo/) 中的套路,直接走流程就行了。 +解决这个问题没有什么排序之类巧妙的方法,只能穷举所有可能,根据我们 [动态规划详解](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶) 中的套路,直接走流程就行了。 ### 动规标准套路 -看来我得每篇动态规划文章都得重复一遍套路,历史文章中的动态规划问题都是按照下面的套路来的。 +看来每篇动态规划文章都得重复一遍套路,历史文章中的动态规划问题都是按照下面的套路来的。 **第一步要明确两点,「状态」和「选择」**。 @@ -55,7 +58,7 @@ for 状态1 in 状态1的所有取值: dp[状态1][状态2][...] = 择优(选择1,选择2...) ``` -PS:此框架出自历史文章 [团灭 LeetCode 股票问题](https://labuladong.gitee.io/algo/)。 +> PS:此框架出自历史文章 [团灭 LeetCode 股票问题](https://labuladong.github.io/article/fname.html?fname=团灭股票问题)。 **第二步要明确 `dp` 数组的定义**。 @@ -65,7 +68,7 @@ PS:此框架出自历史文章 [团灭 LeetCode 股票问题](https://labulado 比如说,如果 `dp[3][5] = 6`,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。 -PS:为什么要这么定义?便于状态转移,或者说这就是套路,记下来就行了。建议看一下我们的动态规划系列文章,几种套路都被扒得清清楚楚了。 +> PS:为什么要这么定义?便于状态转移,或者说这就是套路,记下来就行了。建议看一下我们的动态规划系列文章,几种套路都被扒得清清楚楚了。 根据这个定义,我们想求的最终答案就是 `dp[N][W]`。base case 就是 `dp[0][..] = dp[..][0] = 0`,因为没有物品或者背包没有空间的时候,能装的最大价值就是 0。 @@ -93,15 +96,15 @@ return dp[N][W] 先重申一下刚才我们的 `dp` 数组的定义: -`dp[i][w]` 表示:对于前 `i` 个物品,当前背包的容量为 `w` 时,这种情况下可以装下的最大价值是 `dp[i][w]`。 +`dp[i][w]` 表示:对于前 `i` 个物品(从 1 开始计数),当前背包的容量为 `w` 时,这种情况下可以装下的最大价值是 `dp[i][w]`。 **如果你没有把这第 `i` 个物品装入背包**,那么很显然,最大价值 `dp[i][w]` 应该等于 `dp[i-1][w]`,继承之前的结果。 -**如果你把这第 `i` 个物品装入了背包**,那么 `dp[i][w]` 应该等于 `dp[i-1][w - wt[i-1]] + val[i-1]`。 +**如果你把这第 `i` 个物品装入了背包**,那么 `dp[i][w]` 应该等于 `val[i-1] + dp[i-1][w - wt[i-1]]`。 -首先,由于 `i` 是从 1 开始的,所以 `val` 和 `wt` 的索引是 `i-1` 时表示第 `i` 个物品的价值和重量。 +首先,由于数组索引从 0 开始,而我们定义中的 `i` 是从 1 开始计数的,所以 `val[i-1]` 和 `wt[i-1]` 表示第 `i` 个物品的价值和重量。 -而 `dp[i-1][w - wt[i-1]]` 也很好理解:你如果装了第 `i` 个物品,就要寻求剩余重量 `w - wt[i-1]` 限制下的最大价值,加上第 `i` 个物品的价值 `val[i-1]`。 +你如果选择将第 `i` 个物品装进背包,那么第 `i` 个物品的价值 `val[i-1]` 肯定就到手了,接下来你就要在剩余容量 `w - wt[i-1]` 的限制下,在前 `i - 1` 个物品中挑选,求最大价值,即 `dp[i-1][w - wt[i-1]]`。 综上就是两种选择,我们都已经分析完毕,也就是写出来了状态转移方程,可以进一步细化代码: @@ -117,21 +120,23 @@ return dp[N][W] **最后一步,把伪码翻译成代码,处理一些边界情况**。 -我用 C++ 写的代码,把上面的思路完全翻译了一遍,并且处理了 `w - wt[i-1]` 可能小于 0 导致数组索引越界的问题: +我用 Java 写的代码,把上面的思路完全翻译了一遍,并且处理了 `w - wt[i-1]` 可能小于 0 导致数组索引越界的问题: -```cpp -int knapsack(int W, int N, vector- -
\ No newline at end of file + - [扫描线技巧:安排会议室](https://labuladong.github.io/article/fname.html?fname=安排会议室) + - [经典动态规划:子集背包问题](https://labuladong.github.io/article/fname.html?fname=背包子集) + - [经典动态规划:完全背包问题](https://labuladong.github.io/article/fname.html?fname=背包零钱) + - [经典回溯算法:集合划分问题](https://labuladong.github.io/article/fname.html?fname=集合划分) + +- -
======其他语言代码====== [435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/) diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" deleted file mode 100644 index 5d05644..0000000 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" +++ /dev/null @@ -1,332 +0,0 @@ -# 经典动态规划问题:高楼扔鸡蛋(进阶) - - - - -![](../pictures/souyisou.png) - -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ - -读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: - -[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/) - -**-----------** - -上篇文章聊了高楼扔鸡蛋问题,讲了一种效率不是很高,但是较为容易理解的动态规划解法。后台很多读者问如何更高效地解决这个问题,今天就谈两种思路,来优化一下这个问题,分别是二分查找优化和重新定义状态转移。 - -如果还不知道高楼扔鸡蛋问题的读者可以看下「经典动态规划:高楼扔鸡蛋」,那篇文章详解了题目的含义和基本的动态规划解题思路,请确保理解前文,因为今天的优化都是基于这个基本解法的。 - -二分搜索的优化思路也许是我们可以尽力尝试写出的,而修改状态转移的解法可能是不容易想到的,可以借此见识一下动态规划算法设计的玄妙,当做思维拓展。 - -### 二分搜索优化 - -之前提到过这个解法,核心是因为状态转移方程的单调性,这里可以具体展开看看。 - -首先简述一下原始动态规划的思路: - -1、暴力穷举尝试在所有楼层 `1 <= i <= N` 扔鸡蛋,每次选择尝试次数**最少**的那一层; - -2、每次扔鸡蛋有两种可能,要么碎,要么没碎; - -3、如果鸡蛋碎了,`F` 应该在第 `i` 层下面,否则,`F` 应该在第 `i` 层上面; - -4、鸡蛋是碎了还是没碎,取决于哪种情况下尝试次数**更多**,因为我们想求的是最坏情况下的结果。 - -核心的状态转移代码是这段: - -```python -# 当前状态为 K 个鸡蛋,面对 N 层楼 -# 返回这个状态下的最优结果 -def dp(K, N): - for 1 <= i <= N: - # 最坏情况下的最少扔鸡蛋次数 - res = min(res, - max( - dp(K - 1, i - 1), # 碎 - dp(K, N - i) # 没碎 - ) + 1 # 在第 i 楼扔了一次 - ) - return res -``` - -这个 for 循环就是下面这个状态转移方程的具体代码实现: - - - -![](../pictures/扔鸡蛋/formula1.png) - -如果能够理解这个状态转移方程,那么就很容易理解二分查找的优化思路。 - -首先我们根据 `dp(K, N)` 数组的定义(有 `K` 个鸡蛋面对 `N` 层楼,最少需要扔几次),**很容易知道 `K` 固定时,这个函数随着 `N` 的增加一定是单调递增的**,无论你策略多聪明,楼层增加测试次数一定要增加。 - -那么注意 `dp(K - 1, i - 1)` 和 `dp(K, N - i)` 这两个函数,其中 `i` 是从 1 到 `N` 单增的,如果我们固定 `K` 和 `N`,**把这两个函数看做关于 `i` 的函数,前者随着 `i` 的增加应该也是单调递增的,而后者随着 `i` 的增加应该是单调递减的**: - -![](../pictures/扔鸡蛋/2.jpg) - -这时候求二者的较大值,再求这些最大值之中的最小值,其实就是求这两条直线交点,也就是红色折线的最低点嘛。 - -我们前文「二分查找只能用来查找元素吗」讲过,二分查找的运用很广泛,形如下面这种形式的 for 循环代码: - -```java -for (int i = 0; i < n; i++) { - if (isOK(i)) - return i; -} -``` - -都很有可能可以运用二分查找来优化线性搜索的复杂度,回顾这两个 `dp` 函数的曲线,我们要找的最低点其实就是这种情况: - -```java -for (int i = 1; i <= N; i++) { - if (dp(K - 1, i - 1) == dp(K, N - i)) - return dp(K, N - i); -} -``` - -熟悉二分搜索的同学肯定敏感地想到了,这不就是相当于求 Valley(山谷)值嘛,可以用二分查找来快速寻找这个点的,直接看代码吧,整体的思路还是一样,只是加快了搜索速度: - -```python -def superEggDrop(self, K: int, N: int) -> int: - - memo = dict() - def dp(K, N): - if K == 1: return N - if N == 0: return 0 - if (K, N) in memo: - return memo[(K, N)] - - # for 1 <= i <= N: - # res = min(res, - # max( - # dp(K - 1, i - 1), - # dp(K, N - i) - # ) + 1 - # ) - - res = float('INF') - # 用二分搜索代替线性搜索 - lo, hi = 1, N - while lo <= hi: - mid = (lo + hi) // 2 - broken = dp(K - 1, mid - 1) # 碎 - not_broken = dp(K, N - mid) # 没碎 - # res = min(max(碎,没碎) + 1) - if broken > not_broken: - hi = mid - 1 - res = min(res, broken + 1) - else: - lo = mid + 1 - res = min(res, not_broken + 1) - - memo[(K, N)] = res - return res - - return dp(K, N) -``` - -这个算法的时间复杂度是多少呢?**动态规划算法的时间复杂度就是子问题个数 × 函数本身的复杂度**。 - -函数本身的复杂度就是忽略递归部分的复杂度,这里 `dp` 函数中用了一个二分搜索,所以函数本身的复杂度是 O(logN)。 - -子问题个数也就是不同状态组合的总数,显然是两个状态的乘积,也就是 O(KN)。 - -所以算法的总时间复杂度是 O(K\*N\*logN), 空间复杂度 O(KN)。效率上比之前的算法 O(KN^2) 要高效一些。 - -### 重新定义状态转移 - -前文「不同定义有不同解法」就提过,找动态规划的状态转移本就是见仁见智,比较玄学的事情,不同的状态定义可以衍生出不同的解法,其解法和复杂程度都可能有巨大差异。这里就是一个很好的例子。 - -再回顾一下我们之前定义的 `dp` 数组含义: - -```python -def dp(k, n) -> int -# 当前状态为 k 个鸡蛋,面对 n 层楼 -# 返回这个状态下最少的扔鸡蛋次数 -``` - -用 dp 数组表示的话也是一样的: - -```python -dp[k][n] = m -# 当前状态为 k 个鸡蛋,面对 n 层楼 -# 这个状态下最少的扔鸡蛋次数为 m -``` - -按照这个定义,就是**确定当前的鸡蛋个数和面对的楼层数,就知道最小扔鸡蛋次数**。最终我们想要的答案就是 `dp(K, N)` 的结果。 - -这种思路下,肯定要穷举所有可能的扔法的,用二分搜索优化也只是做了「剪枝」,减小了搜索空间,但本质思路没有变,还是穷举。 - -现在,我们稍微修改 `dp` 数组的定义,**确定当前的鸡蛋个数和最多允许的扔鸡蛋次数,就知道能够确定 `F` 的最高楼层数**。具体来说是这个意思: - -```python -dp[k][m] = n -# 当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋 -# 这个状态下,最坏情况下最多能确切测试一栋 n 层的楼 - -# 比如说 dp[1][7] = 7 表示: -# 现在有 1 个鸡蛋,允许你扔 7 次; -# 这个状态下最多给你 7 层楼, -# 使得你可以确定楼层 F 使得鸡蛋恰好摔不碎 -# (一层一层线性探查嘛) -``` - -这其实就是我们原始思路的一个「反向」版本,我们先不管这种思路的状态转移怎么写,先来思考一下这种定义之下,最终想求的答案是什么? - -我们最终要求的其实是扔鸡蛋次数 `m`,但是这时候 `m` 在状态之中而不是 `dp` 数组的结果,可以这样处理: - -```java -int superEggDrop(int K, int N) { - - int m = 0; - while (dp[K][m] < N) { - m++; - // 状态转移... - } - return m; -} -``` - -题目不是**给你 `K` 鸡蛋,`N` 层楼,让你求最坏情况下最少的测试次数 `m`** 吗?`while` 循环结束的条件是 `dp[K][m] == N`,也就是**给你 `K` 个鸡蛋,测试 `m` 次,最坏情况下最多能测试 `N` 层楼**。 - -注意看这两段描述,是完全一样的!所以说这样组织代码是正确的,关键就是状态转移方程怎么找呢?还得从我们原始的思路开始讲。之前的解法配了这样图帮助大家理解状态转移思路: - -![](../pictures/扔鸡蛋/1.jpg) - -这个图描述的仅仅是某一个楼层 `i`,原始解法还得线性或者二分扫描所有楼层,要求最大值、最小值。但是现在这种 `dp` 定义根本不需要这些了,基于下面两个事实: - -**1、无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上**。 - -**2、无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)**。 - -根据这个特点,可以写出下面的状态转移方程: - -`dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1` - -**`dp[k][m - 1]` 就是楼上的楼层数**,因为鸡蛋个数 `k` 不变,也就是鸡蛋没碎,扔鸡蛋次数 `m` 减一; - -**`dp[k - 1][m - 1]` 就是楼下的楼层数**,因为鸡蛋个数 `k` 减一,也就是鸡蛋碎了,同时扔鸡蛋次数 `m` 减一。 - -PS:这个 `m` 为什么要减一而不是加一?之前定义得很清楚,这个 `m` 是一个允许的次数上界,而不是扔了几次。 - -![](../pictures/扔鸡蛋/3.jpg) - -至此,整个思路就完成了,只要把状态转移方程填进框架即可: - -```java -int superEggDrop(int K, int N) { - // m 最多不会超过 N 次(线性扫描) - int[][] dp = new int[K + 1][N + 1]; - // base case: - // dp[0][..] = 0 - // dp[..][0] = 0 - // Java 默认初始化数组都为 0 - int m = 0; - while (dp[K][m] < N) { - m++; - for (int k = 1; k <= K; k++) - dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; - } - return m; -} -``` - -如果你还觉得这段代码有点难以理解,其实它就等同于这样写: - -```java -for (int m = 1; dp[K][m] < N; m++) - for (int k = 1; k <= K; k++) - dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; -``` - -看到这种代码形式就熟悉多了吧,因为我们要求的不是 `dp` 数组里的值,而是某个符合条件的索引 `m`,所以用 `while` 循环来找到这个 `m` 而已。 - -这个算法的时间复杂度是多少?很明显就是两个嵌套循环的复杂度 O(KN)。 - -另外注意到 `dp[m][k]` 转移只和左边和左上的两个状态有关,所以很容易优化成一维 `dp` 数组,这里就不写了。 - -### 还可以再优化 - -再往下就要用一些数学方法了,不具体展开,就简单提一下思路吧。 - -在刚才的思路之上,**注意函数 `dp(m, k)` 是随着 `m` 单增的,因为鸡蛋个数 `k` 不变时,允许的测试次数越多,可测试的楼层就越高**。 - -这里又可以借助二分搜索算法快速逼近 `dp[K][m] == N` 这个终止条件,时间复杂度进一步下降为 O(KlogN),我们可以设 `g(k, m) =`…… - -算了算了,打住吧。我觉得我们能够写出 O(K\*N\*logN) 的二分优化算法就行了,后面的这些解法呢,听个响鼓个掌就行了,把欲望限制在能力的范围之内才能拥有快乐! - -不过可以肯定的是,根据二分搜索代替线性扫描 `m` 的取值,代码的大致框架肯定是修改穷举 `m` 的 for 循环: - -```java -// 把线性搜索改成二分搜索 -// for (int m = 1; dp[K][m] < N; m++) -int lo = 1, hi = N; -while (lo < hi) { - int mid = (lo + hi) / 2; - if (... < N) { - lo = ... - } else { - hi = ... - } - - for (int k = 1; k <= K; k++) - // 状态转移方程 -} -``` - -简单总结一下吧,第一个二分优化是利用了 `dp` 函数的单调性,用二分查找技巧快速搜索答案;第二种优化是巧妙地修改了状态转移方程,简化了求解了流程,但相应的,解题逻辑比较难以想到;后续还可以用一些数学方法和二分搜索进一步优化第二种解法,不过看了看镜子中的发量,算了。 - -本文终,希望对你有一点启发。 - -**_____________** - -**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。 - -**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 - -- -
- -======其他语言代码====== - -[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/) - -### javascript - -```js -/** - * @param {number} K - * @param {number} N - * @return {number} - */ -var superEggDrop = function (K, N) { - // m 最多不会超过 N 次(线性扫描) - // 初始化一个 (K+1)(N+1) 的矩阵 - let dp = new Array(K + 1); - - // base case: - // dp[0][..] = 0 - // dp[..][0] = 0 - // 初始化数组都为 0 - for (let i = 0; i < K + 1; i++) { - dp[i] = new Array(N + 1); - dp[i].fill(0, 0, N + 1); - } - - let m = 0; - while (dp[K][m] < N) { - m++; - for (let k = 1; k <= K; k++) { - dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; - } - } - return m; -}; -``` - diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" index 05c089c..da6175a 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" @@ -1,33 +1,37 @@ # 经典动态规划问题:高楼扔鸡蛋 - - -![](../pictures/souyisou.png) +![](https://labuladong.github.io/algo/images/souyisou1.png) -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** -读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: -[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/) + +读完本文,你不仅学会了算法套路,还可以顺便解决如下题目: + +| LeetCode | 力扣 | 难度 | +| :----: | :----: | :----: | +| [887. Super Egg Drop](https://leetcode.com/problems/super-egg-drop/) | [887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | 🔴 **-----------** -今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼。国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋太浪费,改成扔杯子,扔破碗什么的。 +本文要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼。国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋太浪费,改成扔杯子,扔破碗什么的。 -具体的问题等会再说,但是这道题的解法技巧很多,光动态规划就好几种效率不同的思路,最后还有一种极其高效数学解法。秉承咱们号一贯的作风,拒绝奇技淫巧,拒绝过于诡异的技巧,因为这些技巧无法举一反三,学了也不划算。 +具体的问题等会再说,但是这道题的解法技巧很多,光动态规划就好几种效率不同的思路,最后还有一种极其高效数学解法。秉承本书一贯的作风,拒绝过于诡异的技巧,因为这些技巧无法举一反三,学了也不划算。 下面就来用我们一直强调的动态规划通用思路来研究一下这道题。 ### 一、解析题目 -题目是这样:你面前有一栋从 1 到 `N` 共 `N` 层的楼,然后给你 `K` 个鸡蛋(`K` 至少为 1)。现在确定这栋楼存在楼层 `0 <= F <= N`,在这层楼将鸡蛋扔下去,鸡蛋**恰好没摔碎**(高于 `F` 的楼层都会碎,低于 `F` 的楼层都不会碎)。现在问你,**最坏**情况下,你**至少**要扔几次鸡蛋,才能**确定**这个楼层 `F` 呢? +这是力扣第 887 题「鸡蛋掉落」,我描述一下题目: + +你面前有一栋从 1 到 `N` 共 `N` 层的楼,然后给你 `K` 个鸡蛋(`K` 至少为 1)。现在确定这栋楼存在楼层 `0 <= F <= N`,在这层楼将鸡蛋扔下去,鸡蛋**恰好没摔碎**(高于 `F` 的楼层都会碎,低于 `F` 的楼层都不会碎)。现在问你,**最坏**情况下,你**至少**要扔几次鸡蛋,才能**确定**这个楼层 `F` 呢? 也就是让你找摔不碎鸡蛋的最高楼层 `F`,但什么叫「最坏情况」下「至少」要扔几次呢?我们分别举个例子就明白了。 @@ -49,8 +53,6 @@ 以这种策略,**最坏**情况应该是试到第 7 层鸡蛋还没碎(`F = 7`),或者鸡蛋一直碎到第 1 层(`F = 0`)。然而无论那种最坏情况,只需要试 `log7` 向上取整等于 3 次,比刚才尝试 7 次要少,这就是所谓的**至少**要扔几次。 -PS:这有点像 Big O 表示法计算算法的复杂度。 - 实际上,如果不限制鸡蛋个数的话,二分思路显然可以得到最少尝试的次数,但问题是,**现在给你了鸡蛋个数的限制 `K`,直接使用二分思路就不行了**。 比如说只给你 1 个鸡蛋,7 层楼,你敢用二分吗?你直接去第 4 层扔一下,如果鸡蛋没碎还好,但如果碎了你就没有鸡蛋继续测试了,无法确定鸡蛋恰好摔不碎的楼层 `F` 了。这种情况下只能用线性扫描的方法,算法返回结果应该是 7。 @@ -59,205 +61,33 @@ PS:这有点像 Big O 表示法计算算法的复杂度。 很遗憾,并不是,比如说把楼层变高一些,100 层,给你 2 个鸡蛋,你在 50 层扔一下,碎了,那就只能线性扫描 1~49 层了,最坏情况下要扔 50 次。 -如果不要「二分」,变成「五分」「十分」都会大幅减少最坏情况下的尝试次数。比方说第一个鸡蛋每隔十层楼扔,在哪里碎了第二个鸡蛋一个个线性扫描,总共不会超过 20 次。 - -最优解其实是 14 次。最优策略非常多,而且并没有什么规律可言。 +如果不要「二分」,变成「五分」「十分」都会大幅减少最坏情况下的尝试次数。比方说第一个鸡蛋每隔十层楼扔,在哪里碎了第二个鸡蛋一个个线性扫描,总共不会超过 20 次。最优解其实是 14 次。最优策略非常多,而且并没有什么规律可言。 说了这么多废话,就是确保大家理解了题目的意思,而且认识到这个题目确实复杂,就连我们手算都不容易,如何用算法解决呢? -### 二、思路分析 - -对动态规划问题,直接套我们以前多次强调的框架即可:这个问题有什么「状态」,有什么「选择」,然后穷举。 - -**「状态」很明显,就是当前拥有的鸡蛋数 `K` 和需要测试的楼层数 `N`**。随着测试的进行,鸡蛋个数可能减少,楼层的搜索范围会减小,这就是状态的变化。 - -**「选择」其实就是去选择哪层楼扔鸡蛋**。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。 - -现在明确了「状态」和「选择」,**动态规划的基本思路就形成了**:肯定是个二维的 `dp` 数组或者带有两个状态参数的 `dp` 函数来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新状态: - -```python -# 当前状态为 K 个鸡蛋,面对 N 层楼 -# 返回这个状态下的最优结果 -def dp(K, N): - int res - for 1 <= i <= N: - res = min(res, 这次在第 i 层楼扔鸡蛋) - return res -``` - -这段伪码还没有展示递归和状态转移,不过大致的算法框架已经完成了。 - -我们选择在第 `i` 层楼扔了鸡蛋之后,可能出现两种情况:鸡蛋碎了,鸡蛋没碎。**注意,这时候状态转移就来了**: - -**如果鸡蛋碎了**,那么鸡蛋的个数 `K` 应该减一,搜索的楼层区间应该从 `[1..N]` 变为 `[1..i-1]` 共 `i-1` 层楼; - -**如果鸡蛋没碎**,那么鸡蛋的个数 `K` 不变,搜索的楼层区间应该从 `[1..N]` 变为 `[i+1..N]` 共 `N-i` 层楼。 - -![](../pictures/扔鸡蛋/1.jpg) - -PS:细心的读者可能会问,在第i层楼扔鸡蛋如果没碎,楼层的搜索区间缩小至上面的楼层,是不是应该包含第i层楼呀?不必,因为已经包含了。开头说了 F 是可以等于 0 的,向上递归后,第i层楼其实就相当于第 0 层,可以被取到,所以说并没有错误。 - -因为我们要求的是**最坏情况**下扔鸡蛋的次数,所以鸡蛋在第 `i` 层楼碎没碎,取决于那种情况的结果**更大**: - -```python -def dp(K, N): - for 1 <= i <= N: - # 最坏情况下的最少扔鸡蛋次数 - res = min(res, - max( - dp(K - 1, i - 1), # 碎 - dp(K, N - i) # 没碎 - ) + 1 # 在第 i 楼扔了一次 - ) - return res -``` - -递归的 base case 很容易理解:当楼层数 `N` 等于 0 时,显然不需要扔鸡蛋;当鸡蛋数 `K` 为 1 时,显然只能线性扫描所有楼层: - -```python -def dp(K, N): - if K == 1: return N - if N == 0: return 0 - ... -``` - -至此,其实这道题就解决了!只要添加一个备忘录消除重叠子问题即可: - -```python -def superEggDrop(K: int, N: int): - - memo = dict() - def dp(K, N) -> int: - # base case - if K == 1: return N - if N == 0: return 0 - # 避免重复计算 - if (K, N) in memo: - return memo[(K, N)] - - res = float('INF') - # 穷举所有可能的选择 - for i in range(1, N + 1): - res = min(res, - max( - dp(K, N - i), - dp(K - 1, i - 1) - ) + 1 - ) - # 记入备忘录 - memo[(K, N)] = res - return res - - return dp(K, N) -``` -这个算法的时间复杂度是多少呢?**动态规划算法的时间复杂度就是子问题个数 × 函数本身的复杂度**。 -函数本身的复杂度就是忽略递归部分的复杂度,这里 `dp` 函数中有一个 for 循环,所以函数本身的复杂度是 O(N)。 +- -
+![](https://labuladong.github.io/algo/images/qrcode.jpg) ======其他语言代码====== diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\255\224\345\241\224.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\255\224\345\241\224.md" new file mode 100644 index 0000000..2bb05dc --- /dev/null +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\255\224\345\241\224.md" @@ -0,0 +1,254 @@ +# 动态规划算法通关魔塔 + + + +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + + + +读完本文,你不仅学会了算法套路,还可以顺便解决如下题目: + +| LeetCode | 力扣 | 难度 | +| :----: | :----: | :----: | +| [174. Dungeon Game](https://leetcode.com/problems/dungeon-game/) | [174. 地下城游戏](https://leetcode.cn/problems/dungeon-game/) | 🔴 + +**-----------** + +「魔塔」是一款经典的地牢类游戏,碰怪物要掉血,吃血瓶能加血,你要收集钥匙,一层一层上楼,最后救出美丽的公主。 + +现在手机上仍然可以玩这个游戏: + +![](https://labuladong.github.io/algo/images/地下城/0.png) + +嗯,相信这款游戏承包了不少人的童年回忆,记得小时候,一个人拿着游戏机玩,两三个人围在左右指手画脚,这导致玩游戏的人体验极差,而左右的人异常快乐 😂 + +力扣第 174 题「地下城游戏」是一道类似的题目,我简单描述一下: + +输入一个存储着整数的二维数组 `grid`,如果 `grid[i][j] > 0`,说明这个格子装着血瓶,经过它可以增加对应的生命值;如果 `grid[i][j] == 0`,则这是一个空格子,经过它不会发生任何事情;如果 `grid[i][j] < 0`,说明这个格子有怪物,经过它会损失对应的生命值。 + +现在你是一名骑士,将会出现在最上角,公主被困在最右下角,你只能向右和向下移动,请问你初始至少需要多少生命值才能成功救出公主? + +**换句话说,就是问你至少需要多少初始生命值,能够让骑士从最左上角移动到最右下角,且任何时候生命值都要大于 0**。 + +函数签名如下: + +```java +int calculateMinimumHP(int[][] grid); +``` + +比如题目给我们举的例子,输入如下一个二维数组 `grid`,用 `K` 表示骑士,用 `P` 表示公主: + +![](https://labuladong.github.io/algo/images/地下城/1.png) + +算法应该返回 7,也就是说骑士的初始生命值**至少**为 7 时才能成功救出公主,行进路线如图中的箭头所示。 + +上篇文章 [最小路径和](https://labuladong.github.io/article/fname.html?fname=最小路径和) 写过类似的问题,问你从左上角到右下角的最小路径和是多少。 + +我们做算法题一定要尝试举一反三,感觉今天这道题和最小路径和有点关系对吧? + +想要最小化骑士的初始生命值,是不是意味着要最大化骑士行进路线上的血瓶?是不是相当于求「最大路径和」?是不是可以直接套用计算「最小路径和」的思路? + +但是稍加思考,发现这个推论并不成立,吃到最多的血瓶,并不一定就能获得最小的初始生命值。 + +比如如下这种情况,如果想要吃到最多的血瓶获得「最大路径和」,应该按照下图箭头所示的路径,初始生命值需要 11: + +![](https://labuladong.github.io/algo/images/地下城/2.png) + +但也很容易看到,正确的答案应该是下图箭头所示的路径,初始生命值只需要 1: + +![](https://labuladong.github.io/algo/images/地下城/3.png) + +**所以,关键不在于吃最多的血瓶,而是在于如何损失最少的生命值**。 + +这类求最值的问题,肯定要借助动态规划技巧,要合理设计 `dp` 数组/函数的定义。类比前文 [最小路径和问题](https://labuladong.github.io/article/fname.html?fname=最小路径和),`dp` 函数签名肯定长这样: + +```java +int dp(int[][] grid, int i, int j); +``` + +但是这道题对 `dp` 函数的定义比较有意思,按照常理,这个 `dp` 函数的定义应该是: + +**从左上角(`grid[0][0]`)走到 `grid[i][j]` 至少需要 `dp(grid, i, j)` 的生命值**。 + +这样定义的话,base case 就是 `i, j` 都等于 0 的时候,我们可以这样写代码: + +```java +int calculateMinimumHP(int[][] grid) { + int m = grid.length; + int n = grid[0].length; + // 我们想计算左上角到右下角所需的最小生命值 + return dp(grid, m - 1, n - 1); +} + +int dp(int[][] grid, int i, int j) { + // base case + if (i == 0 && j == 0) { + // 保证骑士落地不死就行了 + return gird[i][j] > 0 ? 1 : -grid[i][j] + 1; + } + ... +} +``` + +> **PS:为了简洁,之后 `dp(grid, i, j)` 就简写为 `dp(i, j)`,大家理解就好**。 + +接下来我们需要找状态转移了,还记得如何找状态转移方程吗?我们这样定义 `dp` 函数能否正确进行状态转移呢? + +我们希望 `dp(i, j)` 能够通过 `dp(i-1, j)` 和 `dp(i, j-1)` 推导出来,这样就能不断逼近 base case,也就能够正确进行状态转移。 + +具体来说,「到达 `A` 的最小生命值」应该能够由「到达 `B` 的最小生命值」和「到达 `C` 的最小生命值」推导出来: + +![](https://labuladong.github.io/algo/images/地下城/4.png) + +**但问题是,能推出来么?实际上是不能的**。 + +因为按照 `dp` 函数的定义,你只知道「能够从左上角到达 `B` 的最小生命值」,但并不知道「到达 `B` 时的生命值」。 + +「到达 `B` 时的生命值」是进行状态转移的必要参考,我给你举个例子你就明白了,假设下图这种情况: + +![](https://labuladong.github.io/algo/images/地下城/5.png) + +你说这种情况下,骑士救公主的最优路线是什么? + +显然是按照图中蓝色的线走到 `B`,最后走到 `A` 对吧,这样初始血量只需要 1 就可以;如果走黄色箭头这条路,先走到 `C` 然后走到 `A`,初始血量至少需要 6。 + +为什么会这样呢?骑士走到 `B` 和 `C` 的最少初始血量都是 1,为什么最后是从 `B` 走到 `A`,而不是从 `C` 走到 `A` 呢? + +因为骑士走到 `B` 的时候生命值为 11,而走到 `C` 的时候生命值依然是 1。 + +如果骑士执意要通过 `C` 走到 `A`,那么初始血量必须加到 6 点才行;而如果通过 `B` 走到 `A`,初始血量为 1 就够了,因为路上吃到血瓶了,生命值足够抗 `A` 上面怪物的伤害。 + +这下应该说的很清楚了,再回顾我们对 `dp` 函数的定义,上图的情况,算法只知道 `dp(1, 2) = dp(2, 1) = 1`,都是一样的,怎么做出正确的决策,计算出 `dp(2, 2)` 呢? + +**所以说,我们之前对 `dp` 数组的定义是错误的,信息量不足,算法无法做出正确的状态转移**。 + +正确的做法需要反向思考,依然是如下的 `dp` 函数: + +```java +int dp(int[][] grid, int i, int j); +``` + +但是我们要修改 `dp` 函数的定义: + +**从 `grid[i][j]` 到达终点(右下角)所需的最少生命值是 `dp(grid, i, j)`**。 + +那么可以这样写代码: + +```java +int calculateMinimumHP(int[][] grid) { + // 我们想计算左上角到右下角所需的最小生命值 + return dp(grid, 0, 0); +} + +int dp(int[][] grid, int i, int j) { + int m = grid.length; + int n = grid[0].length; + // base case + if (i == m - 1 && j == n - 1) { + return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; + } + ... +} +``` + +根据新的 `dp` 函数定义和 base case,我们想求 `dp(0, 0)`,那就应该试图通过 `dp(i, j+1)` 和 `dp(i+1, j)` 推导出 `dp(i, j)`,这样才能不断逼近 base case,正确进行状态转移。 + +具体来说,「从 `A` 到达右下角的最少生命值」应该由「从 `B` 到达右下角的最少生命值」和「从 `C` 到达右下角的最少生命值」推导出来: + +![](https://labuladong.github.io/algo/images/地下城/6.png) + +能不能推导出来呢?这次是可以的,假设 `dp(0, 1) = 5, dp(1, 0) = 4`,那么可以肯定要从 `A` 走向 `C`,因为 4 小于 5 嘛。 + +那么怎么推出 `dp(0, 0)` 是多少呢? + +假设 `A` 的值为 1,既然知道下一步要往 `C` 走,且 `dp(1, 0) = 4` 意味着走到 `grid[1][0]` 的时候至少要有 4 点生命值,那么就可以确定骑士出现在 `A` 点时需要 4 - 1 = 3 点初始生命值,对吧。 + +那如果 `A` 的值为 10,落地就能捡到一个大血瓶,超出了后续需求,4 - 10 = -6 意味着骑士的初始生命值为负数,这显然不可以,骑士的生命值小于 1 就挂了,所以这种情况下骑士的初始生命值应该是 1。 + +综上,状态转移方程已经推出来了: + +```java +int res = min( + dp(i + 1, j), + dp(i, j + 1) +) - grid[i][j]; + +dp(i, j) = res <= 0 ? 1 : res; +``` + +根据这个核心逻辑,加一个备忘录消除重叠子问题,就可以直接写出最终的代码了: + +```java +/* 主函数 */ +int calculateMinimumHP(int[][] grid) { + int m = grid.length; + int n = grid[0].length; + // 备忘录中都初始化为 -1 + memo = new int[m][n]; + for (int[] row : memo) { + Arrays.fill(row, -1); + } + + return dp(grid, 0, 0); +} + +// 备忘录,消除重叠子问题 +int[][] memo; + +/* 定义:从 (i, j) 到达右下角,需要的初始血量至少是多少 */ +int dp(int[][] grid, int i, int j) { + int m = grid.length; + int n = grid[0].length; + // base case + if (i == m - 1 && j == n - 1) { + return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; + } + if (i == m || j == n) { + return Integer.MAX_VALUE; + } + // 避免重复计算 + if (memo[i][j] != -1) { + return memo[i][j]; + } + // 状态转移逻辑 + int res = Math.min( + dp(grid, i, j + 1), + dp(grid, i + 1, j) + ) - grid[i][j]; + // 骑士的生命值至少为 1 + memo[i][j] = res <= 0 ? 1 : res; + + return memo[i][j]; +} +``` + +这就是自顶向下带备忘录的动态规划解法,参考前文 [动态规划套路详解](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶) 很容易就可以改写成 `dp` 数组的迭代解法,这里就不写了,读者可以尝试自己写一写。 + +这道题的核心是定义 `dp` 函数,找到正确的状态转移方程,从而计算出正确的答案。 + + + +@@ -13,7 +9,7 @@ ![](https://labuladong.github.io/algo/images/souyisou1.png) -**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。** +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** @@ -150,6 +146,20 @@ $ where connect.sh $ sudo /home/fdl/bin/connect.sh ``` + + +
- -
======其他语言代码====== \ No newline at end of file diff --git "a/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" "b/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" index f36a1f1..1561cf8 100644 --- "a/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" +++ "b/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" @@ -1,16 +1,17 @@ # Redis 入侵 - -![](../pictures/souyisou.png) +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ **-----------** @@ -20,7 +21,7 @@ 经过一番攀谈交心了解到,他跑了一个比较古老已经停止维护的开源项目,安装的旧版本的 Redis,而且他对 Linux 的使用不是很熟练。我就知道,他的服务器已经被攻陷了,想到也许还会有不少像我这位朋友的人,不重视操作系统的权限、防火墙的设置和数据库的保护,我就写一篇文章简单看看这种情况出现的原因,以及如何防范。 -PS:这种手法现在已经行不通了,因为新版本 Redis 都增加了 protect mode,增加了安全性,我们只能在本地简单模拟一下,就别乱试了。 +> PS:这种手法现在已经行不通了,因为新版本 Redis 都增加了 protect mode,增加了安全性,我们只能在本地简单模拟一下,就别乱试了。 ### 事件经过 @@ -46,29 +47,29 @@ Redis 监听的默认端口是 6379,我们设置它接收网卡 127.0.0.1 的 除了密码登录之外,还可以使用 RSA 密钥对登录,但是必须要把我的公钥存到 root 的家目录中 `/root/.ssh/authored_keys`。我们知道 `/root` 目录的权限设置是不允许任何其他用户闯入读写的: -![](../pictures/redis入侵/1.png) +![](https://labuladong.github.io/algo/images/redis入侵/1.png) 但是,我发现自己竟然可以直接访问 Redis: -![](../pictures/redis入侵/2.png) +![](https://labuladong.github.io/algo/images/redis入侵/2.png) 如果 Redis 是以 root 的身份运行的,那么我就可以通过操作 Redis,让它把我的公钥写到 root 的家目录中。Redis 有一种持久化方式是生成 RDB 文件,其中会包含原始数据。 我露出了邪恶的微笑,先把 Redis 中的数据全部清空,然后把我的 RSA 公钥写到数据库里,这里在开头和结尾加换行符目的是避免 RDB 文件生成过程中损坏到公钥字符串: -![](../pictures/redis入侵/3.png) +![](https://labuladong.github.io/algo/images/redis入侵/3.png) 命令 Redis 把生成的数据文件保存到 `/root/.ssh/` 中的 `authored_keys` 文件中: -![](../pictures/redis入侵/4.png) +![](https://labuladong.github.io/algo/images/redis入侵/4.png) 现在,root 的家目录中已经包含了我们的 RSA 公钥,我们现在可以通过密钥对登录进 root 了: -![](../pictures/redis入侵/5.png) +![](https://labuladong.github.io/algo/images/redis入侵/5.png) 看一下刚才写入 root 家的公钥: -![](../pictures/redis入侵/6.png) +![](https://labuladong.github.io/algo/images/redis入侵/6.png) 乱码是 GDB 文件的某种编码吧,但是中间的公钥被完整保存了,而且 ssh 登录程序竟然也识别了这段被乱码包围的公钥! @@ -94,14 +95,14 @@ Redis 监听的默认端口是 6379,我们设置它接收网卡 127.0.0.1 的 3、利用 rename 功能伪装 flushall 这种危险命令,以防被删库,丢失数据。 -**_____________** -**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。 -**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -- -
+ +**_____________** + +**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**: + +![](https://labuladong.github.io/algo/images/souyisou2.png) ======其他语言代码====== \ No newline at end of file diff --git "a/\346\212\200\346\234\257/session\345\222\214cookie.md" "b/\346\212\200\346\234\257/session\345\222\214cookie.md" index 57950b7..7216b77 100644 --- "a/\346\212\200\346\234\257/session\345\222\214cookie.md" +++ "b/\346\212\200\346\234\257/session\345\222\214cookie.md" @@ -1,16 +1,17 @@ # 一文读懂 session 和 cookie - -![](../pictures/souyisou.png) +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ **-----------** @@ -45,11 +46,11 @@ func cookie(w http.ResponseWriter, r *http.Request) { 当浏览器访问对应网址时,通过浏览器的开发者工具查看此次 HTTP 通信的细节,可以看见服务器的回应发出了两次 `SetCookie` 命令: -![](../pictures/session/1.png) +![](https://labuladong.github.io/algo/images/session/1.png) 在这之后,浏览器的请求中的 `Cookie` 字段就带上了这两个 cookie: -![](../pictures/session/2.png) +![](https://labuladong.github.io/algo/images/session/2.png) **cookie 的作用其实就是这么简单,无非就是服务器给每个客户端(浏览器)打的标签**,方便服务器辨认而已。当然,HTTP 还有很多参数可以设置 cookie,比如过期时间,或者让某个 cookie 只有某个特定路径才能使用等等。 @@ -69,7 +70,7 @@ session 就可以配合 cookie 解决这一问题,比如说一个 cookie 存 那如果我不让浏览器发送 cookie,每次都伪装成一个第一次来试用的小萌新,不就可以不断白嫖了么?浏览器会把网站的 cookie 以文件的形式存在某些地方(不同的浏览器配置不同),你把他们找到然后删除就行了。但是对于 Firefox 和 Chrome 浏览器,有很多插件可以直接编辑 cookie,比如我的 Chrome 浏览器就用的一款叫做 EditThisCookie 的插件,这是他们官网: -![http://www.editthiscookie.com/](../pictures/session/3.png) +![](https://labuladong.github.io/algo/images/session/3.png) 这类插件可以读取浏览器在当前网页的 cookie,点开插件可以任意编辑和删除 cookie。**当然,偶尔白嫖一两次还行,不鼓励高频率白嫖,想常用还是掏钱吧,否则网站赚不到钱,就只能取消免费试用这个机制了**。 @@ -79,7 +80,7 @@ session 就可以配合 cookie 解决这一问题,比如说一个 cookie 存 session 的原理不难,但是具体实现它可是很有技巧的,一般需要三个组件配合完成,它们分别是 `Manager`、`Provider` 和 `Session` 三个类(接口)。 -![](../pictures/session/4.jpg) +![](https://labuladong.github.io/algo/images/session/4.jpg) 1、浏览器通过 HTTP 协议向服务器请求路径 `/content` 的网页资源,对应路径上有一个 Handler 函数接收请求,解析 HTTP header 中的 cookie,得到其中存储的 sessionID,然后把这个 ID 发给 `Manager`。 @@ -93,7 +94,6 @@ session 的原理不难,但是具体实现它可是很有技巧的,一般需 **这就是设计层面的技巧了**,下面就来说说,为什么分成 `Manager`、`Provider` 和 `Session`。 - 先从最底层的 `Session` 说。既然 session 就是键值对,为啥不直接用哈希表,而是要抽象出这么一个数据结构呢? 第一,因为 `Session` 结构可能不止存储了一个哈希表,还可以存储一些辅助数据,比如 `sid`,访问次数,过期时间或者最后一次的访问时间,这样便于实现想 LRU、LFU 这样的算法。 @@ -115,7 +115,7 @@ type Session interface { 再说 `Provider` 为啥要抽象出来。我们上面那个图的 `Provider` 就是一个散列表,保存 `sid` 到 `Session` 的映射,但是实际中肯定会更加复杂。我们不是要时不时删除一些 session 吗,除了设置存活时间之外,还可以采用一些其他策略,比如 LRU 缓存淘汰算法,这样就需要 `Provider` 内部使用哈希链表这种数据结构来存储 session。 -PS:关于 LRU 算法的奥妙,参见前文「LRU 算法详解」。 +> PS:关于 LRU 算法的奥妙,参见前文 [LRU 算法详解](https://labuladong.github.io/article/fname.html?fname=LRU算法)。 因此,`Provider` 作为一个容器,就是要屏蔽算法细节,以合理的数据结构和算法组织 `sid` 和 `Session` 的映射关系,只需要实现下面这几个方法实现对 session 的增删查改: @@ -134,7 +134,6 @@ type Provider interface { } ``` - 最后说 `Manager`,大部分具体工作都委托给 `Session` 和 `Provider` 承担了,`Manager` 主要就是一个参数集合,比如 session 的存活时间,清理过期 session 的策略,以及 session 的可用存储方式。`Manager` 屏蔽了操作的具体细节,我们可以通过 `Manager` 灵活地配置 session 机制。 综上,session 机制分成几部分的最主要原因就是解耦,实现定制化。我在 Github 上看过几个 Go 语言实现的 session 服务,源码都很简单,有兴趣的朋友可以学习学习: @@ -143,14 +142,14 @@ https://github.com/alexedwards/scs https://github.com/astaxie/build-web-application-with-golang -**_____________** -**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。 -**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -- -
+ +**_____________** + +**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**: + +![](https://labuladong.github.io/algo/images/souyisou2.png) ======其他语言代码====== \ No newline at end of file diff --git "a/\346\212\200\346\234\257/\345\210\267\351\242\230\346\212\200\345\267\247.md" "b/\346\212\200\346\234\257/\345\210\267\351\242\230\346\212\200\345\267\247.md" index 2792179..9ad6d47 100644 --- "a/\346\212\200\346\234\257/\345\210\267\351\242\230\346\212\200\345\267\247.md" +++ "b/\346\212\200\346\234\257/\345\210\267\351\242\230\346\212\200\345\267\247.md" @@ -1,22 +1,21 @@ -# 刷题小技巧 - +# 算法笔试骗分套路 -![](../pictures/souyisou.png) +![](https://labuladong.github.io/algo/images/souyisou1.png) -**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~ +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** -**-----------** -相信每个人都有过被代码的小 bug 搞得心态爆炸的经历,本文分享一个我最常用的简单技巧,可以大幅提升刷题的幸福感。 -在这之前,首先回答一个问题,刷力扣题是直接在网页上刷比较好还是在本地 IDE 上刷比较好? +**-----------** + +首先回答一个问题,刷力扣题是直接在网页上刷比较好还是在本地 IDE 上刷比较好? 如果是牛客网笔试那种自己处理输入输出的判题形式,一定要在 IDE 上写,这个没啥说的,但**像力扣这种判题形式,我个人偏好直接在网页上刷**,原因有二: @@ -34,15 +33,121 @@ 如果平时练习的时候就习惯没有 IDE 的自动补全,习惯手写代码大脑编译,到时候面试的时候写代码就能更快更从容。 -之前我面快手的时候,有个面试官让我 [实现 LRU 算法](https://labuladong.gitee.io/algo/),我直接把双链表的实现、哈希链表的实现,在网页上全写出来了,而且一次无 bug 跑通,可以看到面试官惊讶的表情😂 +之前我面快手的时候,有个面试官让我 [实现 LRU 算法](https://labuladong.github.io/article/fname.html?fname=LRU算法),我直接把双链表的实现、哈希链表的实现,在网页上全写出来了,而且一次无 bug 跑通,可以看到面试官惊讶的表情😂 我秋招能当 offer 收割机,很大程度上就是因为手写算法这一关超出面试官的预期,其实都是因为之前在网页上刷题练出来的。 -接下来分享我觉得最常实用的干货技巧。 +当然,实在不想在网页上刷,也可以用我的 vscode 刷题插件或者 JetBrains 刷题插件,插件和我的网站内容都有完美的融合: + +![](https://labuladong.github.io/algo/images/others/全家桶.jpg) + +### 避实就虚 + +大家也知道,大部分笔试题目都需要你自己来处理输入数据,然后让程序打印输出。判题的底层原理是,把你程序的输出用 Linux 重定向符 `>` 写到文件里面,然后比较你的输出和正确答案是否相同。 + +那么有的问题难点就变得形同虚设,我们可以偷工减料,举个简化的例子,假设题目说给你输入一串用空格分隔的字符,告诉你这代表一个单链表,请你把这个单链表翻转,并且强调,一定要把输入的数字转化成单链表之后再翻转哦! + +那你怎么做?真就自己定义一个 `ListNode` 单链表节点类,然后再写代码把输入转化成一个单链表,然后再用让人头晕的指针操作去老老实实翻转单链表? + +搞清楚我们是来 AC 题目的,不是来学习算法思维的。正确的做法是直接把输入存到数组里,然后用 [双指针技巧](https://labuladong.github.io/article/fname.html?fname=双指针技巧) 几行代码给它翻转了,然后打印出来完事儿。 + +我就见过不少这种题目,比如题目说输入的是一个单链表,让我分组翻转链表,而且还特别强调要用递归实现,就是我们前文 [K 个一组翻转链表](https://labuladong.github.io/article/fname.html?fname=k个一组反转链表) 的算法。嗯,如果用数组进行翻转,两分钟就写出来了,嘿嘿。 + +还有我们前文 [扁平化嵌套列表](https://labuladong.github.io/article/fname.html?fname=nestInteger) 讲到的题目,思路很巧妙,但是在笔试中遇到时,输入是一个形如 `[1,[4,[6]]]` 的字符串,那直接用正则表达式把数字抽出来,就是一个扁平化的列表了…… + +### 巧用随机数 + +再说一个鸡贼的技巧,注意那些输出为「二值」的题目,二值就是类似布尔值,或者 0 和 1 这种组合有限的。 + +比如说很多题目是这样,巴拉巴拉给你说一堆条件,然后问你输入的数据能不能达成这些条件,如果能的话请输出 `YES`,不能的话输出 `NO`。 + +如果你会做当然好,如果不会做怎么办? + +首先这样提交一下: + +```java +public class Main { + public static void main(String[] args) { + System.out.println("YES"); + } +} +``` + +看下 case 通过率,假设是 60%,那么说明结果为 `YES` 有 60% 的概率,所以可以这样写代码: + +```java +public class Main { + public static void main(String[] args) { + // 60% 的概率输出 YES,40% 的概率输出 NO + System.out.println((new Random().nextInt() % 100) < 60 ? "YES" : "NO"); + } +} +``` + +嘿嘿,这题你可以不会,但是一定要在力所能及的范围内做到极致! + +### 编程语言的选择 + +仅从做算法题的角度来说,我个人比较建议使用 Java 作为笔试的编程语言。因为 JetBrain 家的 IntelliJ 实在是太香了,相比其他语言的编辑器,不仅有 `psvm` 和 `sout` 这样的快捷命令(你要是连这都不知道,赶紧面壁去),而且可以帮你检查出很多笔误,比如说 `while` 循环里面忘记递增变量,或者 `return` 语句错写到循环里这种由于疏忽所导致的问题。 + +C++ 也还行,但是我觉得没有 Java 好用。我印象中 C++ 连个分割字符串的 `split` 函数都没有,光这点我就不想用 C++ 了…… + +还有一点,C++ 代码对时间的限制高,别的语言时间限制 4000ms,C++ 限制 2000ms,我觉得挺吃亏的。怪不得看别人用 C++ 写算法,为了提高速度,都不用标准库的 `vector` 容器,非要用原始的 `int[]` 数组,我看着都头疼。 + +Python 的话我刷题用的比较少,因为我不太喜欢用动态语言,不好调试。不过这个语言的奇技淫巧太多,如果你深谙 Python 的套路,可以在某些时候投机取巧。比如说我们前文写到的 [表达式求值算法](https://labuladong.github.io/article/fname.html?fname=实现计算器) 是一个困难级别的算法,但如果用 Python 内置的 `exec` 函数,直接就能算出答案。 + +这个在笔试里肯定是很占便宜的,因为之前说了,我们要的是结果,没人在乎你是怎么得到结果的。 + +### 解法代码分层 + +代码分层应该算是一种比较好的习惯,可以增加写代码的速度和降低调试的难度。 + +简单说就是,不要把所有代码都写在 `main` 函数里面,我一直使用的套路是,`main` 函数负责接收数据,加一个 `solution` 函数负责统一处理数据和输出答案,然后再用诸如 `backtrack` 这样一个函数处理具体的算法逻辑。 + +举个例子,比如说一道题,我决定用带备忘录的动态规划求解,代码的大致结构是这样: + +```java +public class Main { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + // 主要负责接收数据 + int N = scanner.nextInt(); + int[][] orders = new int[N][2]; + for (int i = 0; i < N; i++) { + orders[i][0] = scanner.nextInt(); + orders[i][1] = scanner.nextInt(); + } + // 委托 solution 进行求解 + solution(orders); + } + + static void solution(int[][] orders) { + // 排除一些基本的边界情况 + if (orders.length == 0) { + System.out.println("None"); + return; + } + // 委托 dp 函数执行具体的算法逻辑 + int res = dp(orders, 0); + // 负责输出结果 + System.out.println(res); + } + + // 备忘录 + static HashMap- -
+- -
======其他语言代码====== \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/BST1.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/BST1.md" new file mode 100644 index 0000000..4e15c9f --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/BST1.md" @@ -0,0 +1,253 @@ +# 手把手带你刷二叉搜索树(第一期) + + + +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + + + +读完本文,你不仅学会了算法套路,还可以顺便解决如下题目: + +| LeetCode | 力扣 | 难度 | +| :----: | :----: | :----: | +| [1038. Binary Search Tree to Greater Sum Tree](https://leetcode.com/problems/binary-search-tree-to-greater-sum-tree/) | [1038. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) | 🟠 +| [230. Kth Smallest Element in a BST](https://leetcode.com/problems/kth-smallest-element-in-a-bst/) | [230. 二叉搜索树中第K小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | 🟠 +| [538. Convert BST to Greater Tree](https://leetcode.com/problems/convert-bst-to-greater-tree/) | [538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | 🟠 +| - | [剑指 Offer II 054. 所有大于等于节点的值之和](https://leetcode.cn/problems/w6cpku/) | 🟠 + +**-----------** + +PS:[刷题插件](https://mp.weixin.qq.com/s/OE1zPVPj0V2o82N4HtLQbw) 集成了手把手刷二叉树功能,按照公式和套路讲解了 150 道二叉树题目,可手把手带你刷完二叉树分类的题目,迅速掌握递归思维。 + +前文手把手带你刷二叉树已经写了 [第一期](https://labuladong.github.io/article/fname.html?fname=二叉树系列1),[第二期](https://labuladong.github.io/article/fname.html?fname=二叉树系列2) 和 [第三期](https://labuladong.github.io/article/fname.html?fname=二叉树系列3),今天写一篇二叉搜索树(Binary Search Tree,后文简写 BST)相关的文章,手把手带你刷 BST。 + +首先,BST 的特性大家应该都很熟悉了: + +1、对于 BST 的每一个节点 `node`,左子树节点的值都比 `node` 的值要小,右子树节点的值都比 `node` 的值大。 + +2、对于 BST 的每一个节点 `node`,它的左侧子树和右侧子树都是 BST。 + +二叉搜索树并不算复杂,但我觉得它可以算是数据结构领域的半壁江山,直接基于 BST 的数据结构有 AVL 树,红黑树等等,拥有了自平衡性质,可以提供 logN 级别的增删查改效率;还有 B+ 树,线段树等结构都是基于 BST 的思想来设计的。 + +**从做算法题的角度来看 BST,除了它的定义,还有一个重要的性质:BST 的中序遍历结果是有序的(升序)**。 + +也就是说,如果输入一棵 BST,以下代码可以将 BST 中每个节点的值升序打印出来: + +```java +void traverse(TreeNode root) { + if (root == null) return; + traverse(root.left); + // 中序遍历代码位置 + print(root.val); + traverse(root.right); +} +``` + +那么根据这个性质,我们来做两道算法题。 + +### 寻找第 K 小的元素 + +这是力扣第 230 题「二叉搜索树中第 K 小的元素」,看下题目: + +![](https://labuladong.github.io/algo/images/BST1/title.png) + +这个需求很常见吧,一个直接的思路就是升序排序,然后找第 `k` 个元素呗。BST 的中序遍历其实就是升序排序的结果,找第 `k` 个元素肯定不是什么难事。 + +按照这个思路,可以直接写出代码: + +```java +int kthSmallest(TreeNode root, int k) { + // 利用 BST 的中序遍历特性 + traverse(root, k); + return res; +} + +// 记录结果 +int res = 0; +// 记录当前元素的排名 +int rank = 0; +void traverse(TreeNode root, int k) { + if (root == null) { + return; + } + traverse(root.left, k); + /* 中序遍历代码位置 */ + rank++; + if (k == rank) { + // 找到第 k 小的元素 + res = root.val; + return; + } + /*****************/ + traverse(root.right, k); +} +``` + +这道题就做完了,不过呢,还是要多说几句,因为这个解法并不是最高效的解法,而是仅仅适用于这道题。 + +我们前文 [高效计算数据流的中位数](https://labuladong.github.io/article/fname.html?fname=数据流中位数) 中就提过今天的这个问题: + +> 如果让你实现一个在二叉搜索树中通过排名计算对应元素的方法 `select(int k)`,你会怎么设计? + +如果按照我们刚才说的方法,利用「BST 中序遍历就是升序排序结果」这个性质,每次寻找第 `k` 小的元素都要中序遍历一次,最坏的时间复杂度是 `O(N)`,`N` 是 BST 的节点个数。 + +要知道 BST 性质是非常牛逼的,像红黑树这种改良的自平衡 BST,增删查改都是 `O(logN)` 的复杂度,让你算一个第 `k` 小元素,时间复杂度竟然要 `O(N)`,有点低效了。 + +所以说,计算第 `k` 小元素,最好的算法肯定也是对数级别的复杂度,不过这个依赖于 BST 节点记录的信息有多少。 + +我们想一下 BST 的操作为什么这么高效?就拿搜索某一个元素来说,BST 能够在对数时间找到该元素的根本原因还是在 BST 的定义里,左子树小右子树大嘛,所以每个节点都可以通过对比自身的值判断去左子树还是右子树搜索目标值,从而避免了全树遍历,达到对数级复杂度。 + +那么回到这个问题,想找到第 `k` 小的元素,或者说找到排名为 `k` 的元素,如果想达到对数级复杂度,关键也在于每个节点得知道他自己排第几。 + +比如说你让我查找排名为 `k` 的元素,当前节点知道自己排名第 `m`,那么我可以比较 `m` 和 `k` 的大小: + +1、如果 `m == k`,显然就是找到了第 `k` 个元素,返回当前节点就行了。 + +2、如果 `k < m`,那说明排名第 `k` 的元素在左子树,所以可以去左子树搜索第 `k` 个元素。 + +3、如果 `k > m`,那说明排名第 `k` 的元素在右子树,所以可以去右子树搜索第 `k - m - 1` 个元素。 + +这样就可以将时间复杂度降到 `O(logN)` 了。 + +那么,如何让每一个节点知道自己的排名呢? + +这就是我们之前说的,需要在二叉树节点中维护额外信息。**每个节点需要记录,以自己为根的这棵二叉树有多少个节点**。 + +也就是说,我们 `TreeNode` 中的字段应该如下: + +```java +class TreeNode { + int val; + // 以该节点为根的树的节点总数 + int size; + TreeNode left; + TreeNode right; +} +``` + +有了 `size` 字段,外加 BST 节点左小右大的性质,对于每个节点 `node` 就可以通过 `node.left` 推导出 `node` 的排名,从而做到我们刚才说到的对数级算法。 + +当然,`size` 字段需要在增删元素的时候需要被正确维护,力扣提供的 `TreeNode` 是没有 `size` 这个字段的,所以我们这道题就只能利用 BST 中序遍历的特性实现了,但是我们上面说到的优化思路是 BST 的常见操作,还是有必要理解的。 + +### BST 转化累加树 + +力扣第 538 题和 1038 题都是这道题,完全一样,你可以把它们一块做掉。看下题目: + +![](https://labuladong.github.io/algo/images/BST1/title1.png) + +题目应该不难理解,比如图中的节点 5,转化成累加树的话,比 5 大的节点有 6,7,8,加上 5 本身,所以累加树上这个节点的值应该是 5+6+7+8=26。 + +我们需要把 BST 转化成累加树,函数签名如下: + +```java +TreeNode convertBST(TreeNode root) +``` + +按照二叉树的通用思路,需要思考每个节点应该做什么,但是这道题上很难想到什么思路。 + +BST 的每个节点左小右大,这似乎是一个有用的信息,既然累加和是计算大于等于当前值的所有元素之和,那么每个节点都去计算右子树的和,不就行了吗? + +这是不行的。对于一个节点来说,确实右子树都是比它大的元素,但问题是它的父节点也可能是比它大的元素呀?这个没法确定的,我们又没有触达父节点的指针,所以二叉树的通用思路在这里用不了。 + +**其实,正确的解法很简单,还是利用 BST 的中序遍历特性**。 + +刚才我们说了 BST 的中序遍历代码可以升序打印节点的值: + +```java +void traverse(TreeNode root) { + if (root == null) return; + traverse(root.left); + // 中序遍历代码位置 + print(root.val); + traverse(root.right); +} +``` + +那如果我想降序打印节点的值怎么办? + +很简单,只要把递归顺序改一下就行了: + +```java +void traverse(TreeNode root) { + if (root == null) return; + // 先递归遍历右子树 + traverse(root.right); + // 中序遍历代码位置 + print(root.val); + // 后递归遍历左子树 + traverse(root.left); +} +``` + +**这段代码可以降序打印 BST 节点的值,如果维护一个外部累加变量 `sum`,然后把 `sum` 赋值给 BST 中的每一个节点,不就将 BST 转化成累加树了吗**? + +看下代码就明白了: + +```java +TreeNode convertBST(TreeNode root) { + traverse(root); + return root; +} + +// 记录累加和 +int sum = 0; +void traverse(TreeNode root) { + if (root == null) { + return; + } + traverse(root.right); + // 维护累加和 + sum += root.val; + // 将 BST 转化成累加树 + root.val = sum; + traverse(root.left); +} +``` + +这道题就解决了,核心还是 BST 的中序遍历特性,只不过我们修改了递归顺序,降序遍历 BST 的元素值,从而契合题目累加树的要求。 + +简单总结下吧,BST 相关的问题,要么利用 BST 左小右大的特性提升算法效率,要么利用中序遍历的特性满足题目的要求,也就这么些事儿吧。 + +最后调查下,经过这几篇二叉树相关的系列文章,大家刷题有没有点感觉了?可以留言和我交流。本文对你有帮助的话,请三连~ + + + +@@ -13,7 +9,7 @@ ![](https://labuladong.github.io/algo/images/souyisou1.png) -**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。** +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** @@ -230,6 +226,42 @@ public Key delMax() { > 最后打个广告,我亲自制作了一门 [数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO),以视频课为主,手把手带你实现常用的数据结构及相关算法,旨在帮助算法基础较为薄弱的读者深入理解常用数据结构的底层原理,在算法学习中少走弯路。 + + +
- -
- - -======其他语言代码====== - -[100.相同的树](https://leetcode-cn.com/problems/same-tree) - -[450.删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst) - -[701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree) - -[700.二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree) - -[98.验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree) - -### c++ - -[dekunma](https://www.linkedin.com/in/dekun-ma-036a9b198/)提供第98题C++代码: - -```c++ -/** - * Definition for a binary tree node. - * struct TreeNode { - * int val; - * TreeNode *left; - * TreeNode *right; - * TreeNode(int x) : val(x), left(NULL), right(NULL) {} - * }; - */ -class Solution { -public: - bool isValidBST(TreeNode* root) { - // 用helper method求解 - return isValidBST(root, nullptr, nullptr); - } - - bool isValidBST(TreeNode* root, TreeNode* min, TreeNode* max) { - // base case, root为nullptr - if (!root) return true; - - // 不符合BST的条件 - if (min && root->val <= min->val) return false; - if (max && root->val >= max->val) return false; - - // 向左右子树分别递归求解 - return isValidBST(root->left, min, root) - && isValidBST(root->right, root, max); - } -}; -``` - - -[yanggg1997](https://github.com/yanggg1997)提供第100题C++代码: - -``` c++ -/** - * Definition for a binary tree node. - * struct TreeNode { - * int val; - * TreeNode *left; - * TreeNode *right; - * TreeNode() : val(0), left(nullptr), right(nullptr) {} - * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} - * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} - * }; - */ -class Solution { -public: - bool isSameTree(TreeNode* p, TreeNode* q) { - // 若当前节点均为空,则此处相同 - if(!p && !q) return true; - // 若当前节点在一棵树上有而另一棵树上为空,则两棵树不同 - if(!p && q) return false; - if(p && !q) return false; - // 若当前节点在两棵树上均存在。 - if(p->val != q->val) - { - return false; - } - else - { - // 向左右子树分别递归判断 - return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); - } - } -}; -``` - -### python - -[ChenjieXu](https://github.com/ChenjieXu)提供第98题Python3代码: - -```python -def isValidBST(self, root): - # 递归函数 - def helper(node, lower = float('-inf'), upper = float('inf')): - if not node: - return True - - val = node.val - if val <= lower or val >= upper: - return False - # 右节点 - if not helper(node.right, val, upper): - return False - # 左节点 - if not helper(node.left, lower, val): - return False - return True - - return helper(root) - -``` - -[lixiandea](https://github.com/lixiandea)提供第100题Python3代码: - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: - ''' - 当前节点值相等且树的子树相等,则树相等。 - 递归退出条件:两个节点存在一个节点为空 - ''' - if p == None: - if q == None: - return True - else: - return False - if q == None: - return False - # 当前节点相同且左子树和右子树分别相同 - return p.val==q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) -``` - - -[Edwenc](https://github.com/Edwenc) 提供 leetcode第450题的python3 代码: - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right - -class Solution: - def deleteNode(self, root: TreeNode, key: int) -> TreeNode: - # 如果没有树 直接返回None - if root == None: - return None - - # 如果要删除的结点 就是当前结点 - if root.val == key: - # 左子树为空 只有右子树需要被更新 直接返回 - if root.left == None: - return root.right - # 右子树为空 只有左子树需要被更新 直接返回 - if root.right== None: - return root.left - - # 找出此结点左子树的最大值 - # 用这个最大值 来代替当前结点 - # 再在左子树中递归地删除这个最大值结点 - big = self.getMax( root.left ) - root.val = big.val - root.left = self.deleteNode( root.left , big.val ) - - # 当前结点较大 它的左子树中需要删除节点 递归到左子树 - elif root.val > key: - root.left = self.deleteNode( root.left , key) - # 当前结点较小 它的右子树中需要删除节点 递归到右子树 - else: - root.right= self.deleteNode( root.right, key) - - return root - - # 辅助函数 - # 功能是找出此二叉搜索树中最大元素的结点 并返回此结点 - def getMax( self , node ): - # 一直找它的右子树 直到为空 - while node.right: - node = node.right - return node -``` - -### java -``` -/** -* 第【98】题的扩展解法: -* 对于BST,有一个重要的性质,即“BST的中序遍历是单调递增的”。抓住这个性质,我们可以通过中序遍历来判断该二叉树是不是BST。 -* 我们定义preNode节点表示上一个遍历的节点,在中序遍历的时候,比较当前节点和preNode节点的大小,一旦有节点小于或等于前一个节点,则不满足BST的规则,直接返回false,否则遍历结束,返回true。 -*/ -TreeNode preNode = null; -public boolean isValidBST(TreeNode root) { - if (root == null) return true; - - boolean leftRes = isValidBST(root.left); - - if (preNode != null && root.val <= preNode.val) { - return false; - } - preNode = root; - - boolean rightRes = isValidBST(root.right); - - return leftRes && rightRes; -} -``` - - - -### javascript - -1. 如何把二叉树所有的节点中的值加一? - -热热身,体会体会二叉树的递归思想。 - -```js -let plusOne = function(root) { - if (root == null) return; - root.val += 1; - - plusOne(root.left); - plusOne(root.right); -} -``` - - - -2. 如何判断两棵二叉树是否完全相同? - -[100.相同的树](https://leetcode-cn.com/problems/same-tree) - -```js -/** - * Definition for a binary tree node. - * function TreeNode(val) { - * this.val = val; - * this.left = this.right = null; - * } - */ -/** - * @param {TreeNode} p - * @param {TreeNode} q - * @return {boolean} - */ -var isSameTree = function(p, q) { - if(p == null && q == null) - return true; - if(p == null || q == null) - return false; - if(p.val != q.val) - return false; - return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); -}; -``` - - - -零、判断 BST 的合法性 - -[98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/) - -```js -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @return {boolean} - */ -var isValidBST = function (root) { - return helper(root, null, null); -}; - -var helper = function (root, min, max) { - if (root == null) return true; - if (min != null && root.val <= min.val) return false; - if (max != null && root.val >= max.val) return false; - return helper(root.left, min, root) - && helper(root.right, root, max); -} -``` - - - -一、在BST 中查找一个数是否存在 - -[700.二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree) - -```js -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @param {number} val - * @return {TreeNode} - */ -var searchBST = function(root, target) { - if (root == null) return null; - if (root.val === target) - return root; - if (root.val < target) - return searchBST(root.right, target); - if (root.val > target) - return searchBST(root.left, target); - // root 该做的事做完了,顺带把框架也完成了,妙 -}; -``` - - - -二、在 BST 中插入一个数 - -[701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree) - -```js -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @param {number} val - * @return {TreeNode} - */ -var insertIntoBST = function(root, val) { - // 找到空位置插入新节点 - if (root == null) return new TreeNode(val); - // if (root.val == val) - // BST 中一般不会插入已存在元素 - if (root.val < val) - root.right = insertIntoBST(root.right, val); - if (root.val > val) - root.left = insertIntoBST(root.left, val); - return root; -}; -``` - - - -三、在 BST 中删除一个数 - -[450.删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst) - -```js -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @param {number} key - * @return {TreeNode} - */ -var deleteNode = function(root, key) { - if (!root) return null - // if key > root.val, delete node in root.right. Otherwise delete node in root.left. - if (key > root.val) { - const rightNode = deleteNode(root.right, key) - root.right = rightNode - return root - } else if (key < root.val) { - const leftNode = deleteNode(root.left, key) - root.left = leftNode - return root - } else { - // now root.val === key - if (!root.left) { - return root.right - } - if (!root.right) { - return root.left - } - // 将删除元素的左下方元素替代删除元素; - // 将左下方元素的右侧最下方子元素衔接删除元素的右下方子元素; - const rightChild = root.right - let newRightChild = root.left - while (newRightChild.right) { - newRightChild = newRightChild.right - } - newRightChild.right = rightChild - return root.left - } -}; -``` diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" new file mode 100644 index 0000000..c1dc73b --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" @@ -0,0 +1,796 @@ +# 东哥手把手带你刷二叉树(纲领篇) + + + +![](https://labuladong.github.io/algo/images/souyisou1.png) + +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** + + + +读完本文,你不仅学会了算法套路,还可以顺便解决如下题目: + +| LeetCode | 力扣 | 难度 | +| :----: | :----: | :----: | +| [104. Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/) | [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | 🟢 +| [144. Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/) | [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | 🟢 +| [543. Diameter of Binary Tree](https://leetcode.com/problems/diameter-of-binary-tree/) | [543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | 🟢 +| - | [剑指 Offer 55 - I. 二叉树的深度](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) | 🟢 + +**-----------** + +> 本文有视频版:[二叉树/递归的框架思维(纲领篇)](https://www.bilibili.com/video/BV1nG411x77H/) + +PS:[刷题插件](https://mp.weixin.qq.com/s/OE1zPVPj0V2o82N4HtLQbw) 集成了手把手刷二叉树功能,按照公式和套路讲解了 150 道二叉树题目,可手把手带你刷完二叉树分类的题目,迅速掌握递归思维。 + +公众号历史文章的整个脉络都是按照 [学习数据结构和算法的框架思维](https://labuladong.github.io/article/fname.html?fname=学习数据结构和算法的高效方法) 提出的框架来构建的,其中着重强调了二叉树题目的重要性,所以把本文放在第一篇。 + +我刷了这么多年题,浓缩出二叉树算法的一个总纲放在这里,也许用词不是特别专业化,也没有什么教材会收录我的这些经验总结,但目前各个刷题平台的题库,没有一道二叉树题目能跳出本文划定的框架。如果你能发现一道题目和本文给出的框架不兼容,请留言告知我。 + +先在开头总结一下,二叉树解题的思维模式分两类: + +**1、是否可以通过遍历一遍二叉树得到答案**?如果可以,用一个 `traverse` 函数配合外部变量来实现,这叫「遍历」的思维模式。 + +**2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案**?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。 + +无论使用哪种思维模式,你都需要思考: + +**如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做**?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。 + +本文中会用题目来举例,但都是最最简单的题目,所以不用担心自己看不懂,我可以帮你从最简单的问题中提炼出所有二叉树题目的共性,并将二叉树中蕴含的思维进行升华,反手用到 [动态规划](https://labuladong.github.io/article/fname.html?fname=动态规划详解进阶),[回溯算法](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版),[分治算法](https://labuladong.github.io/article/fname.html?fname=分治算法),[图论算法](https://labuladong.github.io/article/fname.html?fname=图) 中去,这也是我一直强调框架思维的原因。希望你在学习了上述高级算法后,也能回头再来看看本文,会对它们有更深刻的认识。 + +首先,我还是要不厌其烦地强调一下二叉树这种数据结构及相关算法的重要性。 + +### 二叉树的重要性 + +举个例子,比如两个经典排序算法 [快速排序](https://labuladong.github.io/article/fname.html?fname=快速排序) 和 [归并排序](https://labuladong.github.io/article/fname.html?fname=归并排序),对于它俩,你有什么理解? + +**如果你告诉我,快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历,那么我就知道你是个算法高手了**。 + +为什么快速排序和归并排序能和二叉树扯上关系?我们来简单分析一下他们的算法思想和代码框架: + +快速排序的逻辑是,若要对 `nums[lo..hi]` 进行排序,我们先找一个分界点 `p`,通过交换元素使得 `nums[lo..p-1]` 都小于等于 `nums[p]`,且 `nums[p+1..hi]` 都大于 `nums[p]`,然后递归地去 `nums[lo..p-1]` 和 `nums[p+1..hi]` 中寻找新的分界点,最后整个数组就被排序了。 + +快速排序的代码框架如下: + +```java +void sort(int[] nums, int lo, int hi) { + /****** 前序遍历位置 ******/ + // 通过交换元素构建分界点 p + int p = partition(nums, lo, hi); + /************************/ + + sort(nums, lo, p - 1); + sort(nums, p + 1, hi); +} +``` + +先构造分界点,然后去左右子数组构造分界点,你看这不就是一个二叉树的前序遍历吗? + +再说说归并排序的逻辑,若要对 `nums[lo..hi]` 进行排序,我们先对 `nums[lo..mid]` 排序,再对 `nums[mid+1..hi]` 排序,最后把这两个有序的子数组合并,整个数组就排好序了。 + +归并排序的代码框架如下: + +```java +// 定义:排序 nums[lo..hi] +void sort(int[] nums, int lo, int hi) { + int mid = (lo + hi) / 2; + // 排序 nums[lo..mid] + sort(nums, lo, mid); + // 排序 nums[mid+1..hi] + sort(nums, mid + 1, hi); + + /****** 后序位置 ******/ + // 合并 nums[lo..mid] 和 nums[mid+1..hi] + merge(nums, lo, mid, hi); + /*********************/ +} +``` + +先对左右子数组排序,然后合并(类似合并有序链表的逻辑),你看这是不是二叉树的后序遍历框架?另外,这不就是传说中的分治算法嘛,不过如此呀。 + +如果你一眼就识破这些排序算法的底细,还需要背这些经典算法吗?不需要。你可以手到擒来,从二叉树遍历框架就能扩展出算法了。 + +说了这么多,旨在说明,二叉树的算法思想的运用广泛,甚至可以说,只要涉及递归,都可以抽象成二叉树的问题。 + +接下来我们从二叉树的前中后序开始讲起,让你深刻理解这种数据结构的魅力。 + +### 深入理解前中后序 + +我先甩给你几个问题,请默默思考 30 秒: + +1、你理解的二叉树的前中后序遍历是什么,仅仅是三个顺序不同的 List 吗? + +2、请分析,后序遍历有什么特殊之处? + +3、请分析,为什么多叉树没有中序遍历? + +答不上来,说明你对前中后序的理解仅仅局限于教科书,不过没关系,我用类比的方式解释一下我眼中的前中后序遍历。 + +首先,回顾一下 [学习数据结构和算法的框架思维](https://labuladong.github.io/article/fname.html?fname=学习数据结构和算法的高效方法) 中说到的二叉树遍历框架: + +```java +void traverse(TreeNode root) { + if (root == null) { + return; + } + // 前序位置 + traverse(root.left); + // 中序位置 + traverse(root.right); + // 后序位置 +} +``` + +先不管所谓前中后序,单看 `traverse` 函数,你说它在做什么事情? + +其实它就是一个能够遍历二叉树所有节点的一个函数,和你遍历数组或者链表本质上没有区别: + +```java +/* 迭代遍历数组 */ +void traverse(int[] arr) { + for (int i = 0; i < arr.length; i++) { + + } +} + +/* 递归遍历数组 */ +void traverse(int[] arr, int i) { + if (i == arr.length) { + return; + } + // 前序位置 + traverse(arr, i + 1); + // 后序位置 +} + +/* 迭代遍历单链表 */ +void traverse(ListNode head) { + for (ListNode p = head; p != null; p = p.next) { + + } +} + +/* 递归遍历单链表 */ +void traverse(ListNode head) { + if (head == null) { + return; + } + // 前序位置 + traverse(head.next); + // 后序位置 +} +``` + +单链表和数组的遍历可以是迭代的,也可以是递归的,**二叉树这种结构无非就是二叉链表**,由于没办法简单改写成迭代形式,所以一般说二叉树的遍历框架都是指递归的形式。 + +你也注意到了,只要是递归形式的遍历,都可以有前序位置和后序位置,分别在递归之前和递归之后。 + +**所谓前序位置,就是刚进入一个节点(元素)的时候,后序位置就是即将离开一个节点(元素)的时候**,那么进一步,你把代码写在不同位置,代码执行的时机也不同: + +![](https://labuladong.github.io/algo/images/二叉树收官/1.jpeg) + +比如说,如果让你**倒序打印**一条单链表上所有节点的值,你怎么搞? + +实现方式当然有很多,但如果你对递归的理解足够透彻,可以利用后序位置来操作: + +```java +/* 递归遍历单链表,倒序打印链表元素 */ +void traverse(ListNode head) { + if (head == null) { + return; + } + traverse(head.next); + // 后序位置 + print(head.val); +} +``` + +结合上面那张图,你应该知道为什么这段代码能够倒序打印单链表了吧,本质上是利用递归的堆栈帮你实现了倒序遍历的效果。 + +那么说回二叉树也是一样的,只不过多了一个中序位置罢了。 + +教科书里只会问你前中后序遍历结果分别是什么,所以对于一个只上过大学数据结构课程的人来说,他大概以为二叉树的前中后序只不过对应三种顺序不同的 `List- -
+ +@@ -15,7 +9,7 @@ ![](https://labuladong.github.io/algo/images/souyisou1.png) -**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。** +**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。** @@ -203,6 +197,43 @@ int[] nextGreaterElements(int[] nums) { 我会在 [单调栈的几种变体](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_628dc1ace4b09dda126cf793/1) 对比单调栈的几种其他形式,并在 [单调栈的运用](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_628dc2d7e4b0cedf38b67734/1) 中给出单调栈的经典例题。 + + +
@@ -15,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -270,6 +264,38 @@ class MonotonicQueue
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -309,6 +305,20 @@ def calculate(s: str) -> int:
**退而求其次是一种很聪明策略**。你想想啊,假设这是一道考试题,你不会实现这个计算器,但是你写了字符串转整数的算法并指出了容易溢出的陷阱,那起码可以得 20 分吧;如果你能够处理加减法,那可以得 40 分吧;如果你能处理加减乘除四则运算,那起码够 70 分了;再加上处理空格字符,80 有了吧。我就是不会处理括号,那就算了,80 已经很 OK 了好不好。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -294,6 +290,20 @@ public List
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -225,6 +221,35 @@ ListNode reverseBetween(ListNode head, int m, int n) {
> 最后打个广告,我亲自制作了一门 [数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO),以视频课为主,手把手带你实现常用的数据结构及相关算法,旨在帮助算法基础较为薄弱的读者深入理解常用数据结构的底层原理,在算法学习中少走弯路。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -227,6 +223,23 @@ public boolean empty() {
希望本文对你有帮助。
+
+
+
+
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -527,6 +523,41 @@ class UF {
最后,Union-Find 算法也会在一些其他经典图论算法中用到,比如判断「图」和「树」,以及最小生成树的计算,详情见 [Kruskal 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=kruskal)。
+
+
+
-
-
-
-
@@ -15,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -505,6 +499,58 @@ int right_bound(int[] nums, int target) {
理解本文能保证你写出正确的二分查找的代码,但实际题目中不会直接让你写二分代码,我会在 [二分查找的变体](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62a07736e4b01a485209b0b4/1) 和 [二分查找的运用](https://labuladong.github.io/article/fname.html?fname=二分运用) 中进一步讲解如何把二分思维运用到更多算法题中。
+
+
+
-
-
-
-
@@ -15,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -183,6 +177,56 @@ class NumMatrix {
除了本文举例的基本用法,前缀和数组经常和其他数据结构或算法技巧相结合,我会在 [前缀和技巧高频习题](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_627cd61de4b0cedf38b0f3a0/1) 中举例讲解。
+
+
+
-
-
-
-
@@ -15,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -385,6 +379,49 @@ String longestPalindrome(String s) {
到这里,数组相关的双指针技巧就全部讲完了,这些技巧的更多扩展延伸见 [更多双指针经典高频题](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62a1dd68e4b09dda1273a5f9/1)。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -333,6 +329,67 @@ def backtrack(...):
动态规划和回溯算法底层都把问题抽象成了树的结构,但这两种算法在思路上是完全不同的。在 [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结) 你将看到动态规划和回溯算法更深层次的区别和联系。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -98,6 +94,10 @@ string multiply(string num1, string num2) {
也许算法就是一种**寻找思维定式的思维**吧,希望本文对你有帮助。
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md"
index 034a123..33e4589 100644
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md"
+++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md"
@@ -1,22 +1,23 @@
# 学习算法和刷题的思路指南
-
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -247,6 +243,35 @@ int missingNumber(int[] nums) {
http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
+
+
+
@@ -11,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -204,6 +202,21 @@ for (int feq : count)
第二部分写了洗牌算法正确性的衡量标准,即每种随机结果出现的概率必须相等。如果我们不用严格的数学证明,可以通过蒙特卡罗方法大力出奇迹,粗略验证算法的正确性。蒙特卡罗方法也有不同的思路,不过要求不必太严格,因为我们只是寻求一个简单的验证。
+
+
+
-
-
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -146,6 +142,10 @@ void reverse(int[] arr, int i, int j) {
不妨分享一下你的思考。
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md"
deleted file mode 100644
index 40fe42f..0000000
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md"
+++ /dev/null
@@ -1,105 +0,0 @@
-# 算法学习之路
-
-
-
-
-
-
-
@@ -15,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -348,6 +342,37 @@ class LRUCache {
* [手把手带你实现 LFU 算法](https://labuladong.github.io/article/fname.html?fname=LFU)
+
+
+
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -155,6 +151,21 @@ ListNode reverseKGroup(ListNode head, int k) {
> 最后打个广告,我亲自制作了一门 [数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO),以视频课为主,手把手带你实现常用的数据结构及相关算法,旨在帮助算法基础较为薄弱的读者深入理解常用数据结构的底层原理,在算法学习中少走弯路。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -145,6 +141,22 @@ int bulbSwitch(int n) {
就算有的 `n` 平方根结果是小数,强转成 int 型,也相当于一个最大整数上界,比这个上界小的所有整数,平方后的索引都是最后亮着的灯的索引。所以说我们直接把平方根转成整数,就是这个问题的答案。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -230,6 +226,10 @@ int left_bound(ArrayList
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -265,6 +261,23 @@ p.next = reverse(q);
> 最后打个广告,我亲自制作了一门 [数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO),以视频课为主,手把手带你实现常用的数据结构及相关算法,旨在帮助算法基础较为薄弱的读者深入理解常用数据结构的底层原理,在算法学习中少走弯路。
+
+
+
+
-
-
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -922,6 +918,43 @@ void backtrack(int[] nums) {
如果你能够看到这里,真得给你鼓掌,相信你以后遇到各种乱七八糟的算法题,也能一眼看透它们的本质,以不变应万变。另外,考虑到篇幅,本文并没有对这些算法进行复杂度的分析,你可以使用我在 [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度) 讲到的复杂度分析方法尝试自己分析它们的复杂度。
+
+
+
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -228,6 +224,10 @@ private int distance(int[] intv) {
希望本文对大家有帮助。
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md"
index 64c7141..60794c3 100644
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md"
+++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md"
@@ -1,9 +1,5 @@
# 如何高效寻找素数
-
-
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -178,6 +174,33 @@ int countPrimes(int n) {
以上就是素数算法相关的全部内容。怎么样,是不是看似简单的问题却有不少细节可以打磨呀?
+
+
+
@@ -13,7 +11,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -278,6 +276,10 @@ if (height[left] < height[right]) {
至此,这道题也解决了。
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md"
deleted file mode 100644
index 41fef26..0000000
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md"
+++ /dev/null
@@ -1,246 +0,0 @@
-# 如何寻找最长回文子串
-
-
-
-
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -128,6 +124,23 @@ int[] getRandom(ListNode head, int k) {
答案见 [我的这篇文章](https://labuladong.github.io/article/fname.html?fname=随机集合)。
+
+
+
-
-
@@ -13,7 +9,7 @@
![](https://labuladong.github.io/algo/images/souyisou1.png)
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
@@ -25,7 +21,7 @@
**-----------**
-今天就聊一道很看起来简单却十分巧妙的问题,寻找缺失和重复的元素。之前的一篇文章 [常用的位操作](算法思维系列/常用的位操作.md) 中也写过类似的问题,不过这次的和上次的问题使用的技巧不同。
+今天就聊一道很看起来简单却十分巧妙的问题,寻找缺失和重复的元素。之前的一篇文章 [常用的位操作](https://labuladong.github.io/article/fname.html?fname=常用的位操作) 中也写过类似的问题,不过这次的和上次的问题使用的技巧不同。
这是力扣第 645 题「错误的集合」,我来描述一下这个题目:
@@ -132,6 +128,24 @@ int[] findErrorNums(int[] nums) {
异或运算也是常用的,因为异或性质 `a ^ a = 0, a ^ 0 = a`,如果将索引和元素同时异或,就可以消除成对儿的索引和元素,留下的就是重复或者缺失的元素。可以看看前文 [常用的位运算](https://labuladong.github.io/article/fname.html?fname=常用的位操作),介绍过这种方法。
+
+
+
+
+引用本文的文章
+
+ - [数据结构设计:最大栈](https://labuladong.github.io/article/fname.html?fname=最大栈)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1425. Constrained Subsequence Sum](https://leetcode.com/problems/constrained-subsequence-sum/?show=1) | [1425. 带限制的子序列和](https://leetcode.cn/problems/constrained-subsequence-sum/?show=1) |
+| [1696. Jump Game VI](https://leetcode.com/problems/jump-game-vi/?show=1) | [1696. 跳跃游戏 VI](https://leetcode.cn/problems/jump-game-vi/?show=1) |
+| [862. Shortest Subarray with Sum at Least K](https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/?show=1) | [862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/?show=1) |
+| [918. Maximum Sum Circular Subarray](https://leetcode.com/problems/maximum-sum-circular-subarray/?show=1) | [918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/?show=1) |
+| - | [剑指 Offer 59 - I. 滑动窗口的最大值](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/?show=1) |
+
+> allPathsSourceTarget(int[][] graph);
+```
+
+题目输入一幅**有向无环图**,这个图包含 `n` 个节点,标号为 `0, 1, 2,..., n - 1`,请你计算所有从节点 `0` 到节点 `n - 1` 的路径。
+
+输入的这个 `graph` 其实就是「邻接表」表示的一幅图,`graph[i]` 存储这节点 `i` 的所有邻居节点。
+
+比如输入 `graph = [[1,2],[3],[3],[]]`,就代表下面这幅图:
+
+![](https://labuladong.github.io/algo/images/图/1.jpg)
+
+算法应该返回 `[[0,1,3],[0,2,3]]`,即 `0` 到 `3` 的所有路径。
+
+**解法很简单,以 `0` 为起点遍历图,同时记录遍历过的路径,当遍历到终点时将路径记录下来即可**。
+
+既然输入的图是无环的,我们就不需要 `visited` 数组辅助了,直接套用图的遍历框架:
+
+```java
+// 记录所有路径
+List
> res = new LinkedList<>();
+
+public List
> allPathsSourceTarget(int[][] graph) {
+ // 维护递归过程中经过的路径
+ LinkedList
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [Prim 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=prim算法)
+ - [一文秒杀所有岛屿题目](https://labuladong.github.io/article/fname.html?fname=岛屿题目)
+ - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+ - [众里寻他千百度:名流问题](https://labuladong.github.io/article/fname.html?fname=名人问题)
+ - [前缀树算法模板秒杀五道算法题](https://labuladong.github.io/article/fname.html?fname=trie)
+ - [回溯算法解题套路框架](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [环检测及拓扑排序算法](https://labuladong.github.io/article/fname.html?fname=拓扑排序)
+ - [算法学习和心流体验](https://labuladong.github.io/article/fname.html?fname=心流)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [133. Clone Graph](https://leetcode.com/problems/clone-graph/?show=1) | [133. 克隆图](https://leetcode.cn/problems/clone-graph/?show=1) |
+| [200. Number of Islands](https://leetcode.com/problems/number-of-islands/?show=1) | [200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/?show=1) |
+| [2049. Count Nodes With the Highest Score](https://leetcode.com/problems/count-nodes-with-the-highest-score/?show=1) | [2049. 统计最高分的节点数目](https://leetcode.cn/problems/count-nodes-with-the-highest-score/?show=1) |
+| - | [剑指 Offer II 110. 所有路径](https://leetcode.cn/problems/bP4bmD/?show=1) |
+
+
+引用本文的文章
+
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\346\213\223\346\211\221\346\216\222\345\272\217.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\346\213\223\346\211\221\346\216\222\345\272\217.md"
new file mode 100644
index 0000000..e8957b1
--- /dev/null
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\346\213\223\346\211\221\346\216\222\345\272\217.md"
@@ -0,0 +1,607 @@
+# 拓扑排序详解及运用
+
+
+
+![](https://labuladong.github.io/algo/images/souyisou1.png)
+
+**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
+
+
+
+读完本文,你不仅学会了算法套路,还可以顺便解决如下题目:
+
+| LeetCode | 力扣 | 难度 |
+| :----: | :----: | :----: |
+| [207. Course Schedule](https://leetcode.com/problems/course-schedule/) | [207. 课程表](https://leetcode.cn/problems/course-schedule/) | 🟠
+| [210. Course Schedule II](https://leetcode.com/problems/course-schedule-ii/) | [210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | 🟠
+| - | [剑指 Offer II 113. 课程顺序](https://leetcode.cn/problems/QA2IGt/) | 🟠
+
+**-----------**
+
+> 本文有视频版:[拓扑排序详解及应用](https://www.bilibili.com/video/BV1kW4y1y7Ew/)
+
+图这种数据结构有一些比较特殊的算法,比如二分图判断,有环图无环图的判断,拓扑排序,以及最经典的最小生成树,单源最短路径问题,更难的就是类似网络流这样的问题。
+
+不过以我的经验呢,像网络流这种问题,你又不是打竞赛的,没时间的话就没必要学了;像 [最小生成树](https://labuladong.github.io/article/fname.html?fname=prim算法) 和 [最短路径问题](https://labuladong.github.io/article/fname.html?fname=dijkstra算法),虽然从刷题的角度用到的不多,但它们属于经典算法,学有余力可以掌握一下;像 [二分图判定](https://labuladong.github.io/article/fname.html?fname=二分图)、拓扑排序这一类,属于比较基本且有用的算法,应该比较熟练地掌握。
+
+**那么本文就结合具体的算法题,来说两个图论算法:有向图的环检测、拓扑排序算法**。
+
+这两个算法既可以用 DFS 思路解决,也可以用 BFS 思路解决,相对而言 BFS 解法从代码实现上看更简洁一些,但 DFS 解法有助于你进一步理解递归遍历数据结构的奥义,所以本文中我先讲 DFS 遍历的思路,再讲 BFS 遍历的思路。
+
+### 环检测算法(DFS 版本)
+
+先来看看力扣第 207 题「课程表」:
+
+![](https://labuladong.github.io/algo/images/拓扑排序/title1.jpg)
+
+函数签名如下:
+
+```java
+boolean canFinish(int numCourses, int[][] prerequisites);
+```
+
+题目应该不难理解,什么时候无法修完所有课程?当存在循环依赖的时候。
+
+其实这种场景在现实生活中也十分常见,比如我们写代码 import 包也是一个例子,必须合理设计代码目录结构,否则会出现循环依赖,编译器会报错,所以编译器实际上也使用了类似算法来判断你的代码是否能够成功编译。
+
+**看到依赖问题,首先想到的就是把问题转化成「有向图」这种数据结构,只要图中存在环,那就说明存在循环依赖**。
+
+具体来说,我们首先可以把课程看成「有向图」中的节点,节点编号分别是 `0, 1, ..., numCourses-1`,把课程之间的依赖关系看做节点之间的有向边。
+
+比如说必须修完课程 `1` 才能去修课程 `3`,那么就有一条有向边从节点 `1` 指向 `3`。
+
+所以我们可以根据题目输入的 `prerequisites` 数组生成一幅类似这样的图:
+
+![](https://labuladong.github.io/algo/images/拓扑排序/1.jpeg)
+
+**如果发现这幅有向图中存在环,那就说明课程之间存在循环依赖,肯定没办法全部上完;反之,如果没有环,那么肯定能上完全部课程**。
+
+好,那么想解决这个问题,首先我们要把题目的输入转化成一幅有向图,然后再判断图中是否存在环。
+
+如何转换成图呢?我们前文 [图论基础](https://labuladong.github.io/article/fname.html?fname=图) 写过图的两种存储形式,邻接矩阵和邻接表。
+
+以我刷题的经验,常见的存储方式是使用邻接表,比如下面这种结构:
+
+```java
+List
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [Kruskal 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=kruskal)
+ - [Prim 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=prim算法)
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+ - [图论基础及遍历算法](https://labuladong.github.io/article/fname.html?fname=图)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer II 113. 课程顺序](https://leetcode.cn/problems/QA2IGt/?show=1) |
+
+
+引用本文的文章
+
+ - [数据结构设计:最大栈](https://labuladong.github.io/article/fname.html?fname=最大栈)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md"
index bfd19e0..9854f0e 100644
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md"
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md"
@@ -1,9 +1,5 @@
# 递归反转链表的一部分
-
-
-
-
+引用本文的文章
+
+ - [如何判断回文链表](https://labuladong.github.io/article/fname.html?fname=判断回文链表)
+ - [烧饼排序算法](https://labuladong.github.io/article/fname.html?fname=烧饼排序)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer 24. 反转链表](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/?show=1) |
+| - | [剑指 Offer II 024. 反转链表](https://leetcode.cn/problems/UHnkqh/?show=1) |
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer 09. 用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/?show=1) |
+
+
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [Prim 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=prim算法)
+ - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+ - [二叉树的递归转迭代的代码框架](https://labuladong.github.io/article/fname.html?fname=迭代遍历二叉树)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [如何用 BFS 算法秒杀各种智力题](https://labuladong.github.io/article/fname.html?fname=BFS解决滑动拼图)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [旅游省钱大法:加权最短路径](https://labuladong.github.io/article/fname.html?fname=旅行最短路径)
+ - [环检测及拓扑排序算法](https://labuladong.github.io/article/fname.html?fname=拓扑排序)
+ - [算法学习和心流体验](https://labuladong.github.io/article/fname.html?fname=心流)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [102. Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/?show=1) | [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/?show=1) |
+| [117. Populating Next Right Pointers in Each Node II](https://leetcode.com/problems/populating-next-right-pointers-in-each-node-ii/?show=1) | [117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/?show=1) |
+| [431. Encode N-ary Tree to Binary Tree](https://leetcode.com/problems/encode-n-ary-tree-to-binary-tree/?show=1)🔒 | [431. 将 N 叉树编码为二叉树](https://leetcode.cn/problems/encode-n-ary-tree-to-binary-tree/?show=1)🔒 |
+| [773. Sliding Puzzle](https://leetcode.com/problems/sliding-puzzle/?show=1) | [773. 滑动谜题](https://leetcode.cn/problems/sliding-puzzle/?show=1) |
+| [863. All Nodes Distance K in Binary Tree](https://leetcode.com/problems/all-nodes-distance-k-in-binary-tree/?show=1) | [863. 二叉树中所有距离为 K 的结点](https://leetcode.cn/problems/all-nodes-distance-k-in-binary-tree/?show=1) |
+| - | [剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/?show=1) |
+| - | [剑指 Offer II 109. 开密码锁](https://leetcode.cn/problems/zlDJc7/?show=1) |
+
+
+引用本文的文章
+
+ - [BFS 算法解题套路框架](https://labuladong.github.io/article/fname.html?fname=BFS框架)
+
+
+
+
+
+
+
+**_____________**
+
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
\ No newline at end of file
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md"
deleted file mode 100644
index 5a0c797..0000000
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md"
+++ /dev/null
@@ -1,326 +0,0 @@
-# FloodFill算法详解及应用
-
-
-
-
-
-![](https://labuladong.github.io/algo/images/souyisou1.png)
-
-**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V1.9,[第 11 期刷题打卡挑战(9/19 开始)](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 开始报名。**
-
-
-
-读完本文,你不仅学会了算法套路,还可以顺便解决如下题目:
-
-| LeetCode | 力扣 | 难度 |
-| :----: | :----: | :----: |
-| [733. Flood Fill](https://leetcode.com/problems/flood-fill/) | [733. 图像渲染](https://leetcode.cn/problems/flood-fill/) | 🟢
-
-**-----------**
-
-啥是 FloodFill 算法呢,最直接的一个应用就是「颜色填充」,就是 Windows 绘画本中那个小油漆桶的标志,可以把一块被圈起来的区域全部染色。
-
-![](https://labuladong.github.io/algo/images/floodfill/floodfill.gif)
-
-这种算法思想还在许多其他地方有应用。比如说扫雷游戏,有时候你点一个方格,会一下子展开一片区域,这个展开过程,就是 FloodFill 算法实现的。
-
-![](https://labuladong.github.io/algo/images/floodfill/扫雷.png)
-
-类似的,像消消乐这类游戏,相同方块积累到一定数量,就全部消除,也是 FloodFill 算法的功劳。
-
-![](https://labuladong.github.io/algo/images/floodfill/xiaoxiaole.jpg)
-
-通过以上的几个例子,你应该对 FloodFill 算法有个概念了,现在我们要抽象问题,提取共同点。
-
-### 一、构建框架
-
-以上几个例子,都可以抽象成一个二维矩阵(图片其实就是像素点矩阵),然后从某个点开始向四周扩展,直到无法再扩展为止。
-
-矩阵,可以抽象为一幅「图」,这就是一个图的遍历问题,也就类似一个 N 叉树遍历的问题。几行代码就能解决,直接上框架吧:
-
-```java
-// (x, y) 为坐标位置
-void fill(int x, int y) {
- fill(x - 1, y); // 上
- fill(x + 1, y); // 下
- fill(x, y - 1); // 左
- fill(x, y + 1); // 右
-}
-```
-
-这个框架可以解决所有在二维矩阵中遍历的问题,说得高端一点,这就叫深度优先搜索(Depth First Search,简称 DFS),说得简单一点,这就叫四叉树遍历框架。坐标 (x, y) 就是 root,四个方向就是 root 的四个子节点。
-
-下面看一道 LeetCode 题目,其实就是让我们来实现一个「颜色填充」功能。
-
-![](https://labuladong.github.io/algo/images/floodfill/leetcode.png)
-
-根据上篇文章,我们讲了「树」算法设计的一个总路线,今天就可以用到:
-
-```java
-int[][] floodFill(int[][] image,
- int sr, int sc, int newColor) {
-
- int origColor = image[sr][sc];
- fill(image, sr, sc, origColor, newColor);
- return image;
-}
-
-void fill(int[][] image, int x, int y,
- int origColor, int newColor) {
- // 出界:超出边界索引
- if (!inArea(image, x, y)) return;
- // 碰壁:遇到其他颜色,超出 origColor 区域
- if (image[x][y] != origColor) return;
- image[x][y] = newColor;
-
- fill(image, x, y + 1, origColor, newColor);
- fill(image, x, y - 1, origColor, newColor);
- fill(image, x - 1, y, origColor, newColor);
- fill(image, x + 1, y, origColor, newColor);
-}
-
-boolean inArea(int[][] image, int x, int y) {
- return x >= 0 && x < image.length
- && y >= 0 && y < image[0].length;
-}
-```
-
-只要你能够理解这段代码,一定要给你鼓掌,给你 99 分,因为你对「框架思维」的掌控已经炉火纯青,此算法已经 cover 了 99% 的情况,仅有一个细节问题没有解决,就是当 origColor 和 newColor 相同时,会陷入无限递归。
-
-### 二、研究细节
-
-为什么会陷入无限递归呢,很好理解,因为每个坐标都要搜索上下左右,那么对于一个坐标,一定会被上下左右的坐标搜索。**被重复搜索时,必须保证递归函数能够能正确地退出,否则就会陷入死循环**。
-
-为什么 newColor 和 origColor 不同时可以正常退出呢?把算法流程画个图理解一下:
-
-![](https://labuladong.github.io/algo/images/floodfill/ppt1.PNG)
-
-可以看到,fill(1, 1) 被重复搜索了,我们用 fill(1, 1)* 表示这次重复搜索。fill(1, 1)* 执行时,(1, 1) 已经被换成了 newColor,所以 fill(1, 1)* 会在这个 if 语句被怼回去,正确退出了。
-
-```java
-// 碰壁:遇到其他颜色,超出 origColor 区域
-if (image[x][y] != origColor) return;
-```
-![](https://labuladong.github.io/algo/images/floodfill/ppt2.PNG)
-
-但是,如果说 origColor 和 newColor 一样,这个 if 语句就无法让 fill(1, 1)* 正确退出,而是开启了下面的重复递归,形成了死循环。
-
-![](https://labuladong.github.io/algo/images/floodfill/ppt3.PNG)
-
-### 三、处理细节
-
-如何避免上述问题的发生,最容易想到的就是用一个和 image 一样大小的二维 bool 数组记录走过的地方,一旦发现重复立即 return。
-
-```java
- // 出界:超出边界索引
-if (!inArea(image, x, y)) return;
-// 碰壁:遇到其他颜色,超出 origColor 区域
-if (image[x][y] != origColor) return;
-// 不走回头路
-if (visited[x][y]) return;
-visited[x][y] = true;
-image[x][y] = newColor;
-```
-
-完全 OK,这也是处理「图」的一种常用手段。不过对于此题,不用开数组,我们有一种更好的方法,那就是回溯算法。
-
-前文 [回溯算法框架套路](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版)讲过,这里不再赘述,直接套回溯算法框架:
-
-```java
-void fill(int[][] image, int x, int y,
- int origColor, int newColor) {
- // 出界:超出数组边界
- if (!inArea(image, x, y)) return;
- // 碰壁:遇到其他颜色,超出 origColor 区域
- if (image[x][y] != origColor) return;
- // 已探索过的 origColor 区域
- if (image[x][y] == -1) return;
-
- // choose:打标记,以免重复
- image[x][y] = -1;
- fill(image, x, y + 1, origColor, newColor);
- fill(image, x, y - 1, origColor, newColor);
- fill(image, x - 1, y, origColor, newColor);
- fill(image, x + 1, y, origColor, newColor);
- // unchoose:将标记替换为 newColor
- image[x][y] = newColor;
-}
-```
-
-这种解决方法是最常用的,相当于使用一个特殊值 -1 代替 visited 数组的作用,达到不走回头路的效果。为什么是 -1,因为题目中说了颜色取值在 0 - 65535 之间,所以 -1 足够特殊,能和颜色区分开。
-
-### 四、拓展延伸:自动魔棒工具和扫雷
-
-大部分图片编辑软件一定有「自动魔棒工具」这个功能:点击一个地方,帮你自动选中相近颜色的部分。如下图,我想选中老鹰,可以先用自动魔棒选中蓝天背景,然后反向选择,就选中了老鹰。我们来分析一下自动魔棒工具的原理。
-
-![](https://labuladong.github.io/algo/images/floodfill/抠图.jpg)
-
-显然,这个算法肯定是基于 FloodFill 算法的,但有两点不同:首先,背景色是蓝色,但不能保证都是相同的蓝色,毕竟是像素点,可能存在肉眼无法分辨的深浅差异,而我们希望能够忽略这种细微差异。第二,FloodFill 算法是「区域填充」,这里更像「边界填充」。
-
-对于第一个问题,很好解决,可以设置一个阈值 threshold,在阈值范围内波动的颜色都视为 origColor:
-
-```java
-if (Math.abs(image[x][y] - origColor) > threshold)
- return;
-```
-
-对于第二个问题,我们首先明确问题:不要把区域内所有 origColor 的都染色,而是只给区域最外圈染色。然后,我们分析,如何才能仅给外围染色,即如何才能找到最外围坐标,最外围坐标有什么特点?
-
-![](https://labuladong.github.io/algo/images/floodfill/ppt4.PNG)
-
-可以发现,区域边界上的坐标,至少有一个方向不是 origColor,而区域内部的坐标,四面都是 origColor,这就是解决问题的关键。保持框架不变,使用 visited 数组记录已搜索坐标,主要代码如下:
-
-```java
-int fill(int[][] image, int x, int y,
- int origColor, int newColor) {
- // 出界:超出数组边界
- if (!inArea(image, x, y)) return 0;
- // 已探索过的 origColor 区域
- if (visited[x][y]) return 1;
- // 碰壁:遇到其他颜色,超出 origColor 区域
- if (image[x][y] != origColor) return 0;
-
- visited[x][y] = true;
-
- int surround =
- fill(image, x - 1, y, origColor, newColor)
- + fill(image, x + 1, y, origColor, newColor)
- + fill(image, x, y - 1, origColor, newColor)
- + fill(image, x, y + 1, origColor, newColor);
-
- if (surround < 4)
- image[x][y] = newColor;
-
- return 1;
-}
-```
-
-这样,区域内部的坐标探索四周后得到的 surround 是 4,而边界的坐标会遇到其他颜色,或超出边界索引,surround 会小于 4。如果你对这句话不理解,我们把逻辑框架抽象出来看:
-
-```java
-int fill(int[][] image, int x, int y,
- int origColor, int newColor) {
- // 出界:超出数组边界
- if (!inArea(image, x, y)) return 0;
- // 已探索过的 origColor 区域
- if (visited[x][y]) return 1;
- // 碰壁:遇到其他颜色,超出 origColor 区域
- if (image[x][y] != origColor) return 0;
- // 未探索且属于 origColor 区域
- if (image[x][y] == origColor) {
- // ...
- return 1;
- }
-}
-```
-
-这 4 个 if 判断涵盖了 (x, y) 的所有可能情况,surround 的值由四个递归函数相加得到,而每个递归函数的返回值就这四种情况的一种。借助这个逻辑框架,你一定能理解上面那句话了。
-
-这样就实现了仅对 origColor 区域边界坐标染色的目的,等同于完成了魔棒工具选定区域边界的功能。
-
-这个算法有两个细节问题,一是必须借助 visited 来记录已探索的坐标,而无法使用回溯算法;二是开头几个 if 顺序不可打乱。读者可以思考一下原因。
-
-同理,思考扫雷游戏,应用 FloodFill 算法展开空白区域的同时,也需要计算并显示边界上雷的个数,如何实现的?其实也是相同的思路,遇到雷就返回 true,这样 surround 变量存储的就是雷的个数。当然,扫雷的 FloodFill 算法不能只检查上下左右,还得加上四个斜向。
-
-![](https://labuladong.github.io/algo/images/floodfill/ppt5.PNG)
-
-以上详细讲解了 FloodFill 算法的框架设计,**二维矩阵中的搜索问题,都逃不出这个算法框架**。
-
-**_____________**
-
-**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
-
-![](https://labuladong.github.io/algo/images/souyisou2.png)
-
-
-======其他语言代码======
-
-[733.图像渲染](https://leetcode-cn.com/problems/flood-fill)
-
-
-
-### javascript
-
-**BFS**
-从起始像素向上下左右扩散,只要相邻的点存在并和起始点颜色相同,就染成新的颜色,并继续扩散。
-
-借助一个队列去遍历节点,考察出列的节点,带出满足条件的节点入列。已经染成新色的节点不会入列,避免重复访问节点。
-
-时间复杂度:O(n)。空间复杂度:O(n)
-
-```js
-const floodFill = (image, sr, sc, newColor) => {
- const m = image.length;
- const n = image[0].length;
- const oldColor = image[sr][sc];
- if (oldColor == newColor) return image;
-
- const fill = (i, j) => {
- if (i < 0 || i >= m || j < 0 || j >= n || image[i][j] != oldColor) {
- return;
- }
- image[i][j] = newColor;
- fill(i - 1, j);
- fill(i + 1, j);
- fill(i, j - 1);
- fill(i, j + 1);
- };
-
- fill(sr, sc);
- return image;
-};
-```
-
-
-
-**DFS**
-
-思路与上文相同。
-
-```js
-/**
- * @param {number[][]} image
- * @param {number} sr
- * @param {number} sc
- * @param {number} newColor
- * @return {number[][]}
- */
-let floodFill = function (image, sr, sc, newColor) {
- let origColor = image[sr][sc];
- fill(image, sr, sc, origColor, newColor);
- return image;
-}
-
-let fill = function (image, x, y, origColor, newColor) {
- // 出界:超出边界索引
- if (!inArea(image, x, y)) return;
-
- // 碰壁:遇到其他颜色,超出 origColor 区域
- if (image[x][y] !== origColor) return;
-
- // 已探索过的 origColor 区域
- if (image[x][y] === -1) return;
-
- // 打标记 避免重复
- image[x][y] = -1;
-
- fill(image, x, y + 1, origColor, newColor);
- fill(image, x, y - 1, origColor, newColor);
- fill(image, x - 1, y, origColor, newColor);
- fill(image, x + 1, y, origColor, newColor);
-
- // un choose:将标记替换为 newColor
- image[x][y] = newColor;
-}
-
-let inArea = function (image, x, y) {
- return x >= 0 && x < image.length
- && y >= 0 && y < image[0].length;
-}
-```
-
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md"
deleted file mode 100644
index ee53f68..0000000
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md"
+++ /dev/null
@@ -1,570 +0,0 @@
-# Union-Find算法应用
-
-
-
-
-![](../pictures/souyisou.png)
-
-**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~
-
-读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
-
-[130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions)
-
-[990.等式方程的可满足性](https://leetcode-cn.com/problems/satisfiability-of-equality-equations)
-
-[261.以图判树](https://leetcode-cn.com/problems/graph-valid-tree/)
-
-**-----------**
-
-上篇文章很多读者对于 Union-Find 算法的应用表示很感兴趣,这篇文章就拿几道 LeetCode 题目来讲讲这个算法的巧妙用法。
-
-首先,复习一下,Union-Find 算法解决的是图的动态连通性问题,这个算法本身不难,能不能应用出来主要是看你抽象问题的能力,是否能够把原始问题抽象成一个有关图论的问题。
-
-先复习一下上篇文章写的算法代码,回答读者提出的几个问题:
-
-```java
-class UF {
- // 记录连通分量个数
- private int count;
- // 存储若干棵树
- private int[] parent;
- // 记录树的“重量”
- private int[] size;
-
- public UF(int n) {
- this.count = n;
- parent = new int[n];
- size = new int[n];
- for (int i = 0; i < n; i++) {
- parent[i] = i;
- size[i] = 1;
- }
- }
-
- /* 将 p 和 q 连通 */
- public void union(int p, int q) {
- int rootP = find(p);
- int rootQ = find(q);
- if (rootP == rootQ)
- return;
-
- // 小树接到大树下面,较平衡
- if (size[rootP] > size[rootQ]) {
- parent[rootQ] = rootP;
- size[rootP] += size[rootQ];
- } else {
- parent[rootP] = rootQ;
- size[rootQ] += size[rootP];
- }
- count--;
- }
-
- /* 判断 p 和 q 是否互相连通 */
- public boolean connected(int p, int q) {
- int rootP = find(p);
- int rootQ = find(q);
- // 处于同一棵树上的节点,相互连通
- return rootP == rootQ;
- }
-
- /* 返回节点 x 的根节点 */
- private int find(int x) {
- while (parent[x] != x) {
- // 进行路径压缩
- parent[x] = parent[parent[x]];
- x = parent[x];
- }
- return x;
- }
-
- public int count() {
- return count;
- }
-}
-```
-
-算法的关键点有 3 个:
-
-1、用 `parent` 数组记录每个节点的父节点,相当于指向父节点的指针,所以 `parent` 数组内实际存储着一个森林(若干棵多叉树)。
-
-2、用 `size` 数组记录着每棵树的重量,目的是让 `union` 后树依然拥有平衡性,而不会退化成链表,影响操作效率。
-
-3、在 `find` 函数中进行路径压缩,保证任意树的高度保持在常数,使得 `union` 和 `connected` API 时间复杂度为 O(1)。
-
-有的读者问,**既然有了路径压缩,`size` 数组的重量平衡还需要吗**?这个问题很有意思,因为路径压缩保证了树高为常数(不超过 3),那么树就算不平衡,高度也是常数,基本没什么影响。
-
-我认为,论时间复杂度的话,确实,不需要重量平衡也是 O(1)。但是如果加上 `size` 数组辅助,效率还是略微高一些,比如下面这种情况:
-
-![](../pictures/unionfind应用/1.jpg)
-
-如果带有重量平衡优化,一定会得到情况一,而不带重量优化,可能出现情况二。高度为 3 时才会触发路径压缩那个 `while` 循环,所以情况一根本不会触发路径压缩,而情况二会多执行很多次路径压缩,将第三层节点压缩到第二层。
-
-也就是说,去掉重量平衡,虽然对于单个的 `find` 函数调用,时间复杂度依然是 O(1),但是对于 API 调用的整个过程,效率会有一定的下降。当然,好处就是减少了一些空间,不过对于 Big O 表示法来说,时空复杂度都没变。
-
-下面言归正传,来看看这个算法有什么实际应用。
-
-### 一、DFS 的替代方案
-
-很多使用 DFS 深度优先算法解决的问题,也可以用 Union-Find 算法解决。
-
-比如第 130 题,被围绕的区域:给你一个 M×N 的二维矩阵,其中包含字符 `X` 和 `O`,让你找到矩阵中**四面**被 `X` 围住的 `O`,并且把它们替换成 `X`。
-
-```java
-void solve(char[][] board);
-```
-
-注意哦,必须是四面被围的 `O` 才能被换成 `X`,也就是说边角上的 `O` 一定不会被围,进一步,与边角上的 `O` 相连的 `O` 也不会被 `X` 围四面,也不会被替换。
-
-![](../pictures/unionfind应用/2.jpg)
-
-PS:这让我想起小时候玩的棋类游戏「黑白棋」,只要你用两个棋子把对方的棋子夹在中间,对方的子就被替换成你的子。可见,占据四角的棋子是无敌的,与其相连的边棋子也是无敌的(无法被夹掉)。
-
-解决这个问题的传统方法也不困难,先用 for 循环遍历棋盘的**四边**,用 DFS 算法把那些与边界相连的 `O` 换成一个特殊字符,比如 `#`;然后再遍历整个棋盘,把剩下的 `O` 换成 `X`,把 `#` 恢复成 `O`。这样就能完成题目的要求,时间复杂度 O(MN)。
-
-这个问题也可以用 Union-Find 算法解决,虽然实现复杂一些,甚至效率也略低,但这是使用 Union-Find 算法的通用思想,值得一学。
-
-**你可以把那些不需要被替换的 `O` 看成一个拥有独门绝技的门派,它们有一个共同祖师爷叫 `dummy`,这些 `O` 和 `dummy` 互相连通,而那些需要被替换的 `O` 与 `dummy` 不连通**。
-
-![](../pictures/unionfind应用/3.jpg)
-
-这就是 Union-Find 的核心思路,明白这个图,就很容易看懂代码了。
-
-首先要解决的是,根据我们的实现,Union-Find 底层用的是一维数组,构造函数需要传入这个数组的大小,而题目给的是一个二维棋盘。
-
-这个很简单,二维坐标 `(x,y)` 可以转换成 `x * n + y` 这个数(`m` 是棋盘的行数,`n` 是棋盘的列数)。敲黑板,**这是将二维坐标映射到一维的常用技巧**。
-
-其次,我们之前描述的「祖师爷」是虚构的,需要给他老人家留个位置。索引 `[0.. m*n-1]` 都是棋盘内坐标的一维映射,那就让这个虚拟的 `dummy` 节点占据索引 `m * n` 好了。
-
-```java
-void solve(char[][] board) {
- if (board.length == 0) return;
-
- int m = board.length;
- int n = board[0].length;
- // 给 dummy 留一个额外位置
- UF uf = new UF(m * n + 1);
- int dummy = m * n;
- // 将首列和末列的 O 与 dummy 连通
- for (int i = 0; i < m; i++) {
- if (board[i][0] == 'O')
- uf.union(i * n, dummy);
- if (board[i][n - 1] == 'O')
- uf.union(i * n + n - 1, dummy);
- }
- // 将首行和末行的 O 与 dummy 连通
- for (int j = 0; j < n; j++) {
- if (board[0][j] == 'O')
- uf.union(j, dummy);
- if (board[m - 1][j] == 'O')
- uf.union(n * (m - 1) + j, dummy);
- }
- // 方向数组 d 是上下左右搜索的常用手法
- int[][] d = new int[][]{{1,0}, {0,1}, {0,-1}, {-1,0}};
- for (int i = 1; i < m - 1; i++)
- for (int j = 1; j < n - 1; j++)
- if (board[i][j] == 'O')
- // 将此 O 与上下左右的 O 连通
- for (int k = 0; k < 4; k++) {
- int x = i + d[k][0];
- int y = j + d[k][1];
- if (board[x][y] == 'O')
- uf.union(x * n + y, i * n + j);
- }
- // 所有不和 dummy 连通的 O,都要被替换
- for (int i = 1; i < m - 1; i++)
- for (int j = 1; j < n - 1; j++)
- if (!uf.connected(dummy, i * n + j))
- board[i][j] = 'X';
-}
-```
-
-这段代码很长,其实就是刚才的思路实现,只有和边界 `O` 相连的 `O` 才具有和 `dummy` 的连通性,他们不会被替换。
-
-说实话,Union-Find 算法解决这个简单的问题有点杀鸡用牛刀,它可以解决更复杂,更具有技巧性的问题,**主要思路是适时增加虚拟节点,想办法让元素「分门别类」,建立动态连通关系**。
-
-### 二、判定合法等式
-
-这个问题用 Union-Find 算法就显得十分优美了。题目是这样:
-
-给你一个数组 `equations`,装着若干字符串表示的算式。每个算式 `equations[i]` 长度都是 4,而且只有这两种情况:`a==b` 或者 `a!=b`,其中 `a,b` 可以是任意小写字母。你写一个算法,如果 `equations` 中所有算式都不会互相冲突,返回 true,否则返回 false。
-
-比如说,输入 `["a==b","b!=c","c==a"]`,算法返回 false,因为这三个算式不可能同时正确。
-
-再比如,输入 `["c==c","b==d","x!=z"]`,算法返回 true,因为这三个算式并不会造成逻辑冲突。
-
-我们前文说过,动态连通性其实就是一种等价关系,具有「自反性」「传递性」和「对称性」,其实 `==` 关系也是一种等价关系,具有这些性质。所以这个问题用 Union-Find 算法就很自然。
-
-核心思想是,**将 `equations` 中的算式根据 `==` 和 `!=` 分成两部分,先处理 `==` 算式,使得他们通过相等关系各自勾结成门派;然后处理 `!=` 算式,检查不等关系是否破坏了相等关系的连通性**。
-
-```java
-boolean equationsPossible(String[] equations) {
- // 26 个英文字母
- UF uf = new UF(26);
- // 先让相等的字母形成连通分量
- for (String eq : equations) {
- if (eq.charAt(1) == '=') {
- char x = eq.charAt(0);
- char y = eq.charAt(3);
- uf.union(x - 'a', y - 'a');
- }
- }
- // 检查不等关系是否打破相等关系的连通性
- for (String eq : equations) {
- if (eq.charAt(1) == '!') {
- char x = eq.charAt(0);
- char y = eq.charAt(3);
- // 如果相等关系成立,就是逻辑冲突
- if (uf.connected(x - 'a', y - 'a'))
- return false;
- }
- }
- return true;
-}
-```
-
-至此,这道判断算式合法性的问题就解决了,借助 Union-Find 算法,是不是很简单呢?
-
-### 三、简单总结
-
-使用 Union-Find 算法,主要是如何把原问题转化成图的动态连通性问题。对于算式合法性问题,可以直接利用等价关系,对于棋盘包围问题,则是利用一个虚拟节点,营造出动态连通特性。
-
-另外,将二维数组映射到一维数组,利用方向数组 `d` 来简化代码量,都是在写算法时常用的一些小技巧,如果没见过可以注意一下。
-
-很多更复杂的 DFS 算法问题,都可以利用 Union-Find 算法更漂亮的解决。LeetCode 上 Union-Find 相关的问题也就二十多道,有兴趣的读者可以去做一做。
-
-
-
-**_____________**
-
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
-
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
-
-
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [Kruskal 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=kruskal)
+ - [Prim 最小生成树算法](https://labuladong.github.io/article/fname.html?fname=prim算法)
+ - [一文秒杀所有岛屿题目](https://labuladong.github.io/article/fname.html?fname=岛屿题目)
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1361. Validate Binary Tree Nodes](https://leetcode.com/problems/validate-binary-tree-nodes/?show=1) | [1361. 验证二叉树](https://leetcode.cn/problems/validate-binary-tree-nodes/?show=1) |
+| [200. Number of Islands](https://leetcode.com/problems/number-of-islands/?show=1) | [200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/?show=1) |
+| [261. Graph Valid Tree](https://leetcode.com/problems/graph-valid-tree/?show=1)🔒 | [261. 以图判树](https://leetcode.cn/problems/graph-valid-tree/?show=1)🔒 |
+| [765. Couples Holding Hands](https://leetcode.com/problems/couples-holding-hands/?show=1) | [765. 情侣牵手](https://leetcode.cn/problems/couples-holding-hands/?show=1) |
+
+
+引用本文的文章
+
+ - [base case 和备忘录的初始值怎么定?](https://labuladong.github.io/article/fname.html?fname=备忘录等基础)
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+ - [二分搜索怎么用?我又总结了套路](https://labuladong.github.io/article/fname.html?fname=二分运用)
+ - [二分搜索怎么用?我和快手面试官进行了深度探讨](https://labuladong.github.io/article/fname.html?fname=二分分割子数组)
+ - [二分搜索算法经典题目](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62a07736e4b01a485209b0b4/1)
+ - [二分查找高效判定子序列](https://labuladong.github.io/article/fname.html?fname=二分查找判定子序列)
+ - [动态规划设计:最长递增子序列](https://labuladong.github.io/article/fname.html?fname=动态规划设计:最长递增子序列)
+ - [双指针技巧秒杀七道数组题目](https://labuladong.github.io/article/fname.html?fname=双指针技巧)
+ - [带权重的随机选择算法](https://labuladong.github.io/article/fname.html?fname=随机权重)
+ - [快速排序详解及应用](https://labuladong.github.io/article/fname.html?fname=快速排序)
+ - [我写了首诗,把滑动窗口算法变成了默写题](https://labuladong.github.io/article/fname.html?fname=滑动窗口技巧进阶)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [讲两道常考的阶乘算法题](https://labuladong.github.io/article/fname.html?fname=阶乘题目)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1201. Ugly Number III](https://leetcode.com/problems/ugly-number-iii/?show=1) | [1201. 丑数 III](https://leetcode.cn/problems/ugly-number-iii/?show=1) |
+| [162. Find Peak Element](https://leetcode.com/problems/find-peak-element/?show=1) | [162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/?show=1) |
+| [240. Search a 2D Matrix II](https://leetcode.com/problems/search-a-2d-matrix-ii/?show=1) | [240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/?show=1) |
+| [33. Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array/?show=1) | [33. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/?show=1) |
+| [35. Search Insert Position](https://leetcode.com/problems/search-insert-position/?show=1) | [35. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/?show=1) |
+| [74. Search a 2D Matrix](https://leetcode.com/problems/search-a-2d-matrix/?show=1) | [74. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/?show=1) |
+| [793. Preimage Size of Factorial Zeroes Function](https://leetcode.com/problems/preimage-size-of-factorial-zeroes-function/?show=1) | [793. 阶乘函数后 K 个零](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/?show=1) |
+| [81. Search in Rotated Sorted Array II](https://leetcode.com/problems/search-in-rotated-sorted-array-ii/?show=1) | [81. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/?show=1) |
+| [852. Peak Index in a Mountain Array](https://leetcode.com/problems/peak-index-in-a-mountain-array/?show=1) | [852. 山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/?show=1) |
+| - | [剑指 Offer 04. 二维数组中的查找](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/?show=1) |
+| - | [剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/?show=1) |
+| - | [剑指 Offer 53 - II. 0~n-1中缺失的数字](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/?show=1) |
+| - | [剑指 Offer II 068. 查找插入位置](https://leetcode.cn/problems/N6YdxV/?show=1) |
+| - | [剑指 Offer II 069. 山峰数组的顶部](https://leetcode.cn/problems/B1IidL/?show=1) |
+
+
+引用本文的文章
+
+ - [二维数组的花式遍历技巧](https://labuladong.github.io/article/fname.html?fname=花式遍历)
+ - [动态规划设计:最大子数组](https://labuladong.github.io/article/fname.html?fname=最大子数组)
+ - [小而美的算法技巧:差分数组](https://labuladong.github.io/article/fname.html?fname=差分技巧)
+ - [带权重的随机选择算法](https://labuladong.github.io/article/fname.html?fname=随机权重)
+ - [归并排序详解及应用](https://labuladong.github.io/article/fname.html?fname=归并排序)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [经典数组技巧:差分数组](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_629e0d3ae4b0812e17a32f01/1)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1314. Matrix Block Sum](https://leetcode.com/problems/matrix-block-sum/?show=1) | [1314. 矩阵区域和](https://leetcode.cn/problems/matrix-block-sum/?show=1) |
+| [1352. Product of the Last K Numbers](https://leetcode.com/problems/product-of-the-last-k-numbers/?show=1) | [1352. 最后 K 个数的乘积](https://leetcode.cn/problems/product-of-the-last-k-numbers/?show=1) |
+| [238. Product of Array Except Self](https://leetcode.com/problems/product-of-array-except-self/?show=1) | [238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/?show=1) |
+| [327. Count of Range Sum](https://leetcode.com/problems/count-of-range-sum/?show=1) | [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/?show=1) |
+| [437. Path Sum III](https://leetcode.com/problems/path-sum-iii/?show=1) | [437. 路径总和 III](https://leetcode.cn/problems/path-sum-iii/?show=1) |
+| [523. Continuous Subarray Sum](https://leetcode.com/problems/continuous-subarray-sum/?show=1) | [523. 连续的子数组和](https://leetcode.cn/problems/continuous-subarray-sum/?show=1) |
+| [525. Contiguous Array](https://leetcode.com/problems/contiguous-array/?show=1) | [525. 连续数组](https://leetcode.cn/problems/contiguous-array/?show=1) |
+| [560. Subarray Sum Equals K](https://leetcode.com/problems/subarray-sum-equals-k/?show=1) | [560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/?show=1) |
+| [724. Find Pivot Index](https://leetcode.com/problems/find-pivot-index/?show=1) | [724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/?show=1) |
+| [862. Shortest Subarray with Sum at Least K](https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/?show=1) | [862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/?show=1) |
+| [918. Maximum Sum Circular Subarray](https://leetcode.com/problems/maximum-sum-circular-subarray/?show=1) | [918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/?show=1) |
+| [974. Subarray Sums Divisible by K](https://leetcode.com/problems/subarray-sums-divisible-by-k/?show=1) | [974. 和可被 K 整除的子数组](https://leetcode.cn/problems/subarray-sums-divisible-by-k/?show=1) |
+| - | [剑指 Offer 57 - II. 和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/?show=1) |
+| - | [剑指 Offer II 010. 和为 k 的子数组](https://leetcode.cn/problems/QTMn0o/?show=1) |
+| - | [剑指 Offer II 011. 0 和 1 个数相同的子数组](https://leetcode.cn/problems/A1NYOS/?show=1) |
+| - | [剑指 Offer II 012. 左右两边子数组的和相等](https://leetcode.cn/problems/tvdfij/?show=1) |
+| - | [剑指 Offer II 013. 二维子矩阵的和](https://leetcode.cn/problems/O4NDxx/?show=1) |
+| - | [剑指 Offer II 050. 向下的路径节点之和](https://leetcode.cn/problems/6eUYwP/?show=1) |
+
+
+引用本文的文章
+
+ - [一个方法团灭 nSum 问题](https://labuladong.github.io/article/fname.html?fname=nSum)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [动态规划之子序列问题解题模板](https://labuladong.github.io/article/fname.html?fname=子序列问题模板)
+ - [双指针更多经典题目](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62a1dd68e4b09dda1273a5f9/1)
+ - [如何判断回文链表](https://labuladong.github.io/article/fname.html?fname=判断回文链表)
+ - [我写了首诗,把滑动窗口算法变成了默写题](https://labuladong.github.io/article/fname.html?fname=滑动窗口技巧进阶)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [扫描线技巧:安排会议室](https://labuladong.github.io/article/fname.html?fname=安排会议室)
+ - [田忌赛马背后的算法决策](https://labuladong.github.io/article/fname.html?fname=田忌赛马)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1. Two Sum](https://leetcode.com/problems/two-sum/?show=1) | [1. 两数之和](https://leetcode.cn/problems/two-sum/?show=1) |
+| [281. Zigzag Iterator](https://leetcode.com/problems/zigzag-iterator/?show=1)🔒 | [281. 锯齿迭代器](https://leetcode.cn/problems/zigzag-iterator/?show=1)🔒 |
+| [42. Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/?show=1) | [42. 接雨水](https://leetcode.cn/problems/trapping-rain-water/?show=1) |
+| [80. Remove Duplicates from Sorted Array II](https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/?show=1) | [80. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/?show=1) |
+| [82. Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/?show=1) | [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/?show=1) |
+| - | [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/?show=1) |
+| - | [剑指 Offer 57. 和为s的两个数字](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/?show=1) |
+
+
+引用本文的文章
+
+ - [BFS 算法解题套路框架](https://labuladong.github.io/article/fname.html?fname=BFS框架)
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [FloodFill算法详解及应用](https://labuladong.github.io/article/fname.html?fname=FloodFill算法详解及应用)
+ - [base case 和备忘录的初始值怎么定?](https://labuladong.github.io/article/fname.html?fname=备忘录等基础)
+ - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
+ - [两种思路解决单词拼接问题](https://labuladong.github.io/article/fname.html?fname=单词拼接)
+ - [二分搜索怎么用?我和快手面试官进行了深度探讨](https://labuladong.github.io/article/fname.html?fname=二分分割子数组)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [前缀树算法模板秒杀五道算法题](https://labuladong.github.io/article/fname.html?fname=trie)
+ - [动态规划和回溯算法到底谁是谁爹?](https://labuladong.github.io/article/fname.html?fname=targetSum)
+ - [回溯算法最佳实践:括号生成](https://labuladong.github.io/article/fname.html?fname=合法括号生成)
+ - [回溯算法最佳实践:解数独](https://labuladong.github.io/article/fname.html?fname=sudoku)
+ - [回溯算法秒杀所有排列/组合/子集问题](https://labuladong.github.io/article/fname.html?fname=子集排列组合)
+ - [图论基础及遍历算法](https://labuladong.github.io/article/fname.html?fname=图)
+ - [学习算法和刷题的框架思维](https://labuladong.github.io/article/fname.html?fname=学习数据结构和算法的高效方法)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [环检测及拓扑排序算法](https://labuladong.github.io/article/fname.html?fname=拓扑排序)
+ - [算法学习和心流体验](https://labuladong.github.io/article/fname.html?fname=心流)
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+ - [经典动态规划:戳气球](https://labuladong.github.io/article/fname.html?fname=扎气球)
+ - [经典回溯算法:集合划分问题](https://labuladong.github.io/article/fname.html?fname=集合划分)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [112. Path Sum](https://leetcode.com/problems/path-sum/?show=1) | [112. 路径总和](https://leetcode.cn/problems/path-sum/?show=1) |
+| [113. Path Sum II](https://leetcode.com/problems/path-sum-ii/?show=1) | [113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/?show=1) |
+| [140. Word Break II](https://leetcode.com/problems/word-break-ii/?show=1) | [140. 单词拆分 II](https://leetcode.cn/problems/word-break-ii/?show=1) |
+| [17. Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/?show=1) | [17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/?show=1) |
+| [22. Generate Parentheses](https://leetcode.com/problems/generate-parentheses/?show=1) | [22. 括号生成](https://leetcode.cn/problems/generate-parentheses/?show=1) |
+| [39. Combination Sum](https://leetcode.com/problems/combination-sum/?show=1) | [39. 组合总和](https://leetcode.cn/problems/combination-sum/?show=1) |
+| [698. Partition to K Equal Sum Subsets](https://leetcode.com/problems/partition-to-k-equal-sum-subsets/?show=1) | [698. 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/?show=1) |
+| [77. Combinations](https://leetcode.com/problems/combinations/?show=1) | [77. 组合](https://leetcode.cn/problems/combinations/?show=1) |
+| [78. Subsets](https://leetcode.com/problems/subsets/?show=1) | [78. 子集](https://leetcode.cn/problems/subsets/?show=1) |
+| - | [剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/?show=1) |
+| - | [剑指 Offer II 079. 所有子集](https://leetcode.cn/problems/TVdhkn/?show=1) |
+| - | [剑指 Offer II 080. 含有 k 个元素的组合](https://leetcode.cn/problems/uUsW3B/?show=1) |
+| - | [剑指 Offer II 081. 允许重复选择元素的组合](https://leetcode.cn/problems/Ygoe9J/?show=1) |
+| - | [剑指 Offer II 083. 没有重复元素集合的全排列](https://leetcode.cn/problems/VvJkup/?show=1) |
+| - | [剑指 Offer II 085. 生成匹配的括号](https://leetcode.cn/problems/IDBivT/?show=1) |
+
+
+引用本文的文章
+
+ - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法)
+ - [一文秒杀所有岛屿题目](https://labuladong.github.io/article/fname.html?fname=岛屿题目)
+ - [东哥带你刷二叉树(序列化篇)](https://labuladong.github.io/article/fname.html?fname=二叉树的序列化)
+ - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结)
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+ - [二叉树的递归转迭代的代码框架](https://labuladong.github.io/article/fname.html?fname=迭代遍历二叉树)
+ - [前缀树算法模板秒杀五道算法题](https://labuladong.github.io/article/fname.html?fname=trie)
+ - [动态规划和回溯算法到底谁是谁爹?](https://labuladong.github.io/article/fname.html?fname=targetSum)
+ - [回溯算法秒杀所有排列/组合/子集问题](https://labuladong.github.io/article/fname.html?fname=子集排列组合)
+ - [回溯算法解题套路框架](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版)
+ - [图论基础及遍历算法](https://labuladong.github.io/article/fname.html?fname=图)
+ - [如何 K 个一组反转链表](https://labuladong.github.io/article/fname.html?fname=k个一组反转链表)
+ - [如何判断回文链表](https://labuladong.github.io/article/fname.html?fname=判断回文链表)
+ - [归并排序详解及应用](https://labuladong.github.io/article/fname.html?fname=归并排序)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [环检测及拓扑排序算法](https://labuladong.github.io/article/fname.html?fname=拓扑排序)
+ - [算法学习和心流体验](https://labuladong.github.io/article/fname.html?fname=心流)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+ - [题目不让我干什么,我偏要干什么](https://labuladong.github.io/article/fname.html?fname=nestInteger)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [100. Same Tree](https://leetcode.com/problems/same-tree/?show=1) | [100. 相同的树](https://leetcode.cn/problems/same-tree/?show=1) |
+| [341. Flatten Nested List Iterator](https://leetcode.com/problems/flatten-nested-list-iterator/?show=1) | [341. 扁平化嵌套列表迭代器](https://leetcode.cn/problems/flatten-nested-list-iterator/?show=1) |
+| [589. N-ary Tree Preorder Traversal](https://leetcode.com/problems/n-ary-tree-preorder-traversal/?show=1) | [589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/?show=1) |
+| [590. N-ary Tree Postorder Traversal](https://leetcode.com/problems/n-ary-tree-postorder-traversal/?show=1) | [590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/?show=1) |
+
+
+引用本文的文章
+
+ - [二维数组的花式遍历技巧](https://labuladong.github.io/article/fname.html?fname=花式遍历)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [扫描线技巧:安排会议室](https://labuladong.github.io/article/fname.html?fname=安排会议室)
+
+
+
+
+
+
+
+**_____________**
+
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
\ No newline at end of file
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md"
index e10737a..2eb91e4 100644
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md"
+++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md"
@@ -1,9 +1,5 @@
# 常用的位运算技巧
-
-
-
-
+引用本文的文章
+
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+ - [如何同时寻找缺失和重复的元素](https://labuladong.github.io/article/fname.html?fname=缺失和重复的元素)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [389. Find the Difference](https://leetcode.com/problems/find-the-difference/?show=1) | [389. 找不同](https://leetcode.cn/problems/find-the-difference/?show=1) |
+| - | [剑指 Offer 15. 二进制中1的个数](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/?show=1) |
+
+
+引用本文的文章
+
+ - [几个反直觉的概率问题](https://labuladong.github.io/article/fname.html?fname=几个反直觉的概率问题)
+ - [快速排序详解及应用](https://labuladong.github.io/article/fname.html?fname=快速排序)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md"
deleted file mode 100644
index 9f28548..0000000
--- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md"
+++ /dev/null
@@ -1,580 +0,0 @@
-# 滑动窗口算法框架
-
-
-
-![](../pictures/souyisou.png)
-
-**最新消息:关注公众号参与活动,有机会成为 [70k star 算法仓库](https://github.com/labuladong/fucking-algorithm) 的贡献者,机不可失时不再来**!
-
-相关推荐:
-* [东哥吃葡萄时竟然吃出一道算法题!](https://labuladong.gitee.io/algo/)
-* [如何寻找缺失的元素](https://labuladong.gitee.io/algo/)
-
-读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
-
-[76.最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring)
-
-[567.字符串的排列](https://leetcode-cn.com/problems/permutation-in-string)
-
-[438.找到字符串中所有字母异位词](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string)
-
-[3.无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters)
-
-**-----------**
-
-本文是之前的一篇文章 [滑动窗口算法详解](https://mp.weixin.qq.com/s/nJHIxQ2BbqhDv5jZ9NgXrQ) 的修订版,添加了滑动窗口算法更详细的解释。
-
-本文详解「滑动窗口」这种高级双指针技巧的算法框架,带你秒杀几道高难度的子字符串匹配问题。
-
-LeetCode 上至少有 9 道题目可以用此方法高效解决。但是有几道是 VIP 题目,有几道题目虽不难但太复杂,所以本文只选择点赞最高,较为经典的,最能够讲明白的三道题来讲解。第一题为了让读者掌握算法模板,篇幅相对长,后两题就基本秒杀了。
-
-本文代码为 C++ 实现,不会用到什么编程方面的奇技淫巧,但是还是简单介绍一下一些用到的数据结构,以免有的读者因为语言的细节问题阻碍对算法思想的理解:
-
-`unordered_map` 就是哈希表(字典),它的一个方法 count(key) 相当于 containsKey(key) 可以判断键 key 是否存在。
-
-可以使用方括号访问键对应的值 map[key]。需要注意的是,如果该 key 不存在,C++ 会自动创建这个 key,并把 map[key] 赋值为 0。
-
-所以代码中多次出现的 `map[key]++` 相当于 Java 的 `map.put(key, map.getOrDefault(key, 0) + 1)`。
-
-本文大部分代码都是图片形式,可以点开放大,更重要的是可以左右滑动方便对比代码。下面进入正题。
-
-### 一、最小覆盖子串
-
-![题目链接](../pictures/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3/title1.png)
-
-题目不难理解,就是说要在 S(source) 中找到包含 T(target) 中全部字母的一个子串,顺序无所谓,但这个子串一定是所有可能子串中最短的。
-
-如果我们使用暴力解法,代码大概是这样的:
-
-```java
-for (int i = 0; i < s.size(); i++)
- for (int j = i + 1; j < s.size(); j++)
- if s[i:j] 包含 t 的所有字母:
- 更新答案
-```
-
-思路很直接吧,但是显然,这个算法的复杂度肯定大于 O(N^2) 了,不好。
-
-滑动窗口算法的思路是这样:
-
-1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
-
-2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
-
-3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
-
-4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。
-
-这个思路其实也不难,**第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。**左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
-
-下面画图理解一下,needs 和 window 相当于计数器,分别记录 T 中字符出现次数和窗口中的相应字符的出现次数。
-
-初始状态:
-
-![0](../pictures/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3/0.png)
-
-增加 right,直到窗口 [left, right] 包含了 T 中所有字符:
-
-![0](../pictures/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3/1.png)
-
-
-现在开始增加 left,缩小窗口 [left, right]。
-
-![0](../pictures/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3/2.png)
-
-直到窗口中的字符串不再符合要求,left 不再继续移动。
-
-![0](../pictures/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3/3.png)
-
-
-之后重复上述过程,先移动 right,再移动 left…… 直到 right 指针到达字符串 S 的末端,算法结束。
-
-如果你能够理解上述过程,恭喜,你已经完全掌握了滑动窗口算法思想。至于如何具体到问题,如何得出此题的答案,都是编程问题,等会提供一套模板,理解一下就会了。
-
-上述过程可以简单地写出如下伪码框架:
-
-```cpp
-string s, t;
-// 在 s 中寻找 t 的「最小覆盖子串」
-int left = 0, right = 0;
-string res = s;
-
-while(right < s.size()) {
- window.add(s[right]);
- right++;
- // 如果符合要求,移动 left 缩小窗口
- while (window 符合要求) {
- // 如果这个窗口的子串更短,则更新 res
- res = minLen(res, window);
- window.remove(s[left]);
- left++;
- }
-}
-return res;
-```
-
-如果上述代码你也能够理解,那么你离解题更近了一步。现在就剩下一个比较棘手的问题:如何判断 window 即子串 s[left...right] 是否符合要求,是否包含 t 的所有字符呢?
-
-可以用两个哈希表当作计数器解决。用一个哈希表 needs 记录字符串 t 中包含的字符及出现次数,用另一个哈希表 window 记录当前「窗口」中包含的字符及出现的次数,如果 window 包含所有 needs 中的键,且这些键对应的值都大于等于 needs 中的值,那么就可以知道当前「窗口」符合要求了,可以开始移动 left 指针了。
-
-现在将上面的框架继续细化:
-
-```cpp
-string s, t;
-// 在 s 中寻找 t 的「最小覆盖子串」
-int left = 0, right = 0;
-string res = s;
-
-// 相当于两个计数器
-unordered_map
+引用本文的文章
+
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [动态规划设计:最大子数组](https://labuladong.github.io/article/fname.html?fname=最大子数组)
+ - [单调队列的通用实现及经典习题](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62a692efe4b01a48520b9b9b/1)
+ - [单调队列结构解决滑动窗口问题](https://labuladong.github.io/article/fname.html?fname=单调队列)
+ - [双指针技巧秒杀七道数组题目](https://labuladong.github.io/article/fname.html?fname=双指针技巧)
+ - [归并排序详解及应用](https://labuladong.github.io/article/fname.html?fname=归并排序)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [滑动窗口算法延伸:Rabin Karp 字符匹配算法](https://labuladong.github.io/article/fname.html?fname=rabinkarp)
+ - [滑动窗口算法经典习题](https://appktavsiei5995.pc.xiaoe-tech.com/detail/i_62b57985e4b00a4f371dd705/1)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1004. Max Consecutive Ones III](https://leetcode.com/problems/max-consecutive-ones-iii/?show=1) | [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/?show=1) |
+| [1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit](https://leetcode.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/?show=1) | [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/?show=1) |
+| [1658. Minimum Operations to Reduce X to Zero](https://leetcode.com/problems/minimum-operations-to-reduce-x-to-zero/?show=1) | [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/?show=1) |
+| [209. Minimum Size Subarray Sum](https://leetcode.com/problems/minimum-size-subarray-sum/?show=1) | [209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/?show=1) |
+| [219. Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/?show=1) | [219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/?show=1) |
+| [220. Contains Duplicate III](https://leetcode.com/problems/contains-duplicate-iii/?show=1) | [220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/?show=1) |
+| [395. Longest Substring with At Least K Repeating Characters](https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/?show=1) | [395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/?show=1) |
+| [424. Longest Repeating Character Replacement](https://leetcode.com/problems/longest-repeating-character-replacement/?show=1) | [424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/?show=1) |
+| [713. Subarray Product Less Than K](https://leetcode.com/problems/subarray-product-less-than-k/?show=1) | [713. 乘积小于K的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/?show=1) |
+| [862. Shortest Subarray with Sum at Least K](https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/?show=1) | [862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/?show=1) |
+| - | [剑指 Offer 48. 最长不含重复字符的子字符串](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/?show=1) |
+| - | [剑指 Offer 57 - II. 和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/?show=1) |
+| - | [剑指 Offer II 009. 乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/?show=1) |
+| - | [剑指 Offer II 014. 字符串中的变位词](https://leetcode.cn/problems/MPnaiL/?show=1) |
+| - | [剑指 Offer II 015. 字符串中的所有变位词](https://leetcode.cn/problems/VabMRr/?show=1) |
+| - | [剑指 Offer II 016. 不含重复字符的最长子字符串](https://leetcode.cn/problems/wtcaE1/?show=1) |
+| - | [剑指 Offer II 017. 含有所有字符的最短字符串](https://leetcode.cn/problems/M1oyTv/?show=1) |
+| - | [剑指 Offer II 057. 值和下标之差都在给定的范围内](https://leetcode.cn/problems/7WqeDu/?show=1) |
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1260. Shift 2D Grid](https://leetcode.com/problems/shift-2d-grid/?show=1) | [1260. 二维网格迁移](https://leetcode.cn/problems/shift-2d-grid/?show=1) |
+
+
+引用本文的文章
+
+ - [动态规划问题的两种穷举视角](https://labuladong.github.io/article/fname.html?fname=动归两种视角)
+ - [谁能想到,斗地主也能玩出算法](https://labuladong.github.io/article/fname.html?fname=斗地主)
+
+
+
+
+
+
+
+**_____________**
+
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
\ No newline at end of file
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md"
index ececcc8..85f16b4 100644
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md"
+++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md"
@@ -1,11 +1,5 @@
# LRU 缓存淘汰算法设计
-
-
-
-
-
-
+引用本文的文章
+
+ - [一文看懂 session 和 cookie](https://labuladong.github.io/article/fname.html?fname=session和cookie)
+ - [常数时间删除/查找数组中的任意元素](https://labuladong.github.io/article/fname.html?fname=随机集合)
+ - [数据结构设计:最大栈](https://labuladong.github.io/article/fname.html?fname=最大栈)
+ - [算法就像搭乐高:带你手撸 LFU 算法](https://labuladong.github.io/article/fname.html?fname=LFU)
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer II 031. 最近最少使用缓存](https://leetcode.cn/problems/OrIXps/?show=1) |
+
+
+引用本文的文章
+
+ - [东哥带你刷二叉树(思路篇)](https://labuladong.github.io/article/fname.html?fname=二叉树系列1)
+ - [算法笔试「骗分」套路](https://labuladong.github.io/article/fname.html?fname=刷题技巧)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md"
index 8187fff..ebea93a 100644
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md"
+++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md"
@@ -1,9 +1,5 @@
# 一行代码就能解决的算法题
-
-
-
-
+引用本文的文章
+
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [经典动态规划:博弈问题](https://labuladong.github.io/article/fname.html?fname=动态规划之博弈问题)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md"
index 9218a52..4e49312 100644
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md"
+++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md"
@@ -1,9 +1,5 @@
# 二分查找高效判定子序列
-
-
-
-
+引用本文的文章
+
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+ - [二分搜索怎么用?我和快手面试官进行了深度探讨](https://labuladong.github.io/article/fname.html?fname=二分分割子数组)
+ - [我写了首诗,把二分搜索算法变成了默写题](https://labuladong.github.io/article/fname.html?fname=二分查找详解)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [经典动态规划:高楼扔鸡蛋](https://labuladong.github.io/article/fname.html?fname=高楼扔鸡蛋问题)
+ - [讲两道常考的阶乘算法题](https://labuladong.github.io/article/fname.html?fname=阶乘题目)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [1201. Ugly Number III](https://leetcode.com/problems/ugly-number-iii/?show=1) | [1201. 丑数 III](https://leetcode.cn/problems/ugly-number-iii/?show=1) |
+| - | [剑指 Offer II 073. 狒狒吃香蕉](https://leetcode.cn/problems/nZZqjQ/?show=1) |
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer II 027. 回文链表](https://leetcode.cn/problems/aMhZSa/?show=1) |
+
+
+引用本文的文章
+
+ - [二分图判定算法](https://labuladong.github.io/article/fname.html?fname=二分图)
+
+
+
+
+
+
+
+**_____________**
+
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md"
deleted file mode 100644
index 35218b3..0000000
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md"
+++ /dev/null
@@ -1,190 +0,0 @@
-# 如何去除有序数组的重复元素
-
-我们知道对于数组来说,在尾部插入、删除元素是比较高效的,时间复杂度是 O(1),但是如果在中间或者开头插入、删除元素,就会涉及数据的搬移,时间复杂度为 O(N),效率较低。
-
-所以对于一般处理数组的算法问题,我们要尽可能只对数组尾部的元素进行操作,以避免额外的时间复杂度。
-
-这篇文章讲讲如何对一个有序数组去重,先看下题目:
-
-![](../pictures/%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D/title.png)
-
-显然,由于数组已经排序,所以重复的元素一定连在一起,找出它们并不难,但如果毎找到一个重复元素就立即删除它,就是在数组中间进行删除操作,整个时间复杂度是会达到 O(N^2)。而且题目要求我们原地修改,也就是说不能用辅助数组,空间复杂度得是 O(1)。
-
-其实,**对于数组相关的算法问题,有一个通用的技巧:要尽量避免在中间删除元素,那我就先想办法把这个元素换到最后去**。这样的话,最终待删除的元素都拖在数组尾部,一个一个 pop 掉就行了,每次操作的时间复杂度也就降低到 O(1) 了。
-
-按照这个思路呢,又可以衍生出解决类似需求的通用方式:双指针技巧。具体一点说,应该是快慢指针。
-
-我们让慢指针 `slow` 走左后面,快指针 `fast` 走在前面探路,找到一个不重复的元素就告诉 `slow` 并让 `slow` 前进一步。这样当 `fast` 指针遍历完整个数组 `nums` 后,**`nums[0..slow]` 就是不重复元素,之后的所有元素都是重复元素**。
-
-```java
-int removeDuplicates(int[] nums) {
- int n = nums.length;
- if (n == 0) return 0;
- int slow = 0, fast = 1;
- while (fast < n) {
- if (nums[fast] != nums[slow]) {
- slow++;
- // 维护 nums[0..slow] 无重复
- nums[slow] = nums[fast];
- }
- fast++;
- }
- // 长度为索引 + 1
- return slow + 1;
-}
-```
-
-看下算法执行的过程:
-
-![](../pictures/%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D/1.gif)
-
-再简单扩展一下,如果给你一个有序链表,如何去重呢?其实和数组是一模一样的,唯一的区别是把数组赋值操作变成操作指针而已:
-
-```java
-ListNode deleteDuplicates(ListNode head) {
- if (head == null) return null;
- ListNode slow = head, fast = head.next;
- while (fast != null) {
- if (fast.val != slow.val) {
- // nums[slow] = nums[fast];
- slow.next = fast;
- // slow++;
- slow = slow.next;
- }
- // fast++
- fast = fast.next;
- }
- // 断开与后面重复元素的连接
- slow.next = null;
- return head;
-}
-```
-
-![](../pictures/%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D/2.gif)
-
-
-**_____________**
-
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
-
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
-
-
+引用本文的文章
+
+ - [两种思路解决单词拼接问题](https://labuladong.github.io/article/fname.html?fname=单词拼接)
+ - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法)
+ - [回溯算法解题套路框架](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版)
+ - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
+ - [算法时空复杂度分析实用指南](https://labuladong.github.io/article/fname.html?fname=时间复杂度)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [17. Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/?show=1) | [17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/?show=1) |
+| [491. Increasing Subsequences](https://leetcode.com/problems/increasing-subsequences/?show=1) | [491. 递增子序列](https://leetcode.cn/problems/increasing-subsequences/?show=1) |
+| - | [剑指 Offer 38. 字符串的排列](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/?show=1) |
+| - | [剑指 Offer II 079. 所有子集](https://leetcode.cn/problems/TVdhkn/?show=1) |
+| - | [剑指 Offer II 080. 含有 k 个元素的组合](https://leetcode.cn/problems/uUsW3B/?show=1) |
+| - | [剑指 Offer II 081. 允许重复选择元素的组合](https://leetcode.cn/problems/Ygoe9J/?show=1) |
+| - | [剑指 Offer II 083. 没有重复元素集合的全排列](https://leetcode.cn/problems/VvJkup/?show=1) |
+
+
+引用本文的文章
+
+ - [并查集(Union-Find)算法](https://labuladong.github.io/article/fname.html?fname=UnionFind算法详解)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| - | [剑指 Offer 13. 机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/?show=1) |
+| - | [剑指 Offer II 105. 岛屿的最大面积](https://leetcode.cn/problems/ZL6zAn/?show=1) |
+
+
+引用本文的文章
+
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+
+
+
+
+
+
+
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [264. Ugly Number II](https://leetcode.com/problems/ugly-number-ii/?show=1) | [264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/?show=1) |
+
+
+引用本文的文章
+
+ - [一道求中位数的算法题把我整不会了](https://labuladong.github.io/article/fname.html?fname=数据流中位数)
+ - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
+ - [带权重的随机选择算法](https://labuladong.github.io/article/fname.html?fname=随机权重)
+ - [常数时间删除/查找数组中的任意元素](https://labuladong.github.io/article/fname.html?fname=随机集合)
+
+
+
+
+
+
+
**_____________**
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md"
deleted file mode 100644
index 54c0731..0000000
--- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md"
+++ /dev/null
@@ -1,293 +0,0 @@
-# 如何寻找消失的元素
-
-
-
-
-![](../pictures/souyisou.png)
-
-**《labuladong 的算法秘籍》、《labuladong 的刷题笔记》两本 PDF 和刷题插件 2.0 免费开放下载,详情见 [labuladong 的刷题三件套正式发布](https://mp.weixin.qq.com/s/yN4cHQRsFa5SWlacopHXYQ)**~
-
-读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
-
-[448.找到所有数组中消失的数字](https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array)
-
-**-----------**
-
-之前也有文章写过几个有趣的智力题,今天再聊一道巧妙的题目。
-
-题目非常简单:
-
-![](../pictures/缺失元素/title.png)
-
-给一个长度为 n 的数组,其索引应该在 `[0,n)`,但是现在你要装进去 n + 1 个元素 `[0,n]`,那么肯定有一个元素装不下嘛,请你找出这个缺失的元素。
-
-这道题不难的,我们应该很容易想到,把这个数组排个序,然后遍历一遍,不就很容易找到缺失的那个元素了吗?
-
-或者说,借助数据结构的特性,用一个 HashSet 把数组里出现的数字都储存下来,再遍历 `[0,n]` 之间的数字,去 HashSet 中查询,也可以很容易查出那个缺失的元素。
-
-排序解法的时间复杂度是 O(NlogN),HashSet 的解法时间复杂度是 O(N),但是还需要 O(N) 的空间复杂度存储 HashSet。
-
-**第三种方法是位运算**。
-
-对于异或运算(`^`),我们知道它有一个特殊性质:一个数和它本身做异或运算结果为 0,一个数和 0 做异或运算还是它本身。
-
-而且异或运算满足交换律和结合律,也就是说:
-
-2 ^ 3 ^ 2 = 3 ^ (2 ^ 2) = 3 ^ 0 = 3
-
-而这道题索就可以通过这些性质巧妙算出缺失的那个元素。比如说 `nums = [0,3,1,4]`:
-
-![](../pictures/缺失元素/1.jpg)
-
-
-为了容易理解,我们假设先把索引补一位,然后让每个元素和自己相等的索引相对应:
-
-![](../pictures/缺失元素/2.jpg)
-
-
-这样做了之后,就可以发现除了缺失元素之外,所有的索引和元素都组成一对儿了,现在如果把这个落单的索引 2 找出来,也就找到了缺失的那个元素。
-
-如何找这个落单的数字呢,**只要把所有的元素和索引做异或运算,成对儿的数字都会消为 0,只有这个落单的元素会剩下**,也就达到了我们的目的。
-
-```java
-int missingNumber(int[] nums) {
- int n = nums.length;
- int res = 0;
- // 先和新补的索引异或一下
- res ^= n;
- // 和其他的元素、索引做异或
- for (int i = 0; i < n; i++)
- res ^= i ^ nums[i];
- return res;
-}
-```
-
-![](../pictures/缺失元素/3.jpg)
-
-由于异或运算满足交换律和结合律,所以总是能把成对儿的数字消去,留下缺失的那个元素的。
-
-至此,时间复杂度 O(N),空间复杂度 O(1),已经达到了最优,我们是否就应该打道回府了呢?
-
-如果这样想,说明我们受算法的毒害太深,随着我们学习的知识越来越多,反而容易陷入思维定式,这个问题其实还有一个特别简单的解法:**等差数列求和公式**。
-
-题目的意思可以这样理解:现在有个等差数列 0, 1, 2,..., n,其中少了某一个数字,请你把它找出来。那这个数字不就是 `sum(0,1,..n) - sum(nums)` 嘛?
-
-```java
-int missingNumber(int[] nums) {
- int n = nums.length;
- // 公式:(首项 + 末项) * 项数 / 2
- int expect = (0 + n) * (n + 1) / 2;
-
- int sum = 0;
- for (int x : nums)
- sum += x;
- return expect - sum;
-}
-```
-
-你看,这种解法应该是最简单的,但说实话,我自己也没想到这个解法,而且我去问了几个大佬,他们也没想到这个最简单的思路。相反,如果去问一个初中生,他也许很快就能想到。
-
-做到这一步了,我们是否就应该打道回府了呢?
-
-如果这样想,说明我们对细节的把控还差点火候。在用求和公式计算 `expect` 时,你考虑过**整型溢出**吗?如果相乘的结果太大导致溢出,那么结果肯定是错误的。
-
-刚才我们的思路是把两个和都加出来然后相减,为了避免溢出,干脆一边求和一边减算了。很类似刚才位运算解法的思路,仍然假设 `nums = [0,3,1,4]`,先补一位索引再让元素跟索引配对:
-
-![](../pictures/缺失元素/xor.png)
-
-
-我们让每个索引减去其对应的元素,再把相减的结果加起来,不就是那个缺失的元素吗?
-
-```java
-public int missingNumber(int[] nums) {
- int n = nums.length;
- int res = 0;
- // 新补的索引
- res += n - 0;
- // 剩下索引和元素的差加起来
- for (int i = 0; i < n; i++)
- res += i - nums[i];
- return res;
-}
-```
-
-由于加减法满足交换律和结合律,所以总是能把成对儿的数字消去,留下缺失的那个元素的。
-
-至此这道算法题目经历九曲十八弯,终于再也没有什么坑了。
-
-
-**_____________**
-
-**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitee.io/algo/) 持续更新最新文章**。
-
-**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
-
-
+引用本文的题目
+
+安装 [我的 Chrome 刷题插件](https://mp.weixin.qq.com/s/X-fE9sR4BLi6T9pn7xP4pg) 点开下列题目可直接查看解题思路:
+
+| LeetCode | 力扣 |
+| :----: | :----: |
+| [442. Find All Duplicates in an Array](https://leetcode.com/problems/find-all-duplicates-in-an-array/?show=1) | [442. 数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/?show=1) |
+| [448. Find All Numbers Disappeared in an Array](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/?show=1) | [448. 找到所有数组中消失的数字](https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/?show=1) |
+
+
+引用本文的文章
+
+ - [如何在无限序列中随机抽取元素](https://labuladong.github.io/article/fname.html?fname=水塘抽样)
+
+
+
+
+
+
+
+**_____________**
+
+**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「进群」可加入算法群;回复「PDF」可获取精华文章 PDF**:
+
+![](https://labuladong.github.io/algo/images/souyisou2.png)
--
GitLab