diff --git a/README.md b/README.md
index c969002..0a74a82 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
# labuladong 的算法小抄
-
+
@@ -14,6 +14,10 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
+
+
+好消息,《labuladong 的算法小抄》纸质书出版啦!关注公众号查看详情👆
+
@@ -25,27 +29,36 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
只想要答案的话很容易,题目评论区五花八门的答案,动不动就秀 python 一行代码解决,有那么多人点赞。问题是,你去做算法题,是去学习编程语言的奇技淫巧的,还是学习算法思维的呢?你的快乐,到底源自复制别人的一行代码通过测试,已完成题目 +1,还是源自自己通过逻辑推理和算法框架不看答案写出解法?
-网上总有大佬喷我,说我写这玩意太基础了,根本没必要啰嗦。我只能说大家刷算法就是找工作吃饭的,不是打竞赛的,我也是一路摸爬滚打过来的,我们要的是清楚明白有所得,不是故弄玄虚无所指。不想办法做到通俗易懂,难道要上来先把《算法导论》吹上天,然后把人家都心怀敬仰地劝退?
+网上总有大佬喷我,说我写的东西太基础,要么说不能借助框架思维来学习算法。我只能说大家刷算法就是找工作吃饭的,不是打竞赛的,我也是一路摸爬滚打过来的,我们要的是清楚明白有所得,不是故弄玄虚无所指。
+
+不想办法做到通俗易懂,难道要上来先把《算法导论》吹上天,然后把人家都心怀敬仰地劝退?
**做啥事情做多了,都能发现套路的,我把各种算法套路框架总结出来,相信可以帮助其他人少走弯路**。我这个纯靠自学的小童鞋,花了一年时间刷题和总结,自己写了一份算法小抄,后面有目录,这里就不废话了。
### 使用方法
-1、**先给本仓库点个 star,满足一下我的虚荣心**,文章质量绝对值你一个 star。我还在继续创作,给我一点继续写文的动力,感谢。
+**1、先给本仓库点个 star,满足一下我的虚荣心**,文章质量绝对值你一个 star。我还在继续创作,给我一点继续写文的动力,感谢。
-2、**建议收藏我的 Gitbook 网站,每篇文章开头都有对应的力扣题目链接,可以边看文章边刷题**:
+**2、建议收藏我的在线网站,每篇文章开头都有对应的力扣题目链接,可以边看文章边刷题**:
-Gitbook 地址:https://labuladong.gitbook.io/algo/
+Gitbook 地址:https://labuladong.gitbook.io/algo
-3、建议关注我的公众号 **labuladong**,坚持高质量原创,说是最良心最硬核的技术公众号都不为过。本仓库的文章就是从公众号里整理出来的**一部分**内容,公众号后台回复关键词【电子书】可以获得这份小抄的完整版本;回复【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
+GitBook 在国内访问速度很慢,且常被攻击,我特意部署了两个镜像站点,大家可根据网络情况自行选择:
+
+GitHub Pages 地址:https://labuladong.github.io/algo
+
+Gitee Pages 地址:https://labuladong.gitee.io/algo
+
+
+**3、建议关注我的公众号 labuladong,坚持高质量原创,说是最良心最硬核的技术公众号都不为过**。本仓库的文章就是从公众号里整理出来的**一部分**内容,公众号可以查看更多内容;公众号后台回复关键词【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
-4、欢迎关注 [我的知乎](https://www.zhihu.com/people/labuladong)。
+**4、欢迎关注 [我的知乎](https://www.zhihu.com/people/labuladong)**。
-我一直在写优质文章,但是后续的文章只发布到公众号/gitbook/知乎,不能开放到 GitHub。因为本仓库太火了,很多人直接拿我的文章去开付费专栏,价格还不便宜,我这免费写给您看,何必掏冤枉钱呢?所以多多关注本作者,多多宣传,谁也不希望劣币驱逐良币不是么?
+我一直在写优质文章,但是后续的文章只发布到公众号/网站/知乎,不能开放到 GitHub。因为本仓库太火了,很多人直接拿我的文章去开付费专栏,价格还不便宜,我这免费写给您看,何必掏冤枉钱呢?所以多多关注本作者,多多宣传,谁也不希望劣币驱逐良币不是么?
其他的先不多说了,直接上干货吧,我们一起搞定 LeetCode,感受一下支配算法的乐趣。
diff --git a/出版推广1.jpeg b/出版推广1.jpeg
new file mode 100644
index 0000000..6205bfe
Binary files /dev/null and b/出版推广1.jpeg differ
diff --git a/动态规划系列/动态规划之KMP字符匹配算法.md b/动态规划系列/动态规划之KMP字符匹配算法.md
index a0cc4ce..5cde805 100644
--- a/动态规划系列/动态规划之KMP字符匹配算法.md
+++ b/动态规划系列/动态规划之KMP字符匹配算法.md
@@ -431,4 +431,40 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+[MoguCloud](https://github.com/MoguCloud) 提供 实现 strStr() 的 Python 完整代码:
+```py
+class Solution:
+ def strStr(self, haystack: str, needle: str) -> int:
+ # 边界条件判断
+ if not needle:
+ return 0
+ pat = needle
+ txt = haystack
+
+ M = len(pat)
+ # dp[状态][字符] = 下个状态
+ dp = [[0 for _ in range(256)] for _ in pat]
+ # base case
+ dp[0][ord(pat[0])] = 1
+ # 影子状态 X 初始化为 0
+ X = 0
+ for j in range(1, M):
+ for c in range(256):
+ dp[j][c] = dp[X][c]
+ dp[j][ord(pat[j])] = j + 1
+ # 更新影子状态
+ X = dp[X][ord(pat[j])]
+
+ N = len(txt)
+ # pat 初始状态为 0
+ j = 0
+ for i in range(N):
+ # 计算 pat 的下一个状态
+ j = dp[j][ord(txt[i])]
+ # 到达终止态,返回结果
+ if j == M:
+ return i - M + 1
+ # 没到达终止态,匹配失败
+ return -1
+```
diff --git a/动态规划系列/动态规划之博弈问题.md b/动态规划系列/动态规划之博弈问题.md
index 9f52fa4..f9082d8 100644
--- a/动态规划系列/动态规划之博弈问题.md
+++ b/动态规划系列/动态规划之博弈问题.md
@@ -22,7 +22,7 @@
上一篇文章 [几道智力题](https://labuladong.gitbook.io/algo) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
-博弈类问题的套路都差不多,下文举例讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
+博弈类问题的套路都差不多,下文参考 [这个 YouTube 视频](https://www.youtube.com/watch?v=WxpIHvsu1RI) 的思路讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
我们「石头游戏」改的更具有一般性:
@@ -215,4 +215,116 @@ int stoneGame(int[] piles) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+
+
+
+
+* python3版本
+
+由[SCUHZS](https://github.com/brucecat)提供
+
+这里采取的是三维的做法
+
+```python
+class Solution:
+ def stoneGame(self, piles: List[int]) -> bool:
+ n = len(piles)
+
+ # 初始化一个n*n的矩阵 dp数组
+ dp = [[None] * n for i in range(0, n)]
+
+ # 在三角区域填充
+ for i in range(n):
+ for j in range(i, n):
+ dp[i][j] = [0, 0]
+
+ # 填入base case
+ for i in range(0, n):
+ dp[i][i][0] = piles[i]
+ dp[i][i][1] = 0
+
+ # 斜着遍历数组
+ for l in range(2, n + 1):
+ for i in range(0, n-l+1):
+ j = l + i - 1
+
+
+ # 先手选择最左边或最右边的分数
+ left = piles[i] + dp[i + 1][j][1]
+ right = piles[j] + dp[i][j - 1][1]
+
+ # 套用状态转移方程
+ if left > right:
+ dp[i][j][0] = left
+ dp[i][j][1] = dp[i + 1][j][0]
+ else:
+ dp[i][j][0] = right
+ dp[i][j][1] = dp[i][j - 1][0]
+
+ res = dp[0][n - 1]
+
+ return res[0] - res[1] > 0
+
+```
+
+
+
+压缩成一维数组,以减小空间复杂度,做法如下。
+
+```python
+class Solution:
+ def stoneGame(self, piles: List[int]) -> bool:
+ dp = piles.copy()
+
+ for i in range(len(piles) - 1, -1, -1): # 从下往上遍历
+ for j in range(i, len(piles)): # 从前往后遍历
+ dp[j] = max(piles[i] - dp[j], piles[j] - dp[j - 1]) # 计算之后覆盖一维数组的对应位置
+
+ return dp[len(piles) - 1] > 0
+
+
+```
+
+* C++ 版本
+
+由 [TCeason](https://github.com/TCeason) 提供
+
+这里采用 hash map 来解决问题
+
+```cpp
+class Solution {
+public:
+ unordered_map memo;
+
+ int dfs(vector &piles, int index) {
+ // 从两边向中间获取
+ // index 值为 1/2 piles.size() 时可以停止算法
+ if (index == piles.size() / 2)
+ return 0;
+
+ // 减少计算,快速返回已有结果
+ if (memo.count(index))
+ return memo[index];
+
+ // 防止第一次取最右时越界
+ int n = piles.size() - 1;
+
+ // 先手选择最左边或最右边后的分数
+ int l = piles[index] + dfs(piles, index + 1);
+ int r = piles[n - index] + dfs(piles, index + 1);
+
+ // 返回先手左或右边的最高分
+ return memo[index] = max(l, r);
+ }
+
+ bool stoneGame(vector& piles) {
+ // 最佳发挥时:
+ // 先手得分 * 2 > 总大小 则先手者胜利
+ return dfs(piles, 0) * 2 > accumulate(begin(piles), end(piles), 0);
+ }
+};
+
+```
+
diff --git a/动态规划系列/动态规划设计:最长递增子序列.md b/动态规划系列/动态规划设计:最长递增子序列.md
index c905a60..edef473 100644
--- a/动态规划系列/动态规划设计:最长递增子序列.md
+++ b/动态规划系列/动态规划设计:最长递增子序列.md
@@ -10,8 +10,8 @@

相关推荐:
- * [动态规划设计:最大子数组](../动态规划系列/最大子数组.md)
- * [一文学会递归解题](../投稿/一文学会递归解题.md)
+ * [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo)
+ * [一文学会递归解题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
@@ -19,7 +19,7 @@
**-----------**
-也许有读者看了前文 [动态规划详解](../动态规划系列/动态规划详解进阶.md),学会了动态规划的套路:找到了问题的「状态」,明确了 `dp` 数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
+也许有读者看了前文 [动态规划详解](https://labuladong.gitbook.io/algo),学会了动态规划的套路:找到了问题的「状态」,明确了 `dp` 数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
不要担心,动态规划的难点本来就在于寻找正确的状态转移方程,本文就借助经典的「最长递增子序列问题」来讲一讲设计动态规划的通用技巧:**数学归纳思想**。
@@ -43,7 +43,7 @@
**我们的定义是这样的:`dp[i]` 表示以 `nums[i]` 这个数结尾的最长递增子序列的长度。**
-PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文[动态规划之子序列问题解题模板](../动态规划系列/子序列问题模板.md) 总结了几种常见套路。你读完本章所有的动态规划问题,就会发现 `dp` 数组的定义方法也就那几种。
+PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文[动态规划之子序列问题解题模板](https://labuladong.gitbook.io/algo) 总结了几种常见套路。你读完本章所有的动态规划问题,就会发现 `dp` 数组的定义方法也就那几种。
根据这个定义,我们就可以推出 base case:`dp[i]` 初始值为 1,因为以 `nums[i]` 结尾的最长递增子序列起码要包含它自己。
@@ -164,7 +164,7 @@ public int lengthOfLIS(int[] nums) {
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是**有序**吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
-PS:旧文[二分查找算法详解](../算法思维系列/二分查找详解.md)详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
+PS:旧文[二分查找算法详解](https://labuladong.gitbook.io/algo)详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
```java
public int lengthOfLIS(int[] nums) {
@@ -215,4 +215,91 @@ public int lengthOfLIS(int[] nums) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+```python 动态规划
+class Solution:
+ def lengthOfLIS(self, nums: List[int]) -> int:
+ n = len(nums)
+ f = [1] * (n)
+
+ for i in range(n):
+ for j in range(i):
+ if nums[j] < nums[i]:
+ f[i] = max(f[i], f[j] + 1)
+
+ res = 0
+ for i in range(n):
+ res = max(res, f[i])
+ return res
+```
+
+```python 二分查找
+class Solution:
+ def lengthOfLIS(self, nums: List[int]) -> int:
+ stack = []
+
+ def find_index(num):
+ l, r = 0, len(stack)
+ while l < r:
+ mid = l + r >> 1
+ if stack[mid] >= num:
+ r = mid
+ else:
+ l = mid + 1
+
+ return r
+
+
+ for num in nums:
+ if not stack or num > stack[-1]:
+ stack.append(num)
+ else:
+ position = find_index(num)
+ stack[position] = num
+
+ return len(stack)
+```
+
+
+[Kian](https://github.com/KianKw/) 提供 C++ 代码
+
+```c++
+class Solution {
+public:
+ int lengthOfLIS(vector& nums) {
+ /* len 为牌的数量 */
+ int len = nums.size();
+ vector top(len, 0);
+ /* 牌堆数初始化为0 */
+ int piles = 0;
+ for (int i = 0; i < len; i++) {
+ /* nums[i] 为要处理的扑克牌 */
+ int poker = nums[i];
+
+ /***** 搜索左侧边界的二分查找 *****/
+ int left = 0, right = piles;
+ while (left < right) {
+ int mid = left + (right - left) / 2;
+ if (top[mid] > poker) {
+ right = mid;
+ } else if (top[mid] < poker) {
+ left = mid + 1;
+ } else if (top[mid] == poker) {
+ right = mid;
+ }
+ }
+ /*********************************/
+
+ /* 没找到合适的牌堆,新建一堆 */
+ if (left == piles)
+ piles++;
+ /* 把这张牌放到牌堆顶 */
+ top[left] = poker;
+ }
+ /* 牌堆数就是 LIS 长度 */
+ return piles;
+ }
+};
+```
+
diff --git a/动态规划系列/动态规划详解进阶.md b/动态规划系列/动态规划详解进阶.md
index bff29f4..f51608e 100644
--- a/动态规划系列/动态规划详解进阶.md
+++ b/动态规划系列/动态规划详解进阶.md
@@ -146,6 +146,8 @@ int helper(vector& memo, int n) {
```cpp
int fib(int N) {
+ if (N == 0) return 0;
+ if (N == 1) return 1;
vector dp(N + 1, 0);
// base case
dp[1] = dp[2] = 1;
@@ -200,7 +202,7 @@ int coinChange(int[] coins, int amount);
比如说 `k = 3`,面值分别为 1,2,5,总金额 `amount = 11`。那么最少需要 3 枚硬币凑出,即 11 = 5 + 5 + 1。
-你认为计算机应该如何解决这个问题?显然,就是把所有肯能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。
+你认为计算机应该如何解决这个问题?显然,就是把所有可能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。
**1、暴力递归**
@@ -366,4 +368,45 @@ PS:为啥 `dp` 数组初始化为 `amount + 1` 呢,因为凑成 `amount` 金
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[DapangLiu](https://github.com/DapangLiu) 提供 509. 斐波那契数 Python3 解法代码:
+
+递归写法
+
+```python
+class Solution:
+ def fib(self, N: int) -> int:
+ if N <= 1:
+ return N
+ return self.fib(N-1) + self.fib(N-2)
+```
+
+动态规划写法
+
+```python
+class Solution:
+ def fib(self, N: int) -> int:
+ if N == 0:
+ return 0
+ # init
+ result = [0 for i in range(N+1)]
+ result[1] = 1
+
+ # status transition
+ for j in range(2, N+1):
+ result[j] = result[j-1] + result[j-2]
+ return result[-1]
+```
+
+动态规划写法 (状态压缩)
+
+```python
+class Solution:
+ def fib(self, n: int) -> int:
+ # current status only depends on two previous status
+ dp_0, dp_1 = 0, 1
+ for _ in range(n):
+ dp_0, dp_1 = dp_1, dp_0 + dp_1
+ return dp_0
+```
\ No newline at end of file
diff --git a/动态规划系列/团灭股票问题.md b/动态规划系列/团灭股票问题.md
index be2b463..61f64a3 100644
--- a/动态规划系列/团灭股票问题.md
+++ b/动态规划系列/团灭股票问题.md
@@ -16,7 +16,7 @@
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
-[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/)
+[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock)
[买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
@@ -32,7 +32,7 @@
很多读者抱怨 LeetCode 的股票系列问题奇技淫巧太多,如果面试真的遇到这类问题,基本不会想到那些巧妙的办法,怎么办?**所以本文拒绝奇技淫巧,而是稳扎稳打,只用一种通用方法解决所用问题,以不变应万变**。
-这篇文章用状态机的技巧来解决,可以全部提交通过。不要觉得这个名词高大上,文学词汇而已,实际上就是 DP table,看一眼就明白了。
+这篇文章参考 [英文版高赞题解](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems) 的思路,用状态机的技巧来解决,可以全部提交通过。不要觉得这个名词高大上,文学词汇而已,实际上就是 DP table,看一眼就明白了。
先随便抽出一道题,看看别人的解法:
diff --git a/动态规划系列/抢房子.md b/动态规划系列/抢房子.md
index 35a0fb6..081c583 100644
--- a/动态规划系列/抢房子.md
+++ b/动态规划系列/抢房子.md
@@ -258,4 +258,66 @@ int[] dp(TreeNode root) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+[Shantom](https://github.com/Shantom) 提供 198. House Robber I Python3 解法代码:
+
+```Python
+class Solution:
+ def rob(self, nums: List[int]) -> int:
+ # 当前,上一间,上上间
+ cur, pre1, pre2 = 0, 0, 0
+
+ for num in nums:
+ # 当前 = max(上上间+(抢当前),上间(放弃当前))
+ cur = max(pre2 + num, pre1)
+ pre2 = pre1
+ pre1 = cur
+
+ return cur
+```
+[Shantom](https://github.com/Shantom) 提供 213. House Robber II Python3 解法代码:
+
+```Python
+class Solution:
+ def rob(self, nums: List[int]) -> int:
+ # 只有一间时不成环
+ if len(nums) == 1:
+ return nums[0]
+
+ # 该函数同198题
+ def subRob(nums: List[int]) -> int:
+ # 当前,上一间,上上间
+ cur, pre1, pre2 = 0, 0, 0
+ for num in nums:
+ # 当前 = max(上上间+(抢当前),上间(放弃当前))
+ cur = max(pre2 + num, pre1)
+ pre2 = pre1
+ pre1 = cur
+ return cur
+
+ # 不考虑第一间或者不考虑最后一间
+ return max(subRob(nums[:-1]), subRob(nums[1:]))
+```
+[Shantom](https://github.com/Shantom) 提供 337. House Robber III Python3 解法代码:
+
+```Python
+class Solution:
+ def rob(self, root: TreeNode) -> int:
+ # 返回值0项为不抢该节点,1项为抢该节点
+ def dp(root):
+ if not root:
+ return 0, 0
+
+ left = dp(root.left)
+ right = dp(root.right)
+
+ # 抢当前,则两个下家不抢
+ do = root.val + left[0] + right[0]
+ # 不抢当前,则下家随意
+ do_not = max(left) + max(right)
+
+ return do_not, do
+
+ return max(dp(root))
+```
+
diff --git a/动态规划系列/最优子结构.md b/动态规划系列/最优子结构.md
index 2dc21f2..caf68cf 100644
--- a/动态规划系列/最优子结构.md
+++ b/动态规划系列/最优子结构.md
@@ -54,7 +54,7 @@ return result;
当然,上面这个例子太简单了,不过请读者回顾一下,我们做动态规划问题,是不是一直在求各种最值,本质跟我们举的例子没啥区别,无非需要处理一下重叠子问题。
-前文不[同定义不同解法](../动态规划系列/动态规划之四键键盘.md) 和 [高楼扔鸡蛋进阶](../动态规划系列/高楼扔鸡蛋问题.md) 就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
+前文不[同定义不同解法](https://labuladong.gitbook.io/algo) 和 [高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo) 就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
再举个常见但也十分简单的例子,求一棵二叉树的最大值,不难吧(简单起见,假设节点中的值都是非负数):
diff --git a/动态规划系列/最长公共子序列.md b/动态规划系列/最长公共子序列.md
index bee8947..bff1721 100644
--- a/动态规划系列/最长公共子序列.md
+++ b/动态规划系列/最长公共子序列.md
@@ -1,4 +1,4 @@
-# 最长公共子序列
+# 最长公共子序列
@@ -147,4 +147,64 @@ else:
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+
+[Edwenc](https://github.com/Edwenc) 提供 C++ 代码:
+
+```C++
+class Solution {
+public:
+ int longestCommonSubsequence(string text1, string text2) {
+ // 先计算两条字符串的长度
+ int m = text1.size();
+ int n = text2.size();
+
+ // 构建dp矩阵 默认初始值0
+ // 这里会多扩建一边和一列
+ // 因为dp[i][j]的含义是:对于 s1[1..i] 和 s2[1..j],它们的LCS长度是 dp[i][j]。
+ // 所以当i或者j为零时 LCS的长度默认为0
+ vector< vector > dp ( m+1 , vector ( n+1 , 0 ) );
+
+ // 状态转移
+ // i、j都从1开始遍历 因为下面的操作中都会-1 相当于从0开始
+ for ( int i=1 ; i
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+[ChenjieXu](https://github.com/ChenjieXu) 提供Python版本代码:
+
+```python3
+def minDistance(word1, word2):
+ m, n = len(word1), len(word2)
+ # 创建 DP 数组
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
+
+ # base case初始化
+ for i in range(m + 1):
+ dp[i][0] = i
+ for j in range(n + 1):
+ dp[0][j] = j
+
+ # 自底向上求解
+ for i in range(1, m + 1):
+ for j in range(1, n + 1):
+ # 状态转移方程
+ if word1[i - 1] == word2[j - 1]:
+ dp[i][j] = dp[i - 1][j - 1]
+ else:
+ dp[i][j] = min(dp[i - 1][j] + 1,
+ dp[i][j - 1] + 1,
+ dp[i - 1][j - 1] + 1)
+ # 储存着整个 word1 和 word2 的最小编辑距离
+ return dp[m][n]
+````
\ No newline at end of file
diff --git a/动态规划系列/贪心算法之区间调度问题.md b/动态规划系列/贪心算法之区间调度问题.md
index 70e27fc..3660bdc 100644
--- a/动态规划系列/贪心算法之区间调度问题.md
+++ b/动态规划系列/贪心算法之区间调度问题.md
@@ -1,4 +1,4 @@
-# 贪心算法之区间调度问题
+# 贪心算法之区间调度问题
@@ -158,4 +158,43 @@ int findMinArrowShots(int[][] intvs) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+### python
+Edwenc 提供 第435题的python3 代码:
+
+```python
+class Solution:
+ def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
+ ### 思路是首先找到不重叠的区间的个数
+ ### 然后再用总个数减去不重叠个数
+ ### 获得的就是 需要移除的个数
+
+ # 首先获得区间的个数 为0的话就不用移除
+ n = len(intervals)
+ if n==0:
+ return 0
+
+ # 按照每个区间的右端点值进行排序
+ sorted_list = sorted( intervals , key=lambda x: x[1] )
+
+ # 不重叠区间个数至少是1
+ count = 1
+
+ # end是所有不重叠的区间中 最大的右端点
+ # end的初始值即是sorted_list[0]的右端点
+ end = sorted_list[0][1]
+
+ # 从1开始往后找 因为0在上面已经取过了
+ for i in range(1,n):
+ # start是当前区间左端点值
+ start = sorted_list[i][0]
+ # 如果当前左端点比最大右端点都大了(可能相等)
+ # 说明两区间不重叠 count+1 再更新end
+ if start>=end:
+ count += 1
+ end = sorted_list[i][1]
+
+ # 最后返回的是 需要移除的区间个数
+ return n-count
+```
\ No newline at end of file
diff --git a/动态规划系列/高楼扔鸡蛋问题.md b/动态规划系列/高楼扔鸡蛋问题.md
index 70c63a0..68bd9b8 100644
--- a/动态规划系列/高楼扔鸡蛋问题.md
+++ b/动态规划系列/高楼扔鸡蛋问题.md
@@ -243,7 +243,7 @@ def superEggDrop(self, K: int, N: int) -> int:
return dp(K, N)
```
-这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](../动态规划系列/高楼扔鸡蛋进阶.md)
+这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo)
我觉得吧,我们这种解法就够了:找状态,做选择,足够清晰易懂,可流程化,可举一反三。掌握这套框架学有余力的话,再去考虑那些奇技淫巧也不迟。
diff --git a/数据结构系列/二叉搜索树操作集锦.md b/数据结构系列/二叉搜索树操作集锦.md
index cac9c8c..ffe8b5c 100644
--- a/数据结构系列/二叉搜索树操作集锦.md
+++ b/数据结构系列/二叉搜索树操作集锦.md
@@ -312,10 +312,97 @@ void BST(TreeNode root, int target) {
======其他语言代码======
+### 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);
+ }
+};
+```
+
+### 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 代码:
-```python3
-
+```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
@@ -361,5 +448,4 @@ class Solution:
while node.right:
node = node.right
return node
-
```
\ No newline at end of file
diff --git a/数据结构系列/单调栈.md b/数据结构系列/单调栈.md
index fca0ca8..d49168b 100644
--- a/数据结构系列/单调栈.md
+++ b/数据结构系列/单调栈.md
@@ -11,7 +11,7 @@

相关推荐:
- * [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
+* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
@@ -20,7 +20,7 @@
[503.下一个更大元素II](https://leetcode-cn.com/problems/next-greater-element-ii)
-[1118.一月有多少天](https://leetcode-cn.com/problems/number-of-days-in-a-month)
+[739.每日温度](https://leetcode-cn.com/problems/daily-temperatures/)
**-----------**
@@ -82,7 +82,7 @@ vector nextGreaterElement(vector& nums) {
### 问题变形
-单调栈的使用技巧差不多了,来一个简单的变形,力扣第 1118 题「一月有多少天」:
+单调栈的使用技巧差不多了,来一个简单的变形,力扣第 739 题「每日温度」:
给你一个数组 `T`,这个数组存放的是近几天的天气气温,你返回一个等长的数组,计算:**对于每一天,你还要至少等多少天才能等到一个更暖和的气温;如果等不到那一天,填 0**。
@@ -181,4 +181,26 @@ vector nextGreaterElements(vector& nums) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+### java
+
+```java
+// 739. Daily Temperatures
+class Solution {
+ public int[] dailyTemperatures(int[] T) {
+ Stack stack = new Stack<>();
+ int[] ans = new int[T.length];
+ for (int i = 0; i < T.length; i++) {
+ // 如果压栈之后不满足单调递减,弹出元素,直至保持单调性
+ while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
+ int index = stack.pop();
+ // 被弹出的元素(T[index])都是小于当前的元素(T[i]),由于栈内元素单调递减,大于被弹出元素(index)的最近的就是当前元素(i)
+ ans[index] = i - index;
+ }
+ stack.push(i);
+ }
+ return ans;
+ }
+}
+```
+
diff --git a/数据结构系列/单调队列.md b/数据结构系列/单调队列.md
index eb298a1..140391e 100644
--- a/数据结构系列/单调队列.md
+++ b/数据结构系列/单调队列.md
@@ -210,4 +210,105 @@ vector maxSlidingWindow(vector& nums, int k) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+### python3
+
+由[SCUHZS](ttps://github.com/brucecat)提供
+
+
+```python
+from collections import deque
+
+class MonotonicQueue(object):
+ def __init__(self):
+ # 双端队列
+ self.data = deque()
+
+ def push(self, n):
+ # 实现单调队列的push方法
+ while self.data and self.data[-1] < n:
+ self.data.pop()
+ self.data.append(n)
+
+ def max(self):
+ # 取得单调队列中的最大值
+ return self.data[0]
+
+ def pop(self, n):
+ # 实现单调队列的pop方法
+ if self.data and self.data[0] == n:
+ self.data.popleft()
+
+
+class Solution:
+ def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
+ # 单调队列实现的窗口
+ window = MonotonicQueue()
+
+ # 结果
+ res = []
+
+ for i in range(0, len(nums)):
+
+ if i < k-1:
+ # 先填满窗口前k-1
+ window.push(nums[i])
+ else:
+ # 窗口向前滑动
+ window.push(nums[i])
+ res.append(window.max())
+ window.pop(nums[i-k+1])
+ return res
+
+```
+
+### java
+
+```java
+class Solution {
+ public int[] maxSlidingWindow(int[] nums, int k) {
+ int len = nums.length;
+ // 判断数组或者窗口长度为0的情况
+ if (len * k == 0) {
+ return new int[0];
+ }
+
+ /*
+ 采用两端扫描的方法
+ 将数组分成大小为 k 的若干个窗口, 对每个窗口分别从左往右和从右往左扫描, 记录扫描的最大值
+ left[] 记录从左往右扫描的最大值
+ right[] 记录从右往左扫描的最大值
+ */
+ int[] left = new int[len];
+ int[] right = new int[len];
+
+ for (int i = 0; i < len; i = i + k) {
+ // 每个窗口中的第一个值
+ left[i] = nums[i];
+ // 窗口的最后边界
+ int index = i + k - 1 >= len ? len - 1 : i + k - 1;
+ // 每个窗口的最后一个值
+ right[index] = nums[index];
+ // 对该窗口从左往右扫描
+ for (int j = i + 1; j <= index; j++) {
+ left[j] = Math.max(left[j - 1], nums[j]);
+ }
+ // 对该窗口从右往左扫描
+ for (int j = index - 1; j >= i; j--) {
+ right[j] = Math.max(right[j + 1], nums[j]);
+ }
+ }
+
+ int[] arr = new int[len - k + 1];
+
+ // 对于第 i 个位置, 它一定是该窗口从右往左扫描数组中的最后一个值, 相对的 i + k - 1 是该窗口从左向右扫描数组中的最后一个位置
+ // 对两者取最大值即可
+ for (int i = 0; i < len - k + 1; i++) {
+ arr[i] = Math.max(right[i], left[i + k - 1]);
+ }
+
+ return arr;
+ }
+}
+```
diff --git a/数据结构系列/设计Twitter.md b/数据结构系列/设计Twitter.md
index 399fbbb..5f6c5f5 100644
--- a/数据结构系列/设计Twitter.md
+++ b/数据结构系列/设计Twitter.md
@@ -302,4 +302,121 @@ PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[happy-yuxuan](https://github.com/happy-yuxuan) 提供 C++ 代码:
+
+```c++
+static int timestamp = 0;
+class Tweet {
+private:
+ int id;
+ int time;
+public:
+ Tweet *next;
+ // id为推文内容,time为发文时间
+ Tweet(int id, int time) {
+ this->id = id;
+ this->time = time;
+ next = nullptr;
+ }
+ int getId() const {
+ return this->id;
+ }
+ int getTime() const {
+ return this->time;
+ }
+};
+class User {
+private:
+ int id;
+public:
+ Tweet *head; // 发布的Twitter,用链表表示
+ unordered_set followed; // 用户关注了那些人
+ User(int userId) {
+ this->id = userId;
+ head = nullptr;
+ // 要先把自己关注了
+ followed.insert(id);
+ }
+ void follow(int userId) {
+ followed.insert(userId);
+ }
+ void unfollow(int userId) {
+ // 不可以取关自己
+ if (userId != this->id)
+ followed.erase(userId);
+ }
+ void post(int contentId) {
+ Tweet *twt = new Tweet(contentId, timestamp);
+ timestamp++;
+ // 将新建的推文插入链表头
+ // 越靠前的推文 timestamp 值越大
+ twt->next = head;
+ head = twt;
+ }
+};
+class Twitter {
+private:
+ // 映射将 userId 和 User 对象对应起来
+ unordered_map userMap;
+ // 判断该用户存不存在系统中,即userMap中存不存在id
+ inline bool contain(int id) {
+ return userMap.find(id) != userMap.end();
+ }
+public:
+ Twitter() {
+ userMap.clear();
+ }
+ /* user 发表一条 tweet 动态 */
+ void postTweet(int userId, int tweetId) {
+ if (!contain(userId))
+ userMap[userId] = new User(userId);
+ userMap[userId]->post(tweetId);
+ }
+ /* 返回该 user 关注的人(包括他自己)最近的动态 id,
+ 最多 10 条,而且这些动态必须按从新到旧的时间线顺序排列。*/
+ vector getNewsFeed(int userId) {
+ vector ret;
+ if (!contain(userId)) return ret;
+ // 构造一个自动通过Tweet发布的time属性从大到小排序的二叉堆
+ typedef function Compare;
+ Compare cmp = [](const Tweet *a, const Tweet *b) {
+ return a->getTime() < b->getTime();
+ };
+ priority_queue, Compare> q(cmp);
+ // 关注列表的用户Id
+ unordered_set &users = userMap[userId]->followed;
+ // 先将所有链表头节点插入优先级队列
+ for (int id : users) {
+ if (!contain(id)) continue;
+ Tweet *twt = userMap[id]->head;
+ if (twt == nullptr) continue;
+ q.push(twt);
+ }
+ while (!q.empty()) {
+ Tweet *t = q.top(); q.pop();
+ ret.push_back(t->getId());
+ if (ret.size() == 10) return ret; // 最多返回 10 条就够了
+ if (t->next)
+ q.push(t->next);
+ }
+ return ret;
+ }
+ /* follower 关注 followee */
+ void follow(int followerId, int followeeId) {
+ // 若 follower 不存在,则新建
+ if (!contain(followerId))
+ userMap[followerId] = new User(followerId);
+ // 若 followee 不存在,则新建
+ if (!contain(followeeId))
+ userMap[followeeId] = new User(followeeId);
+ userMap[followerId]->follow(followeeId);
+ }
+ /* follower 取关 followee,如果 Id 不存在则什么都不做 */
+ void unfollow(int followerId, int followeeId) {
+ if (contain(followerId))
+ userMap[followerId]->unfollow(followeeId);
+ }
+};
+```
\ No newline at end of file
diff --git a/数据结构系列/递归反转链表的一部分.md b/数据结构系列/递归反转链表的一部分.md
index 830e503..1f8bca2 100644
--- a/数据结构系列/递归反转链表的一部分.md
+++ b/数据结构系列/递归反转链表的一部分.md
@@ -218,4 +218,40 @@ ListNode reverseBetween(ListNode head, int m, int n) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[DiamondI](https://github.com/DiamondI) 提供python3版本代码:
+
+思路:递归。时间复杂度为O(n),由于递归调用需要借助栈的空间,因此空间复杂度亦为O(n)。
+
+```python3
+# Definition for singly-linked list.
+# class ListNode:
+# def __init__(self, val=0, next=None):
+# self.val = val
+# self.next = next
+class Solution:
+ def __init__(self):
+ self.__successor = None
+
+ def __reverseN(self, head: ListNode, n: int) -> ListNode:
+ if n == 1:
+ # 记录第 n + 1 个节点
+ self.__successor = head.next;
+ return head;
+ # 以 head.next 为起点,需要反转前 n - 1 个节点
+ last = self.__reverseN(head.next, n - 1);
+
+ head.next.next = head;
+ # 让反转之后的 head 节点和后面的节点连起来
+ head.next = self.__successor;
+ return last;
+
+ def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
+ # base case
+ if m == 1:
+ return self.__reverseN(head, n);
+ # 前进到反转的起点触发 base case
+ head.next = self.reverseBetween(head.next, m - 1, n - 1);
+ return head;
+```
diff --git a/算法思维系列/二分查找详解.md b/算法思维系列/二分查找详解.md
index 502962f..a413541 100644
--- a/算法思维系列/二分查找详解.md
+++ b/算法思维系列/二分查找详解.md
@@ -65,7 +65,7 @@ int binarySearch(int[] nums, int target) {
### 一、寻找一个数(基本的二分搜索)
-这个场景是最简单的,肯能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
+这个场景是最简单的,可能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
```java
int binarySearch(int[] nums, int target) {
@@ -104,7 +104,7 @@ int binarySearch(int[] nums, int target) {
`while(left <= right)` 的终止条件是 `left == right + 1`,写成区间的形式就是 `[right + 1, right]`,或者带个具体的数字进去 `[3, 2]`,可见**这时候区间为空**,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
-`while(left < right)` 的终止条件是 `left == right`,写成区间的形式就是 `[left, right]`,或者带个具体的数字进去 `[2, 2]`,**这时候区间非空**,还有一个数 2,但此时 while 循环终止了。也就是说这区间 `[2, 2]` 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
+`while(left < right)` 的终止条件是 `left == right`,写成区间的形式就是 `[right, right]`,或者带个具体的数字进去 `[2, 2]`,**这时候区间非空**,还有一个数 2,但此时 while 循环终止了。也就是说这区间 `[2, 2]` 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
当然,如果你非要用 `while(left < right)` 也可以,我们已经知道了出错的原因,就打个补丁好了:
diff --git a/算法思维系列/双指针技巧.md b/算法思维系列/双指针技巧.md
index cf5227d..ff2aebe 100644
--- a/算法思维系列/双指针技巧.md
+++ b/算法思维系列/双指针技巧.md
@@ -20,7 +20,7 @@
[141.环形链表II](https://leetcode-cn.com/problems/linked-list-cycle-ii)
-[167.两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum)
+[167.两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted)
**-----------**
@@ -80,6 +80,11 @@ ListNode detectCycle(ListNode head) {
if (fast == slow) break;
}
// 上面的代码类似 hasCycle 函数
+ if (fast == null || fast.next == null) {
+ // fast 遇到空指针说明没有环
+ return null;
+ }
+
slow = head;
while (slow != fast) {
fast = fast.next;
@@ -230,4 +235,25 @@ void reverse(int[] nums) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[ryandeng32](https://github.com/ryandeng32/) 提供 Python 代码
+```python
+class Solution:
+ def hasCycle(self, head: ListNode) -> bool:
+ # 检查链表头是否为None,是的话则不可能为环形
+ if head is None:
+ return False
+ # 快慢指针初始化
+ slow = fast = head
+ # 若链表非环形则快指针终究会遇到None,然后退出循环
+ while fast.next and fast.next.next:
+ # 更新快慢指针
+ slow = slow.next
+ fast = fast.next.next
+ # 快指针追上慢指针则链表为环形
+ if slow == fast:
+ return True
+ # 退出循环,则链表有结束,不可能为环形
+ return False
+```
diff --git a/算法思维系列/字符串乘法.md b/算法思维系列/字符串乘法.md
index 0273744..6c7f67d 100644
--- a/算法思维系列/字符串乘法.md
+++ b/算法思维系列/字符串乘法.md
@@ -101,4 +101,47 @@ string multiply(string num1, string num2) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[Zane Wang](https://github.com/zanecat) 提供 Java 解法代码:
+```java
+public String multiply(String num1, String num2) {
+ // 初始化字符数组
+ char[] s1 = num1.toCharArray();
+ char[] s2 = num2.toCharArray();
+
+ // 结果长度最多为两字符串长度之和
+ int[] res = new int[s1.length + s2.length];
+
+ // 从个位开始遍历,把两数字中每一位相乘
+ for (int i = s1.length - 1; i >= 0; i--) {
+ for (int j = s2.length - 1; j >= 0; j--) {
+ // 计算乘积,并把乘积放在 res 对应的位置, 暂时不考虑进位
+ res[i + j + 1] += (s1[i] - '0') * (s2[j] - '0');
+ }
+ }
+
+ // 从个位再次遍历,如果上一次遍历中两数乘积为两位数,进位并叠加到前面一位
+ int carry = 0;
+ for (int i = res.length - 1; i >= 0; i--) {
+ int sum = res[i] + carry;
+ res[i] = sum % 10;
+ carry = sum / 10;
+ }
+
+ //遍历res数组,构造最终答案字符串
+ StringBuilder ans = new StringBuilder();
+ int i = 0;
+
+ // 首先找到不为0的第一位
+ while (i < res.length - 1 && res[i] == 0) {
+ i++;
+ }
+
+ // 将后面的数字附加到ans后面
+ while (i < res.length) {
+ ans.append(res[i++]);
+ }
+ return ans.toString();
+}
+```
\ No newline at end of file
diff --git a/算法思维系列/滑动窗口技巧.md b/算法思维系列/滑动窗口技巧.md
index e65a104..67e6539 100644
--- a/算法思维系列/滑动窗口技巧.md
+++ b/算法思维系列/滑动窗口技巧.md
@@ -12,8 +12,8 @@
**最新消息:关注公众号参与活动,有机会成为 [70k star 算法仓库](https://github.com/labuladong/fucking-algorithm) 的贡献者,机不可失时不再来**!
相关推荐:
-* [东哥吃葡萄时竟然吃出一道算法题!](../高频面试系列/吃葡萄.md)
-* [如何寻找缺失的元素](../高频面试系列/消失的元素.md)
+* [东哥吃葡萄时竟然吃出一道算法题!](https://labuladong.gitbook.io/algo)
+* [如何寻找缺失的元素](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
diff --git a/高频面试系列/LRU算法.md b/高频面试系列/LRU算法.md
index b9ca311..caa550a 100644
--- a/高频面试系列/LRU算法.md
+++ b/高频面试系列/LRU算法.md
@@ -346,4 +346,121 @@ class LRUCache {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[gowufang](https://github.com/gowufang)提供第146题C++代码:
+```cpp
+class LRUCache {
+ public:
+ struct node {
+ int val;
+ int key;
+ node* pre;//当前节点的前一个节点
+ node* next;//当前节点的后一个节点
+ node(){}
+ node(int key, int val):key(key), val(val), pre(NULL), next(NULL){}
+ };
+
+ LRUCache(int size) {
+ this->size = size;
+ head = new node();
+ tail = new node();
+ head->next = tail;
+ tail->pre = head;
+ }
+
+
+ void movetohead(node* cur)//相当于一个insert操作,在head 和 head的next之间插入一个节点
+ {
+ node* next = head->next;//head的next先保存起来
+ head->next = cur;//将当前节点移动到head的后面
+ cur->pre = head;//当前节点cur的pre指向head
+ next->pre = cur;
+ cur->next = next;
+ }
+
+ node* deletecurrentnode(node* cur)//移除当前节点
+ {
+ cur->pre->next = cur->next;
+ cur->next->pre = cur->pre;
+ return cur;
+ }
+ void makerecently(node* cur)
+ {
+ node* temp = deletecurrentnode(cur);// 删除 cur,要重新插入到对头
+ movetohead(temp);//cur放到队头去
+ }
+ int get(int key)
+ {
+ int ret = -1;
+ if ( map.count(key))
+ {
+ node* temp = map[key];
+ makerecently(temp);// 将 key 变为最近使用
+ ret = temp->val;
+ }
+ return ret;
+ }
+
+ void put(int key, int value) {
+ if ( map.count(key))
+ {
+ // 修改 key 的值
+ node* temp = map[key];
+ temp->val = value;
+ // 将 key 变为最近使用
+ makerecently(temp);
+ }
+ else
+ {
+ node* cur = new node(key, value);
+ if( map.size()== size )
+ {
+ // 链表头部就是最久未使用的 key
+ node *temp = deletecurrentnode(tail->pre);
+ map.erase(temp->key);
+ }
+ movetohead(cur);
+ map[key] = cur;
+
+ }
+
+ }
+
+ unordered_map map;
+ int size;
+ node* head, *tail;
+
+ };
+```
+
+```python3
+"""
+所谓LRU缓存,根本的难点在于记录最久被使用的键值对,这就设计到排序的问题,
+在python中,天生具备排序功能的字典就是OrderDict。
+注意到,记录最久未被使用的键值对的充要条件是将每一次put/get的键值对都定义为
+最近访问,那么最久未被使用的键值对自然就会排到最后。
+如果你深入python OrderDict的底层实现,就会知道它的本质是个双向链表+字典。
+它内置支持了
+1. move_to_end来重排链表顺序,它可以让我们将最近访问的键值对放到最后面
+2. popitem来弹出键值对,它既可以弹出最近的,也可以弹出最远的,弹出最远的就是我们要的操作。
+"""
+from collections import OrderedDict
+class LRUCache:
+ def __init__(self, capacity: int):
+ self.capacity = capacity # cache的容量
+ self.visited = OrderedDict() # python内置的OrderDict具备排序的功能
+ def get(self, key: int) -> int:
+ if key not in self.visited:
+ return -1
+ self.visited.move_to_end(key) # 最近访问的放到链表最后,维护好顺序
+ return self.visited[key]
+ def put(self, key: int, value: int) -> None:
+ if key not in self.visited and len(self.visited) == self.capacity:
+ # last=False时,按照FIFO顺序弹出键值对
+ # 因为我们将最近访问的放到最后,所以最远访问的就是最前的,也就是最first的,故要用FIFO顺序
+ self.visited.popitem(last=False)
+ self.visited[key]=value
+ self.visited.move_to_end(key) # 最近访问的放到链表最后,维护好顺序
+
+```
diff --git a/高频面试系列/koko偷香蕉.md b/高频面试系列/koko偷香蕉.md
index cc880dd..635291c 100644
--- a/高频面试系列/koko偷香蕉.md
+++ b/高频面试系列/koko偷香蕉.md
@@ -169,4 +169,45 @@ for (int i = 0; i < n; i++)
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[tonytang731](https://https://github.com/tonytang731) 提供 Python3 代码:
+```python
+import math
+
+class Solution:
+ def minEatingSpeed(self, piles, H):
+ # 初始化起点和终点, 最快的速度可以一次拿完最大的一堆
+ start = 1
+ end = max(piles)
+
+ # while loop进行二分查找
+ while start + 1 < end:
+ mid = start + (end - start) // 2
+
+ # 如果中点所需时间大于H, 我们需要加速, 将起点设为中点
+ if self.timeH(piles, mid) > H:
+ start = mid
+ # 如果中点所需时间小于H, 我们需要减速, 将终点设为中点
+ else:
+ end = mid
+
+ # 提交前确认起点是否满足条件,我们要尽量慢拿
+ if self.timeH(piles, start) <= H:
+ return start
+
+ # 若起点不符合, 则中点是答案
+ return end
+
+
+
+ def timeH(self, piles, K):
+ # 初始化时间
+ H = 0
+
+ #求拿每一堆需要多长时间
+ for pile in piles:
+ H += math.ceil(pile / K)
+
+ return H
+```
diff --git a/高频面试系列/二分查找判定子序列.md b/高频面试系列/二分查找判定子序列.md
index c71e65f..e9c5b0f 100644
--- a/高频面试系列/二分查找判定子序列.md
+++ b/高频面试系列/二分查找判定子序列.md
@@ -168,4 +168,67 @@ boolean isSubsequence(String s, String t) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+[dekunma](https://www.linkedin.com/in/dekun-ma-036a9b198/) 提供C++代码
+**解法一:遍历(也可以用双指针):**
+```C++
+class Solution {
+public:
+ bool isSubsequence(string s, string t) {
+ // 遍历s
+ for(int i = 0; i < s.size(); i++) {
+ // 找到s[i]字符在t中的位置
+ size_t pos = t.find(s[i]);
+
+ // 如果s[i]字符不在t中,返回false
+ if(pos == std::string::npos) return false;
+ // 如果s[i]在t中,后面就只看pos以后的字串,防止重复查找
+ else t = t.substr(pos + 1);
+ }
+ return true;
+ }
+};
+```
+
+**解法二:二分查找:**
+```C++
+class Solution {
+public:
+ bool isSubsequence(string s, string t) {
+ int m = s.size(), n = t.size();
+ // 对 t 进行预处理
+ vector index[256];
+ for (int i = 0; i < n; i++) {
+ char c = t[i];
+ index[c].push_back(i);
+ }
+ // 串 t 上的指针
+ int j = 0;
+ // 借助 index 查找 s[i]
+ for (int i = 0; i < m; i++) {
+ char c = s[i];
+ // 整个 t 压根儿没有字符 c
+ if (index[c].empty()) return false;
+ int pos = left_bound(index[c], j);
+ // 二分搜索区间中没有找到字符 c
+ if (pos == index[c].size()) return false;
+ // 向前移动指针 j
+ j = index[c][pos] + 1;
+ }
+ return true;
+ }
+ // 查找左侧边界的二分查找
+ int left_bound(vector arr, int tar) {
+ int lo = 0, hi = arr.size();
+ while (lo < hi) {
+ int mid = lo + (hi - lo) / 2;
+ if (tar > arr[mid]) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ return lo;
+ }
+};
+```
diff --git a/高频面试系列/判断回文链表.md b/高频面试系列/判断回文链表.md
index 3e24068..4d0225a 100644
--- a/高频面试系列/判断回文链表.md
+++ b/高频面试系列/判断回文链表.md
@@ -237,4 +237,35 @@ p.next = reverse(q);
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+C++版本:
+```cpp
+ bool isPalindrome(ListNode* head) {
+ if (head == nullptr || head->next == nullptr) //为空或者只有一个节点时,直接判断为true
+ return true;
+ ListNode* slow = head, * fast = head;
+ while (fast != nullptr) {//首先找到中间节点
+ slow = slow->next;
+ fast = fast->next == nullptr? fast->next:fast->next->next; //因为链表长度可能是奇数或偶数,所以需要进行判断
+ }
+
+ ListNode* temp = nullptr,* pre = nullptr;//pre始终保持后续链表的头部,temp节点则作为中间零时替换的节点
+ while (slow != nullptr) {//利用头插法,将当前节点与后续链表断链处理,反转后半部分的链表
+ temp = slow->next;
+ slow->next = pre;//建立连接
+ pre = slow;//pre始终作为后续链表的头部
+ slow = temp;
+ }
+
+ while (head !=nullptr && pre != nullptr) {//同步进行比较
+ if (head->val != pre->val) {//值有不一样的,说明不是回文联表,直接返回false了
+ return false;
+ }
+ head = head->next;//head向下走,直到走到空
+ pre = pre->next;//pre节点也向下走,直到走到空
+ }
+ return true;//到此说明当前链表是回文链表返回true即可
+ }
+
+```
diff --git a/高频面试系列/合法括号判定.md b/高频面试系列/合法括号判定.md
index 32239bd..6516aa1 100644
--- a/高频面试系列/合法括号判定.md
+++ b/高频面试系列/合法括号判定.md
@@ -112,4 +112,51 @@ char leftOf(char c) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+### Python3
+```python
+def isValid(self, s: str) -> bool:
+ left = []
+ leftOf = {
+ ')':'(',
+ ']':'[',
+ '}':'{'
+ }
+ for c in s:
+ if c in '([{':
+ left.append(c)
+ elif left and leftOf[c]==left[-1]: # 右括号 + left不为空 + 和最近左括号能匹配
+ left.pop()
+ else: # 右括号 + (left为空 / 和堆顶括号不匹配)
+ return False
+
+ # left中所有左括号都被匹配则return True 反之False
+ return not left
+```
+
+
+```java
+//基本思想:每次遇到左括号时都将相对应的右括号')',']'或'}'推入堆栈
+//如果在字符串中出现右括号,则需要检查堆栈是否为空,以及顶部元素是否与该右括号相同。如果不是,则该字符串无效。
+//最后,我们还需要检查堆栈是否为空
+public boolean isValid(String s) {
+ Deque stack = new ArrayDeque<>();
+ for(char c : s.toCharArray()){
+ //是左括号就将相对应的右括号入栈
+ if(c=='(') {
+ stack.offerLast(')');
+ }else if(c=='{'){
+ stack.offerLast('}');
+ }else if(c=='['){
+ stack.offerLast(']');
+ }else if(stack.isEmpty() || stack.pollLast()!=c){//出现右括号,检查堆栈是否为空,以及顶部元素是否与该右括号相同
+ return false;
+ }
+ }
+ return stack.isEmpty();
+}
+
+```
+
+
diff --git a/高频面试系列/打印素数.md b/高频面试系列/打印素数.md
index 2ab9e10..3a58074 100644
--- a/高频面试系列/打印素数.md
+++ b/高频面试系列/打印素数.md
@@ -178,4 +178,41 @@ int countPrimes(int n) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+C++解法:
+采用的算法是埃拉托斯特尼筛法
+埃拉托斯特尼筛法的具体内容就是:**要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。**
+同时考虑到大于2的偶数都不是素数,所以可以进一步优化成:**要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的奇数倍剔除,剩下的奇数就是素数。**
+此算法其实就是上面的Java解法所采用的。
+
+这里提供C++的代码:
+```C++
+class Solution {
+public:
+ int countPrimes(int n) {
+ int res = 0;
+ bool prime[n+1];
+ for(int i = 0; i < n; ++i)
+ prime[i] = true;
+
+ for(int i = 2; i <= sqrt(n); ++i) //计数过程
+ { //外循环优化,因为判断一个数是否为质数只需要整除到sqrt(n),反推亦然
+ if(prime[i])
+ {
+ for(int j = i * i; j < n; j += i) //内循环优化,i*i之前的比如i*2,i*3等,在之前的循环中已经验证了
+ {
+ prime[j] = false;
+ }
+ }
+ }
+ for (int i = 2; i < n; ++i)
+ if (prime[i]) res++; //最后遍历统计一遍,存入res
+
+ return res;
+ }
+};
+```
+
+
+
diff --git a/高频面试系列/接雨水.md b/高频面试系列/接雨水.md
index e1fd625..a500aac 100644
--- a/高频面试系列/接雨水.md
+++ b/高频面试系列/接雨水.md
@@ -211,4 +211,99 @@ if (l_max < r_max) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[Yifan Zhang](https://github.com/FanFan0919) 提供 java 代码
+
+**双指针解法**:时间复杂度 O(N),空间复杂度 O(1)
+
+对cpp版本的解法有非常微小的优化。
+因为我们每次循环只会选 left 或者 right 处的柱子来计算,因此我们并不需要在每次循环中同时更新`maxLeft`和`maxRight`。
+我们可以先比较 `maxLeft` 和 `maxRight`,决定这次选择计算的柱子是 `height[left]` 或者 `height[right]` 后再更新对应的 `maxLeft` 或 `maxRight`。
+当然这并不会在时间上带来什么优化,只是提供一种思路。
+
+```java
+class Solution {
+ public int trap(int[] height) {
+ if (height == null || height.length == 0) return 0;
+ int left = 0, right = height.length - 1;
+ int maxLeft = height[left], maxRight = height[right];
+ int res = 0;
+
+ while (left < right) {
+ // 比较 maxLeft 和 maxRight,决定这次计算 left 还是 right 处的柱子
+ if (maxLeft < maxRight) {
+ left++;
+ maxLeft = Math.max(maxLeft, height[left]); // update maxLeft
+ res += maxLeft - height[left];
+ } else {
+ right--;
+ maxRight = Math.max(maxRight, height[right]); // update maxRight
+ res += maxRight - height[right];
+ }
+ }
+
+ return res;
+ }
+}
+```
+
+附上暴力解法以及备忘录解法的 java 代码
+
+**暴力解法**:时间复杂度 O(N^2),空间复杂度 O(1)
+```java
+class Solution {
+ public int trap(int[] height) {
+ if (height == null || height.length == 0) return 0;
+ int n = height.length;
+ int res = 0;
+ // 跳过最左边和最右边的柱子,从第二个柱子开始
+ for (int i = 1; i < n - 1; i++) {
+ int maxLeft = 0, maxRight = 0;
+ // 找右边最高的柱子
+ for (int j = i; j < n; j++) {
+ maxRight = Math.max(maxRight, height[j]);
+ }
+ // 找左边最高的柱子
+ for (int j = i; j >= 0; j--) {
+ maxLeft = Math.max(maxLeft, height[j]);
+ }
+ // 如果自己就是最高的话,
+ // maxLeft == maxRight == height[i]
+ res += Math.min(maxLeft, maxRight) - height[i];
+ }
+ return res;
+ }
+}
+```
+
+**备忘录解法**:时间复杂度 O(N),空间复杂度 O(N)
+```java
+class Solution {
+ public int trap(int[] height) {
+ if (height == null || height.length == 0) return 0;
+ int n = height.length;
+ int res = 0;
+ // 数组充当备忘录
+ int[] maxLeft = new int[n];
+ int[] maxRight = new int[n];
+ // 初始化 base case
+ maxLeft[0] = height[0];
+ maxRight[n - 1] = height[n - 1];
+
+ // 从左向右计算 maxLeft
+ for (int i = 1; i < n; i++) {
+ maxLeft[i] = Math.max(maxLeft[i - 1], height[i]);
+ }
+ // 从右向左计算 maxRight
+ for (int i = n - 2; i >= 0; i--) {
+ maxRight[i] = Math.max(maxRight[i + 1], height[i]);
+ }
+ // 计算答案
+ for (int i = 1; i < n; i++) {
+ res += Math.min(maxLeft[i], maxRight[i]) - height[i];
+ }
+ return res;
+ }
+}
+```
\ No newline at end of file
diff --git a/高频面试系列/消失的元素.md b/高频面试系列/消失的元素.md
index 844b5cb..355953e 100644
--- a/高频面试系列/消失的元素.md
+++ b/高频面试系列/消失的元素.md
@@ -89,6 +89,7 @@ int missingNumber(int[] nums) {
for (int x : nums)
sum += x;
return expect - sum;
+}
```
你看,这种解法应该是最简单的,但说实话,我自己也没想到这个解法,而且我去问了几个大佬,他们也没想到这个最简单的思路。相反,如果去问一个初中生,他也许很快就能想到。
@@ -132,4 +133,49 @@ public int missingNumber(int[] nums) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[happy-yuxuan](https://github.com/happy-yuxuan) 提供 三种方法的 C++ 代码:
+
+```c++
+// 方法:异或元素和索引
+int missingNumber(vector& nums) {
+ int n = nums.size();
+ int res = 0;
+ // 先和新补的索引异或一下
+ res ^= n;
+ // 和其他的元素、索引做异或
+ for (int i = 0; i < n; i++)
+ res ^= i ^ nums[i];
+ return res;
+}
+```
+
+```c++
+// 方法:等差数列求和
+int missingNumber(vector& nums) {
+ int n = nums.size();
+ // 公式:(首项 + 末项) * 项数 / 2
+ int expect = (0 + n) * (n + 1) / 2;
+ int sum = 0;
+ for (int x : nums)
+ sum += x;
+ return expect - sum;
+}
+```
+
+```c++
+// 方法:防止整型溢出
+int missingNumber(vector& nums) {
+ int n = nums.size();
+ int res = 0;
+ // 新补的索引
+ res += n - 0;
+ // 剩下索引和元素的差加起来
+ for (int i = 0; i < n; i++)
+ res += i - nums[i];
+ return res;
+}
+```
+
+
diff --git a/高频面试系列/缺失和重复的元素.md b/高频面试系列/缺失和重复的元素.md
index 06e554f..eb21f4c 100644
--- a/高频面试系列/缺失和重复的元素.md
+++ b/高频面试系列/缺失和重复的元素.md
@@ -139,4 +139,30 @@ vector findErrorNums(vector& nums) {
-======其他语言代码======
\ No newline at end of file
+======其他语言代码======
+
+[zhuli](https://github.com/1097452462 "zhuli")提供的Java代码:
+```java
+class Solution {
+ public int[] findErrorNums(int[] nums) {
+ int n = nums.length;
+ int dup = -1;
+ for (int i = 0; i < n; i++) {
+ // 元素是从 1 开始的
+ int index = Math.abs(nums[i]) - 1;
+ // nums[index] 小于 0 则说明重复访问
+ if (nums[index] < 0)
+ dup = Math.abs(nums[i]);
+ else
+ nums[index] *= -1;
+ }
+ int missing = -1;
+ for (int i = 0; i < n; i++)
+ // nums[i] 大于 0 则说明没有访问
+ if (nums[i] > 0)
+ // 将索引转换成元素
+ missing = i + 1;
+ return new int[]{dup, missing};
+ }
+}
+```