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 的算法小抄

-Website +Website GitHub

@@ -14,6 +14,10 @@ English version repo and Gitbook is on [english branch](https://github.com/labul

+![](pictures/souyisou.png) + +好消息,《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 @@ ![](../pictures/souyisou.png) 相关推荐: - * [动态规划设计:最大子数组](../动态规划系列/最大子数组.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 @@ ![](../pictures/souyisou.png) 相关推荐: - * [回溯算法解题套路框架](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}; + } +} +```