提交 b4a38fbe 编写于 作者: L labuladong

大更新:开头添加对应的力扣题目

上级 74a3d066
......@@ -37,7 +37,7 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
Gitbook 地址:https://labuladong.gitbook.io/algo/
2、建议关注我的公众号 **labuladong**,坚持高质量原创,说是最良心最硬核的技术公众号都不为过。本仓库的文章就是从公众号里整理出来的**一部分**内容,公众号后台回复关键词【电子书】可以获得这份小抄的完整版本;回复【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
3、建议关注我的公众号 **labuladong**,坚持高质量原创,说是最良心最硬核的技术公众号都不为过。本仓库的文章就是从公众号里整理出来的**一部分**内容,公众号后台回复关键词【电子书】可以获得这份小抄的完整版本;回复【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
<p align='center'>
<img src="https://gitee.com/labuladong/pictures/raw/master/qrcode.jpg" width = "200" />
......
......@@ -8,6 +8,6 @@
这就是思维模式的框架,**本章都会按照以上的模式来解决问题,辅助读者养成这种模式思维**,有了方向遇到问题就不会抓瞎,足以解决一般的动态规划问题。
欢迎关注我的公众号 labuladong,方便获得最新的优质文章:
欢迎关注我的公众号 labuladong,查看全部文章:
![labuladong二维码](../pictures/qrcode.jpg)
\ No newline at end of file
# 动态规划之KMP字符匹配算法
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [经典动态规划:最长公共子序列](https://labuladong.gitbook.io/algo)
* [特殊数据结构:单调栈](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[28.实现 strStr()](https://leetcode-cn.com/problems/implement-strstr)
**-----------**
KMP 算法(Knuth-Morris-Pratt 算法)是一个著名的字符串匹配算法,效率很高,但是确实有点复杂。
很多读者抱怨 KMP 算法无法理解,这很正常,想到大学教材上关于 KMP 算法的讲解,也不知道有多少未来的 Knuth、Morris、Pratt 被提前劝退了。有一些优秀的同学通过手推 KMP 算法的过程来辅助理解该算法,这是一种办法,不过本文要从逻辑层面帮助读者理解算法的原理。十行代码之间,KMP 灰飞烟灭。
......@@ -37,7 +57,7 @@ int search(String pat, String txt) {
}
```
对于暴力算法,如果出现不匹配字符,同时回退 `txt``pat` 的指针,嵌套 for 循环,时间复杂度 $O(MN)$,空间复杂度$O(1)$。最主要的问题是,如果字符串中重复的字符比较多,该算法就显得很蠢。
对于暴力算法,如果出现不匹配字符,同时回退 `txt``pat` 的指针,嵌套 for 循环,时间复杂度 `O(MN)`,空间复杂度`O(1)`。最主要的问题是,如果字符串中重复的字符比较多,该算法就显得很蠢。
比如 txt = "aaacaaab" pat = "aaab":
......@@ -399,12 +419,14 @@ public class KMP {
KMP 算法也就是动态规划那点事,我们的公众号文章目录有一系列专门讲动态规划的,而且都是按照一套框架来的,无非就是描述问题逻辑,明确 `dp` 数组含义,定义 base case 这点破事。希望这篇文章能让大家对动态规划有更深的理解。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:贪心算法之区间调度问题](../动态规划系列/贪心算法之区间调度问题.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:团灭 LeetCode 股票买卖问题](../动态规划系列/团灭股票问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 动态规划之博弈问题
上一篇文章 [几道智力题](../高频面试系列/一行代码解决的智力题.md) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [40张图解:TCP三次握手和四次挥手面试题](https://labuladong.gitbook.io/algo)
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[877.石子游戏](https://leetcode-cn.com/problems/stone-game)
**-----------**
上一篇文章 [几道智力题](https://labuladong.gitbook.io/algo) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
博弈类问题的套路都差不多,下文举例讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
......@@ -43,7 +63,7 @@ dp[0][1].fir = 9 意味着:面对石头堆 [3, 9],先手最终能够获得 9
dp[1][3].sec = 2 意味着面对石头堆 [9, 1, 2]后手最终能够获得 2
```
我们想求的答案是先手和后手最终分数之差,按照这个定义也就是 $dp[0][n-1].fir - dp[0][n-1].sec$,即面对整个 piles,先手的最优得分和后手的最优得分之差。
我们想求的答案是先手和后手最终分数之差,按照这个定义也就是 `dp[0][n-1].fir - dp[0][n-1].sec`,即面对整个 piles,先手的最优得分和后手的最优得分之差。
### 二、状态转移方程
......@@ -183,41 +203,14 @@ int stoneGame(int[] piles) {
希望本文对你有帮助。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[Hanmin](https://github.com/Miraclemin/) 提供 Python3 代码:
```python
def stoneGame(self, piles:List[int]) -> int:
n = len(piles)
##初始化dp数组,用三维数组表示
dp = [[[0,0] for _ in range(0,n)] for _ in range(n)]
##填入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]
```
**_____________**
[上一篇:动态规划之子序列问题解题模板](../动态规划系列/子序列问题模板.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:贪心算法之区间调度问题](../动态规划系列/贪心算法之区间调度问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 动态规划之四键键盘
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何高效寻找素数](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[651.四键键盘](https://leetcode-cn.com/problems/4-keys-keyboard)
**-----------**
PS:现在这到题好想变成会员题目了?我当时做的时候还是免费的。
四键键盘问题很有意思,而且可以明显感受到:对 dp 数组的不同定义需要完全不同的逻辑,从而产生完全不同的解法。
首先看一下题目:
......@@ -168,13 +190,12 @@ def dp(n, a_num, copy):
根据这个事实,我们重新定义了状态,重新寻找了状态转移,从逻辑上减少了无效的子问题个数,从而提高了算法的效率。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:团灭 LeetCode 打家劫舍问题](../动态规划系列/抢房子.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划之正则表达](../动态规划系列/动态规划之正则表达.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 动态规划设计:最长递增子序列
很多读者反应,就算看了前文[动态规划详解](动态规划详解进阶.md),了解了动态规划的套路,也不会写状态转移方程,没有思路,怎么办?本文就借助「最长递增子序列」来讲一种设计动态规划的通用技巧:数学归纳思想。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
最长递增子序列(Longest Increasing Subsequence,简写 LIS)是比较经典的一个问题,比较容易想到的是动态规划解法,时间复杂度 O(N^2),我们借这个问题来由浅入深讲解如何写动态规划。比较难想到的是利用二分查找,时间复杂度是 O(NlogN),我们通过一种简单的纸牌游戏来辅助理解这种巧妙的解法。
![](../pictures/souyisou.png)
相关推荐:
* [动态规划设计:最大子数组](../动态规划系列/最大子数组.md)
* [一文学会递归解题](../投稿/一文学会递归解题.md)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[300.最长上升子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence)
**-----------**
也许有读者看了前文 [动态规划详解](../动态规划系列/动态规划详解进阶.md),学会了动态规划的套路:找到了问题的「状态」,明确了 `dp` 数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
不要担心,动态规划的难点本来就在于寻找正确的状态转移方程,本文就借助经典的「最长递增子序列问题」来讲一讲设计动态规划的通用技巧:**数学归纳思想**
最长递增子序列(Longest Increasing Subsequence,简写 LIS)是非常经典的一个算法问题,比较容易想到的是动态规划解法,时间复杂度 O(N^2),我们借这个问题来由浅入深讲解如何找状态转移方程,如何写出动态规划解法。比较难想到的是利用二分查找,时间复杂度是 O(NlogN),我们通过一种简单的纸牌游戏来辅助理解这种巧妙的解法。
先看一下题目,很容易理解:
![title](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/title.png)
注意「子序列」和「子串」这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。下面先来一步一步设计动态规划算法解决这个问题。
注意「子序列」和「子串」这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。下面先来设计动态规划算法解决这个问题。
### 一、动态规划解法
动态规划的核心设计思想是数学归纳法。
相信大家对数学归纳法都不陌生,高中就学过,而且思路很简单。比如我们想证明一个数学结论,那么我们先假设这个结论在 $k<n$ 时成立,然后想办法证明 $k=n$ 的时候此结论也成立。如果能够证明出来,那么就说明这个结论对于 k 等于任何数都成立。
相信大家对数学归纳法都不陌生,高中就学过,而且思路很简单。比如我们想证明一个数学结论,那么**我们先假设这个结论在 `k<n` 时成立,然后根据这个假设,想办法推导证明出 `k=n` 的时候此结论也成立**。如果能够证明出来,那么就说明这个结论对于 `k` 等于任何数都成立。
类似的,我们设计动态规划算法,不是需要一个 dp 数组吗?我们可以假设 `dp[0...i-1]` 都已经被算出来了,然后问自己:怎么通过这些结果算出 `dp[i]`
直接拿最长递增子序列这个问题举例你就明白了。不过,首先要定义清楚 dp 数组的含义,即 `dp[i]` 的值到底代表着什么?
类似的,我们设计动态规划算法,不是需要一个 dp 数组吗?我们可以假设 $dp[0...i-1]$ 都已经被算出来了,然后问自己:怎么通过这些结果算出 dp[i]?
**我们的定义是这样的:`dp[i]` 表示以 `nums[i]` 这个数结尾的最长递增子序列的长度。**
直接拿最长递增子序列这个问题举例你就明白了。不过,首先要定义清楚 dp 数组的含义,即 dp[i] 的值到底代表着什么?
PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文[动态规划之子序列问题解题模板](../动态规划系列/子序列问题模板.md) 总结了几种常见套路。你读完本章所有的动态规划问题,就会发现 `dp` 数组的定义方法也就那几种。
**我们的定义是这样的:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。**
根据这个定义,我们就可以推出 base case:`dp[i]` 初始值为 1,因为以 `nums[i]` 结尾的最长递增子序列起码要包含它自己。
举两个例子:
......@@ -43,19 +68,19 @@ for (int i = 0; i < dp.size(); i++) {
return res;
```
读者也许会问,刚才这个过程中每个 dp[i] 的结果是我们肉眼看出来的,我们应该怎么设计算法逻辑来正确计算每个 dp[i] 呢?
读者也许会问,刚才的算法演进过程中每个 `dp[i]` 的结果是我们肉眼看出来的,我们应该怎么设计算法逻辑来正确计算每个 `dp[i]` 呢?
这就是动态规划的重头戏了,要思考如何进行状态转移,这里就可以使用数学归纳的思想:
这就是动态规划的重头戏了,要思考如何设计算法逻辑进行状态转移,才能正确运行呢?这里就可以使用数学归纳的思想:
我们已经知道了 $dp[0...4]$ 的所有结果,我们如何通过这些已知结果推出 $dp[5]$ 呢
**假设我们已经知道了 `dp[0..4]` 的所有结果,我们如何通过这些已知结果推出 `dp[5]` 呢**
![3](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/3.jpeg)
根据刚才我们对 dp 数组的定义,现在想求 dp[5] 的值,也就是想求以 nums[5] 为结尾的最长递增子序列。
根据刚才我们对 `dp` 数组的定义,现在想求 `dp[5]` 的值,也就是想求以 `nums[5]` 为结尾的最长递增子序列。
nums[5] = 3,既然是递增子序列,我们只要找到前面那些结尾比 3 小的子序列,然后把 3 接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一
**`nums[5] = 3`,既然是递增子序列,我们只要找到前面那些结尾比 3 小的子序列,然后把 3 接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一**
当然,可能形成很多种新的子序列,但是我们只要最长的,把最长子序列的长度作为 dp[5] 的值即可。
显然,可能形成很多种新的子序列,但是我们只选择最长的那一个,把最长子序列的长度作为 `dp[5]` 的值即可。
![gif2](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/gif2.gif)
......@@ -66,9 +91,9 @@ for (int j = 0; j < i; j++) {
}
```
这段代码的逻辑就可以算出 dp[5]。到这里,这道算法题我们就基本做完了。读者也许会问,我们刚才只是算了 dp[5] 呀,dp[4], dp[3] 这些怎么算呢?
`i = 5` 时,这段代码的逻辑就可以算出 `dp[5]`。其实到这里,这道算法题我们就基本做完了。
类似数学归纳法,你已经可以算出 dp[5] 了,其他的就都可以算出来:
读者也许会问,我们刚才只是算了 `dp[5]` 呀,`dp[4]`, `dp[3]` 这些怎么算呢?类似数学归纳法,你已经可以算出 `dp[5]` 了,其他的就都可以算出来:
```java
for (int i = 0; i < nums.length; i++) {
......@@ -79,12 +104,12 @@ for (int i = 0; i < nums.length; i++) {
}
```
还有一个细节问题,dp 数组应该全部初始化为 1,因为子序列最少也要包含自己,所以长度最小为 1。下面我们看一下完整代码:
结合我们刚才说的 base case,下面我们看一下完整代码:
```java
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
// dp 数组全都初始化为 1
// base case:dp 数组全都初始化为 1
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
......@@ -101,33 +126,31 @@ public int lengthOfLIS(int[] nums) {
}
```
至此,这道题就解决了,时间复杂度 O(N^2)。总结一下动态规划的设计流程
至此,这道题就解决了,时间复杂度 O(N^2)。总结一下如何找到动态规划的状态转移关系
首先明确 dp 数组所存数据的含义。这步很重要,如果不得当或者不够清晰,会阻碍之后的步骤。
1、明确 `dp` 数组所存数据的含义。这一步对于任何动态规划问题都很重要,如果不得当或者不够清晰,会阻碍之后的步骤。
然后根据 dp 数组的定义,运用数学归纳法的思想,假设 $dp[0...i-1]$ 都已知,想办法求出 $dp[i]$,一旦这一步完成,整个题目基本就解决了。
2、根据 `dp` 数组的定义,运用数学归纳法的思想,假设 `dp[0...i-1]` 都已知,想办法求出 `dp[i]`,一旦这一步完成,整个题目基本就解决了。
但如果无法完成这一步,很可能就是 dp 数组的定义不够恰当,需要重新定义 dp 数组的含义;或者可能是 dp 数组存储的信息还不够,不足以推出下一步的答案,需要把 dp 数组扩大成二维数组甚至三维数组。
最后想一想问题的 base case 是什么,以此来初始化 dp 数组,以保证算法正确运行。
但如果无法完成这一步,很可能就是 `dp` 数组的定义不够恰当,需要重新定义 `dp` 数组的含义;或者可能是 `dp` 数组存储的信息还不够,不足以推出下一步的答案,需要把 `dp` 数组扩大成二维数组甚至三维数组。
### 二、二分查找解法
这个解法的时间复杂度会将为 O(NlogN),但是说实话,正常人基本想不到这种解法(也许玩过某些纸牌游戏的人可以想出来)。所以如果大家了解一下就好,正常情况下能够给出动态规划解法就已经很不错了。
这个解法的时间复杂度为 O(NlogN),但是说实话,正常人基本想不到这种解法(也许玩过某些纸牌游戏的人可以想出来)。所以大家了解一下就好,正常情况下能够给出动态规划解法就已经很不错了。
根据题目的意思,我都很难想象这个问题竟然能和二分查找扯上关系。其实最长递增子序列和一种叫做 patience game 的纸牌游戏有关,甚至有一种排序方法就叫做 patience sorting(耐心排序)。
为了简单起见,后文跳过所有数学证明,通过一个简化的例子来理解一下思路。
为了简单起见,后文跳过所有数学证明,通过一个简化的例子来理解一下算法思路。
首先,给你一排扑克牌,我们像遍历数组那样从左到右一张一张处理这些扑克牌,最终要把这些牌分成若干堆。
![poker1](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/poker1.jpeg)
处理这些扑克牌要遵循以下规则
**处理这些扑克牌要遵循以下规则**
只能把点数小的牌压到点数比它大的牌上。如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去。如果当前牌有多个堆可供选择,则选择最左边的堆放置。
只能把点数小的牌压到点数比它大的牌上;如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去;如果当前牌有多个堆可供选择,则选择最左边的那一堆放置。
比如说上述的扑克牌最终会被分成这样 5 堆(我们认为 A 的值是最大的,而不是 1)。
比如说上述的扑克牌最终会被分成这样 5 堆(我们认为纸牌 A 的牌面是最大的,纸牌 2 的牌面是最小的)。
![poker2](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/poker2.jpeg)
......@@ -139,9 +162,9 @@ public int lengthOfLIS(int[] nums) {
![LIS](../pictures/%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97/poker4.jpeg)
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是有序吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是**有序**吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
PS:旧文[二分查找算法详解](../算法思维系列/二分查找详解.md)详细介绍了二分查找的细节及变体,这里就完美应用上了如果没读过强烈建议阅读。
PS:旧文[二分查找算法详解](../算法思维系列/二分查找详解.md)详细介绍了二分查找的细节及变体,这里就完美应用上了如果没读过强烈建议阅读。
```java
public int lengthOfLIS(int[] nums) {
......@@ -182,61 +205,12 @@ public int lengthOfLIS(int[] nums) {
所以,这个方法作为思维拓展好了。但动态规划的设计方法应该完全理解:假设之前的答案已知,利用数学归纳的思想正确进行状态的推演转移,最终得到答案。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[Hanmin](https://github.com/Miraclemin/) 提供 Python3 代码:
**动态规划解法**
``` python
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
## dp 数组全部初始化为1
dp = [1 for x in range(0,n)]
for i in range(0,n):
for j in range(0,i):
if nums[i] > nums[j]:
dp[i] = max(dp[i],dp[j]+1)
res = 0
for temp in dp:
res = max(temp,res)
return res
```
**二分查找解法**
```python
def lengthOfLIS(self, nums: List[int]) -> int:
top = []
##牌堆初始化为0
piles = 0
for num in nums:
## num为要处理的扑克牌
##二分查找
left, right = 0, piles
while left < right:
mid = (left + right ) // 2
##搜索左侧边界
if top[mid] > num:
right = mid
##搜索右侧边界
elif top[mid] < num:
left = mid + 1
else:
right = mid
if left == piles:
##没有找到合适的堆,新建一堆
piles += 1
##把这张牌放到牌堆顶
top[left] = num
return piles
##牌堆数就是LIS的长度
```
**_____________**
[上一篇:动态规划答疑篇](../动态规划系列/最优子结构.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:编辑距离](../动态规划系列/编辑距离.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 团灭 LeetCode 股票买卖问题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [动态规划之KMP字符匹配算法](https://labuladong.gitbook.io/algo)
* [如何判断回文链表](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/)
[买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
[买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
[买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
[最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
[买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
**-----------**
很多读者抱怨 LeetCode 的股票系列问题奇技淫巧太多,如果面试真的遇到这类问题,基本不会想到那些巧妙的办法,怎么办?**所以本文拒绝奇技淫巧,而是稳扎稳打,只用一种通用方法解决所用问题,以不变应万变**
这篇文章用状态机的技巧来解决,可以全部提交通过。不要觉得这个名词高大上,文学词汇而已,实际上就是 DP table,看一眼就明白了。
PS:本文参考自[英文版 LeetCode 的一篇题解](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/discuss/39038)
先随便抽出一道题,看看别人的解法:
```cpp
......@@ -388,243 +416,14 @@ int maxProfit_k_any(int max_k, int[] prices) {
具体到股票买卖问题,我们发现了三个状态,使用了一个三维数组,无非还是穷举 + 更新,不过我们可以说的高大上一点,这叫「三维 DP」,怕不怕?这个大实话一说,立刻显得你高人一等,名利双收有没有,所以给个在看/分享吧,鼓励一下我。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[Hanmin](https://github.com/Miraclemin/) 提供 Python3 代码:
**第一题,k = 1**
```python
def maxProfit(self, prices: List[int]) -> int:
dp_i_0,dp_i_1 = 0,float('-inf')
for price in prices:
dp_i_0 = max(dp_i_0, dp_i_1 + price)
dp_i_1 = max(dp_i_1, -price)
return dp_i_0
```
**第二题,k = +infinity**
```python
def maxProfit_k_inf(self, prices: List[int]) -> int:
dp_i_0,dp_i_1 = 0,float('-inf')
for price in prices:
temp = dp_i_0
dp_i_0 = max(dp_i_0, dp_i_1 + price)
dp_i_1 = max(dp_i_1, temp - price)
return dp_i_0
```
**第三题,k = +infinity with cooldown**
```python
def maxProfit_with_cool(self, prices: List[int]) -> int:
dp_i_0,dp_i_1 = 0,float('-inf')
dp_pre_0 = 0 ##代表 dp[i-2][0]
for price in prices:
temp = dp_i_0
dp_i_0 = max(dp_i_0, dp_i_1 + price)
dp_i_1 = max(dp_i_1, dp_pre_0 - price)
dp_pre_0 = temp
return dp_i_0
```
**第四题,k = +infinity with fee**
```python
def maxProfit_with_fee(self, prices: List[int], fee: int) -> int:
dp_i_0,dp_i_1 = 0,float('-inf')
for price in prices:
temp = dp_i_0
dp_i_0 = max(dp_i_0, dp_i_1 + price)
dp_i_1 = max(dp_i_1, temp - price -fee)
return dp_i_0
```
**第五题,k = 2**
```python
def maxProfit_k_2(self, prices: List[int]) -> int:
dp_i10,dp_i11 = 0,float('-inf')
dp_i20,dp_i21 = 0,float('-inf')
for price in prices:
dp_i20 = max(dp_i20, dp_i21 + price)
dp_i21 = max(dp_i21, dp_i10 - price)
dp_i10 = max(dp_i10, dp_i11 + price)
dp_i11 = max(dp_i11, -price)
return dp_i20
```
**第六题,k = any integer**
```python
def maxProfit_k_any(self, max_k: int, prices: List[int]) -> int:
n = len(prices)
if max_k > n // 2:
return self.maxProfit_k_inf(prices)
else:
dp = [[[None, None] for _ in range(max_k + 1)] for _ in range(n)]
for i in range(0,n):
for k in range(max_k,0,-1):
if i-1 == -1:## 处理 base case
dp[i][k][0] = 0
## 解释:
## dp[i][k][0] = max(dp[-1][k][0], dp[-1][k][1] + prices[i])
## = max(0, -infinity + prices[i]) = 0
dp[i][k][1] = -prices[i]
## 解释:
## dp[i][1] = max(dp[-1][k][1], dp[-1][k][0] - prices[i])
## = max(-infinity, 0 - prices[i]) = -prices[i]
continue
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
return dp[n - 1][max_k][0];
```
[z2z23n0](https://github.com/YuzeZhang/) 提供 C++ 代码:
**第一题,k = 1**
```c++
int maxProfit(vector<int>& prices) {
int n = prices.size();
// base case
int dp_i_0 = 0, dp_i_1 = INT_MIN;
// dp_i_0: 第i天的最大利润, 0表示不持有股票
// dp_i_1: 第i天的最大利润, 1表示持有股票
for (int i = 1; i <= n; i++) {
// 如果今天不持有股票,表示昨天也不持有股票或是今天卖掉了股票
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i - 1]);
// 如果今天持有股票,表示昨天就持有股票或是今天买了股票
dp_i_1 = max(dp_i_1, -prices[i - 1]);
}
return dp_i_0;
}
```
**第二题,k = +infinity**
```c++
int maxProfit(vector<int>& prices) {
int n = prices.size();
// base case
int dp_i_0 = 0, dp_i_1 = INT_MIN;
// dp_i_0: 第i天的最大利润, 0表示不持有股票
// dp_i_1: 第i天的最大利润, 1表示持有股票
for (int i = 1; i <= n; i++) {
int temp = dp_i_0;
// 如果今天不持有股票,表示昨天也不持有股票或是今天卖掉了股票
dp_i_0 = std::max(dp_i_0, dp_i_1 + prices[i - 1]);
// 如果今天持有股票,表示昨天就持有股票或是今天买了股票
dp_i_1 = std::max(dp_i_1, temp - prices[i - 1]);
}
return dp_i_0;
}
```
**第三题,k = +infinity with cooldown**
```c++
int maxProfit(vector<int>& prices) {
int n = prices.size();
// base case
// dp_i_0: 第i天的最大利润, 0表示不持有股票
// dp_i_1: 第i天的最大利润, 1表示持有股票
int dp_i_0 = 0, dp_i_1 = INT_MIN;
// 表示第(i-2)天的最大利润,并且未持有股票
int prev_dp_i_0 = 0;
for (int i = 1; i <= n; i++) {
// temp和prev_dp_i_0用来记录第(i-2)天(前天)的最大利润
int temp = dp_i_0;
// 如果今天不持有股票,表示昨天也不持有股票或是今天卖掉了股票
dp_i_0 = std::max(dp_i_0, dp_i_1 + prices[i - 1]);
// 如果今天持有股票,表示昨天就持有股票或是今天买了股票
dp_i_1 = std::max(dp_i_1, prev_dp_i_0 - prices[i - 1]);
prev_dp_i_0 = temp;
}
return dp_i_0;
}
```
**第四题,k = +infinity with fee**
```c++
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
// base case
int dp_i_0 = 0, dp_i_1 = INT_MIN;
// dp_i_0: 第i天的最大利润, 0表示不持有股票
// dp_i_1: 第i天的最大利润, 1表示持有股票
for (int i = 1; i <= n; i++) {
int temp = dp_i_0;
// 如果今天不持有股票,表示昨天也不持有股票或是今天卖掉了股票
dp_i_0 = std::max(dp_i_0, dp_i_1 + prices[i - 1]);
// 如果今天持有股票,表示昨天就持有股票或是今天买了股票
//tips:为什么不在卖股票的时候减掉transaction fee?因为在base case中, dp_i_1 == INT_MIN, INT_MIN + prices[i] - fee 可能会造成整型溢出
dp_i_1 = std::max(dp_i_1, temp - prices[i - 1] - fee);
}
return dp_i_0;
}
```
**第五题,k = 2**
```c++
int maxProfit(vector<int>& prices) {
int size = prices.size();
int max_k = 2;
// i从1开始而不是0,这样可以使base case是 dp[0][0][0] 和 dp[0][0][1] 而不是 dp[-1][0][0] 或者 dp[-1][0]
int dp[size + 1][max_k + 1][2];
// 初始化
for (int k = max_k; k >= 0; k--) {
dp[0][k][0] = 0;
dp[0][k][1] = INT_MIN;
}
for (int i = 1; i <= size; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = INT_MIN;
for (int k = max_k; k >= 1; k--) {
dp[i][k][0] = std::max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i - 1]);
dp[i][k][1] = std::max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i - 1]);
}
}
return dp[size][max_k][0];
}
```
**第六题,k = any integer**
```c++
int maxProfit(int k, vector<int> &prices) {
int size = prices.size();
if (k > size / 2) {
return maxProfitLimitless(prices);
}
// i从1开始而不是0,这样可以使base case是 dp[0][0][0] 和 dp[0][0][1] 而不是 dp[-1][0][0] 或者 dp[-1][0][1]
int dp[size + 1][k + 1][2];
// 初始化
for (int j = k; j >= 0; j--) {
dp[0][j][0] = 0;
dp[0][j][1] = INT_MIN;
}
for (int i = 1; i <= size; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = INT_MIN;
for (int j = k; j >= 1; j--) {
dp[i][j][0] = std::max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);
dp[i][j][1] = std::max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);
}
}
return dp[size][k][0];
}
```
**_____________**
[上一篇:动态规划之KMP字符匹配算法](../动态规划系列/动态规划之KMP字符匹配算法.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:团灭 LeetCode 打家劫舍问题](../动态规划系列/抢房子.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 动态规划之子序列问题解题模板
**学好算法全靠套路,认准 labuladong 就够了**
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [洗牌算法](https://labuladong.gitbook.io/algo)
* [twoSum问题的核心思想](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[516.最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence)
**-----------**
子序列问题是常见的算法问题,而且并不好解决。
首先,子序列问题本身就相对子串、子数组更困难一些,因为前者是不连续的序列,而后两者是连续的,就算穷举你都不一定会,更别说求解相关的算法问题了。
......@@ -37,7 +58,7 @@ for (int i = 1; i < n; i++) {
```java
int n = arr.length;
int[][] dp = new int[n][n];
int[][] dp = new dp[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
......@@ -67,7 +88,7 @@ for (int i = 0; i < n; i++) {
之前解决了「最长回文子串」的问题,这次提升难度,求最长回文子序列的长度:
![](../pictures/最长回文子序列/1.jpg)
![](../pictures/最长回文子序列/title.jpg)
我们说这个问题对 dp 数组的定义是:**在子串 `s[i..j]` 中,最长回文子序列的长度为 `dp[i][j]`**。一定要记住这个定义才能理解算法。
......@@ -141,12 +162,14 @@ int longestPalindromeSubseq(string s) {
至此,最长回文子序列的问题就解决了。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:经典动态规划问题:高楼扔鸡蛋(进阶)](../动态规划系列/高楼扔鸡蛋进阶.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划之博弈问题](../动态规划系列/动态规划之博弈问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,labuladong 带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 团灭 LeetCode 打家劫舍问题
**学好算法全靠套路,认准 labuladong 就够了**
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo)
* [经典动态规划:子集背包问题](/https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[198.打家劫舍](https://leetcode-cn.com/problems/house-robber)
[213.打家劫舍II](https://leetcode-cn.com/problems/house-robber-ii)
[337.打家劫舍III](https://leetcode-cn.com/problems/house-robber-iii)
**-----------**
有读者私下问我 LeetCode 「打家劫舍」系列问题(英文版叫 House Robber)怎么做,我发现这一系列题目的点赞非常之高,是比较有代表性和技巧性的动态规划题目,今天就来聊聊这道题目。
打家劫舍系列总共有三道,难度设计非常合理,层层递进。第一道是比较标准的动态规划问题,而第二道融入了环形数组的条件,第三道更绝,把动态规划的自底向上和自顶向下解法和二叉树结合起来,我认为很有启发性。如果没做过的朋友,建议学习一下。
......@@ -225,127 +250,12 @@ int[] dp(TreeNode root) {
实际上,这个解法比我们的解法运行时间要快得多,虽然算法分析层面时间复杂度是相同的。原因在于此解法没有使用额外的备忘录,减少了数据操作的复杂性,所以实际运行效率会快。
**_____________**
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[5ooo](https://github.com/5ooo) 提供 House Robber I C++ 解法代码:
```c++
class Solution {
public:
int rob(vector<int>& nums) {
int dp_i = 0; //第i间房子最多能抢的钱
int dp_i_1 = 0; //第i+1间房子最多能抢的钱
int dp_i_2 = 0; //第i+2间房子最多能抢的钱
//从最后一间房子开始,往前移动
for (int i = nums.size() - 1; i >= 0; i--) {
dp_i = max(dp_i_1, nums[i] + dp_i_2);
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i;
}
};
```
[5ooo](https://github.com/5ooo) 提供 House Robber II C++ 解法代码:
```c++
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 1)
return nums[0];
return max(robRange(nums, 0, nums.size() - 2),
robRange(nums, 1, nums.size() - 1));
}
int robRange(vector<int>& nums, int start, int end) {
int dp_i = 0; //第i间房子最多能抢的钱
int dp_i_1 = 0; //第i+1间房子最多能抢的钱
int dp_i_2 = 0; //第i+2间房子最多能抢的钱
for (int i = end; i >= start; i--) {
dp_i = max(dp_i_1, nums[i] + dp_i_2);
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i;
}
};
```
[5ooo](https://github.com/5ooo) 提供 House Robber III C++ 解法代码:
```c++
class Solution {
public:
int rob(TreeNode* root) {
if (root == nullptr)
return 0;
// 利用备忘录消除重叠子问题
if (memo.find(root) != memo.end())
return memo[root];
// 抢,然后去下下家
int do_it = root->val +
(root->left == nullptr ?
0 : rob(root->left->left) + rob(root->left->right)) +
(root->right == nullptr ?
0 : rob(root->right->left) + rob(root->right->right));
// 不抢,然后去下家
int not_do = rob(root->left) + rob(root->right);
int ret = max(do_it, not_do);
memo[root] = ret;
return ret;
}
private:
unordered_map<TreeNode *, int > memo;
};
```
```c++
class Solution {
public:
int rob(TreeNode* root) {
//ret[0]表示不抢root,获取的最大钱数
//ret[1]表示抢root,获取的最大钱数
vector<int> ret = dp(root);
return max(ret[0], ret[1]);
}
vector<int> dp(TreeNode* root) {
if (root == nullptr)
return {0, 0};
vector<int> left = dp(root->left);
vector<int> right = dp(root->right);
//抢当前的,则接下来不能抢
int rob = root->val + left[0] + right[0];
//不抢当前的,接下来可抢可不抢,取收益大的
int not_rob = max(left[0], left[1]) + max(right[0], right[1]);
return {not_rob, rob};
}
};
```
[上一篇:团灭 LeetCode 股票买卖问题](../动态规划系列/团灭股票问题.md)
[下一篇:动态规划之四键键盘](../动态规划系列/动态规划之四键键盘.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[目录](../README.md#目录)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 动态规划答疑篇
**学好算法全靠套路,认准 labuladong 就够了**
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [搜索引擎背后的经典数据结构和算法](https://labuladong.gitbook.io/algo)
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo)
**-----------**
这篇文章就给你讲明白两个问题:
1、到底什么才叫「最优子结构」,和动态规划什么关系。
......@@ -18,7 +35,7 @@
再举个例子:假设你们学校有 10 个班,你已知每个班的最大分数差(最高分和最低分的差值)。那么现在我让你计算全校学生中的最大分数差,你会不会算?可以想办法算,但是肯定不能通过已知的这 10 个班的最大分数差推到出来。因为这 10 个班的最大分数差不一定就包含全校学生的最大分数差,比如全校的最大分数差可能是 3 班的最高分和 6 班的最低分之差。
这次我给你提出的问题就**不符合最优子结构**,因为你没办通过每个班的最优值推出全校的最优值,没办法通过子问题的最优值推出规模更大的问题的最优值。前文「动态规划详解」说过,想满足最优子结,子问题之间必须互相独立。全校的最大分数差可能出现在两个班之间,显然子问题不独立,所以这个问题本身不符合最优子结构。
这次我给你提出的问题就**不符合最优子结构**,因为你没办通过每个班的最优值推出全校的最优值,没办法通过子问题的最优值推出规模更大的问题的最优值。前文「动态规划详解」说过,想满足最优子结,子问题之间必须互相独立。全校的最大分数差可能出现在两个班之间,显然子问题不独立,所以这个问题本身不符合最优子结构。
**那么遇到这种最优子结构失效情况,怎么办?策略是:改造问题**。对于最大分数差这个问题,我们不是没办法利用已知的每个班的分数差吗,那我只能这样写一段暴力代码:
......@@ -37,7 +54,7 @@ return result;
当然,上面这个例子太简单了,不过请读者回顾一下,我们做动态规划问题,是不是一直在求各种最值,本质跟我们举的例子没啥区别,无非需要处理一下重叠子问题。
前文「不同定义不同解法」和「高楼扔鸡蛋进阶」就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
前文[同定义不同解法](../动态规划系列/动态规划之四键键盘.md)[高楼扔鸡蛋进阶](../动态规划系列/高楼扔鸡蛋问题.md) 就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
再举个常见但也十分简单的例子,求一棵二叉树的最大值,不难吧(简单起见,假设节点中的值都是非负数):
......@@ -100,7 +117,7 @@ for (int l = 2; l <= n; l++) {
**2、遍历的终点必须是存储结果的那个位置**
下面来具体解释上面两个原则是什么意思。
下面来距离解释上面两个原则是什么意思。
比如编辑距离这个经典的问题,详解见前文「编辑距离详解」,我们通过对 `dp` 数组的定义,确定了 base case 是 `dp[..][0]``dp[0][..]`,最终答案是 `dp[m][n]`;而且我们通过状态转移方程知道 `dp[i][j]` 需要从 `dp[i-1][j]`, `dp[i][j-1]`, `dp[i-1][j-1]` 转移而来,如下图:
......@@ -129,12 +146,13 @@ for (int i = 1; i < m; i++)
现在,你应该理解了这两个原则,主要就是看 base case 和最终结果的存储位置,保证遍历过程中使用的数据都是计算完毕的就行,有时候确实存在多种方法可以得到正确答案,可根据个人口味自行选择。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
**_____________**
[上一篇:动态规划解题框架](../动态规划系列/动态规划详解进阶.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:回溯算法解题框架](../算法思维系列/回溯算法详解修订版.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 最长公共子序列
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[1143.最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence)
**-----------**
最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经典的面试题目,因为它的解法是典型的二维动态规划,大部分比较困难的字符串问题都和这个问题一个套路,比如说编辑距离。而且,这个算法稍加改造就可以用于解决其他问题,所以说 LCS 算法是值得掌握的。
题目就是让我们求两个字符串的 LCS 长度:
......@@ -117,83 +137,12 @@ else:
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[labuladong](https://github.com/labuladong) 提供Python解法代码:
```python
def longestCommonSubsequence(str1, str2) -> int:
m, n = len(str1), len(str2)
# 构建 DP table 和 base case
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 进行状态转移
for i in range(1, m + 1):
for j in range(1, n + 1):
if str1[i - 1] == str2[j - 1]:
# 找到一个 lcs 中的字符
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
```
[Jinglun Zhou](https://github.com/Jasper-Joe) 提供C++解法代码:
```CPP
class Solution {
public:
int longestCommonSubsequence(string str1, string str2) {
int m=str1.size(), n=str2.size();
// 构建DP table 和 base case
vector<vector<int>> dp(m+1,vector<int>(n+1));
// dp[i][j]表示: 字符串str1[0:i]和字符串str2[0:j]的最大公共子序列
// 进行状态转移
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if(str1[i-1]==str2[j-1]) // 两个字符相等,必然可以构成子问题的最优解
// 找到一个lcs中的字符
dp[i][j]=1+dp[i-1][j-1];
else //如果两个字符不相等,我们往前看一个字符
//寄希望于str1[i-2]和str2[j-1]相等,或者str1[i-1]和str2[j-2]
//如果他们两个当中有任何一个可以匹配, 我们就有机会更新当前dp值
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
return dp[m][n]; // 根据dp的定义,答案就存储在dp[m][n]中
}
};
```
[weijiew](https://github.com/weijiew) 提供Java解法代码:
```java
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
// 构建 DP table 和 base case
// dp[i][j] 表示: 字符串 str1[0:i] 和字符串 str2[0:j] 的最大公共子序列
int[][] dp = new int[m+1][n+1];
// 进行状态转移
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
if(text1.charAt(i-1) == text2.charAt(j-1)){ // 若两个字符相等,必然可以构成子问题的最优解
// 这个字符存在于 lcs 之中
dp[i][j] = dp[i-1][j-1] + 1;
}else{
// 此时 text1[i] != text2[j] 则表示至少有一个不在 lcs 中(要么 text1[i] 不在,要么 text2[j]不在,或者都不在)。
// 所以当前结果就相当于之前结果的中最大的那一个
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
}
```
**_____________**
[上一篇:动态规划之正则表达](../动态规划系列/动态规划之正则表达.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:学习算法和刷题的思路指南](../算法思维系列/学习数据结构和算法的高效方法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 编辑距离
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [labuladong优质作者扶持计划](https://labuladong.gitbook.io/algo)
* [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[72.编辑距离](https://leetcode-cn.com/problems/edit-distance)
**-----------**
前几天看了一份鹅场的面试题,算法部分大半是动态规划,最后一题就是写一个计算编辑距离的函数,今天就专门写一篇文章来探讨一下这个问题。
我个人很喜欢编辑距离这个问题,因为它看起来十分困难,解法却出奇得简单漂亮,而且它是少有的比较实用的算法(是的,我承认很多算法问题都不太实用)。下面先来看下题目:
......@@ -23,6 +43,7 @@
设两个字符串分别为 "rad" 和 "apple",为了把 `s1` 变成 `s2`,算法会这样进行:
![](../pictures/editDistance/edit.gif)
![](../pictures/editDistance/1.jpg)
请记住这个 GIF 过程,这样就能算出编辑距离。关键在于如何做出正确的操作,稍后会讲。
......@@ -222,13 +243,13 @@ int min(int a, int b, int c) {
}
```
### 、扩展延伸
### 、扩展延伸
一般来说,处理两个字符串的动态规划问题,都是按本文的思路处理,建立 DP table。为什么呢,因为易于找出状态转移的关系,比如编辑距离的 DP table:
![](../pictures/editDistance/4.jpg)
还有一个细节,既然每个 `dp[i][j]` 只和它附近的三个状态有关,空间复杂度是可以压缩成 $O(min(M, N))$ 的(M,N 是两个字符串的长度)。不难,但是可解释性大大降低,读者可以自己尝试优化一下。
还有一个细节,既然每个 `dp[i][j]` 只和它附近的三个状态有关,空间复杂度是可以压缩成 `O(min(M, N))` 的(M,N 是两个字符串的长度)。不难,但是可解释性大大降低,读者可以自己尝试优化一下。
你可能还会问,**这里只求出了最小的编辑距离,那具体的操作是什么**?你之前举的修改公众号文章的例子,只有一个最小编辑距离肯定不够,还得知道具体怎么修改才行。
......@@ -258,103 +279,14 @@ class Node {
![](../pictures/editDistance/6.jpg)
以上就是编辑距离算法的全部内容,如果本文对你有帮助,**欢迎关注我的公众号 labuladong,致力于把算法问题讲清楚**
![labuladong](../pictures/labuladong.png)
[labuladong](https://github.com/labuladong) 提供Java解法代码:
```JAVA
int minDistance(String s1, String s2) {
int m = s1.length(), n = s2.length();
int[][] dp = new int[m + 1][n + 1];
// base case
for (int i = 1; i <= m; i++)
dp[i][0] = i;
for (int j = 1; j <= n; j++)
dp[0][j] = j;
// 自底向上求解
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (s1.charAt(i-1) == s2.charAt(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
);
// 储存着整个 s1 和 s2 的最小编辑距离
return dp[m][n];
}
int min(int a, int b, int c) {
return Math.min(a, Math.min(b, c));
}
```
[Jinglun Zhou](https://github.com/Jasper-Joe) 提供C++解法代码:
```CPP
class Solution {
public:
int minDistance(string s1, string s2) {
int m=s1.size(), n=s2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
for(int i=1;i<=m;i++)
dp[i][0]=i; // base case: 当s2为空,s1需要删除所有字符才能与s2相等
for(int j=1;j<=n;j++)
dp[0][j]=j; // base case: 当s1为空, s1需要不断插入新字符才能与s2相等
//自底向上求解
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if(s1[i-1]==s2[j-1]) // 两个字符串当前的字符一样
dp[i][j]=dp[i-1][j-1];
else // 两个字符串当前的字符不同
//使得s1[0:i]和s2[0:j]相同的最短编辑距离可通过插入,删除或替换三种操作其中一种得到
dp[i][j]=min({
dp[i-1][j]+1, // 删除s1[i]这个字符
dp[i][j-1]+1, // 在s1[i]后面加一个和s2[j]相同的字符
dp[i-1][j-1]+1}); // 将s1[i]的字符替换为s2[j]的字符
//储存着整个 s1 和 s2 的最小编辑距离
return dp[m][n];
}
};
```
[Hanmin](https://github.com/Miraclemin/) 提供 Python3 代码:
```python
def minDistance(self, word1: str, word2: str) -> int:
m, n= len(word1), len(word2)
dp = [[0 for i in range(0,n+1)] for j in range(0,m+1)]
for i in range(1,m+1):
dp[i][0] = i ##base case:当s2为空,s1需要删除所有的字符
for j in range(1,n+1):
dp[0][j] = j ##base case:当s1为空,需要插入所有s2的字符
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,
##删除s1字符操作,可以理解为我直接把 s1[i]
##这个字符删掉,前移 i,继续跟 j 对比,操作数加一
dp[i][j-1]+1,
##增加s1字符操作,可以理解为我直接在s1[i]插入一个和s2[j]一样的字符
##s2[j]被匹配,那么前移 j,继续跟 i 对比,操作数加一
dp[i-1][j-1]+1
##修改s1字符操作,可以理解为我直接替换s1[i]为s2[j]一样的字符
##s2[j]被匹配,那么前移 i,j,操作数加一
)
return dp[m][n] ##返回s1,s2最小的编辑距离
```
**_____________**
[上一篇:动态规划设计:最长递增子序列](../动态规划系列/动态规划设计:最长递增子序列.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:经典动态规划问题:高楼扔鸡蛋](../动态规划系列/高楼扔鸡蛋问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 贪心算法之区间调度问题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何判定括号合法性](https://labuladong.gitbook.io/algo)
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)
[452.用最少数量的箭引爆气球](https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons)
**-----------**
什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。
比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。
......@@ -118,66 +140,14 @@ int findMinArrowShots(int[][] intvs) {
}
```
这么做的原因也不难理解,因为现在边界接触也算重叠,所以 `start == x_end` 时不能更新 x。
如果本文对你有帮助,欢迎关注我的公众号 labuladong,致力于把算法问题讲清楚~
[renxiaoyao](https://github.com/tianzhongwei) 提供C++解法代码:435题 无重叠区间
```C++
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int n = intervals.size();
if(n <= 1) return 0;
auto myCmp = [&](const auto& a,const auto& b) {
return a[1] < b[1];
};
sort(intervals.begin(),intervals.end(),myCmp);
int cnt = 1;
int end = intervals[0][1]; // 区间动态历史最小值
for(const auto interval : intervals) {
int start = interval[0];
if(start >= end) {
cnt++;
end = interval[1];
}
}
return n - cnt;
}
};
```
[renxiaoyao](https://github.com/tianzhongwei) 提供C++解法代码:312 题 戳气球
```
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
int n = points.size();
if(n < 2) return n;
auto myCmp = [&](const auto& a,const auto& b) {
return a[1] < b[1];
};
sort(points.begin(),points.end(),myCmp);
int cnt = 1;
int end = points[0][1];
for(const auto& point : points) {
int start = point[0];
if(start > end) { // 若当前区间的起点在当前历史最右边界的后面
cnt++; // 则非重叠区间个数累加一
end = point[1]; // 更新当前历史最优边界
}
}
return cnt; // 返回非重叠区间的个数
}
};
```
**_____________**
[上一篇:动态规划之博弈问题](../动态规划系列/动态规划之博弈问题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划之KMP字符匹配算法](../动态规划系列/动态规划之KMP字符匹配算法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 经典动态规划问题:高楼扔鸡蛋(进阶)
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第二期)](https://labuladong.gitbook.io/algo)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/)
**-----------**
上篇文章聊了高楼扔鸡蛋问题,讲了一种效率不是很高,但是较为容易理解的动态规划解法。后台很多读者问如何更高效地解决这个问题,今天就谈两种思路,来优化一下这个问题,分别是二分查找优化和重新定义状态转移。
如果还不知道高楼扔鸡蛋问题的读者可以看下「经典动态规划:高楼扔鸡蛋」,那篇文章详解了题目的含义和基本的动态规划解题思路,请确保理解前文,因为今天的优化都是基于这个基本解法的。
......@@ -39,7 +59,9 @@ def dp(K, N):
这个 for 循环就是下面这个状态转移方程的具体代码实现:
$$ dp(K, N) = \min_{0 <= i <= N}\{\max\{dp(K - 1, i - 1), dp(K, N - i)\} + 1\}$$
<!-- $$ dp(K, N) = \min_{0 <= i <= N}\{\max\{dp(K - 1, i - 1), dp(K, N - i)\} + 1\}$$ -->
![](../pic/../pictures/扔鸡蛋/formula1.png)
如果能够理解这个状态转移方程,那么就很容易理解二分查找的优化思路。
......@@ -263,13 +285,12 @@ while (lo < hi) {
本文终,希望对你有一点启发。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:经典动态规划问题:高楼扔鸡蛋](../动态规划系列/高楼扔鸡蛋问题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划之子序列问题解题模板](../动态规划系列/子序列问题模板.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 经典动态规划问题:高楼扔鸡蛋
**学好算法全靠套路,认准 labuladong 就够了**
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何判断回文链表](https://labuladong.gitbook.io/algo)
* [SQL进阶技巧](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/)
**-----------**
今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼。国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋太浪费,改成扔杯子,扔破碗什么的。
具体的问题等会再说,但是这道题的解法技巧很多,光动态规划就好几种效率不同的思路,最后还有一种极其高效数学解法。秉承咱们号一贯的作风,拒绝奇技淫巧,拒绝过于诡异的技巧,因为这些技巧无法举一反三,学了也不划算。
......@@ -169,7 +190,9 @@ def dp(K, N):
这个 for 循环就是下面这个状态转移方程的具体代码实现:
$$ dp(K, N) = \min_{0 <= i <= N}\{\max\{dp(K - 1, i - 1), dp(K, N - i)\} + 1\}$$
<!-- $$ dp(K, N) = \min_{0 <= i <= N}\{\max\{dp(K - 1, i - 1), dp(K, N - i)\} + 1\}$$ -->
![](../pic/../pictures/扔鸡蛋/formula1.png)
首先我们根据 `dp(K, N)` 数组的定义(有 `K` 个鸡蛋面对 `N` 层楼,最少需要扔几次),**很容易知道 `K` 固定时,这个函数一定是单调递增的**,无论你策略多聪明,楼层增加测试次数一定要增加。
......@@ -220,53 +243,20 @@ def superEggDrop(self, K: int, N: int) -> int:
return dp(K, N)
```
这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](高楼扔鸡蛋进阶.md)
这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](../动态规划系列/高楼扔鸡蛋进阶.md)
我觉得吧,我们这种解法就够了:找状态,做选择,足够清晰易懂,可流程化,可举一反三。掌握这套框架学有余力的话,再去考虑那些奇技淫巧也不迟。
最后预告一下,《动态规划详解(修订版)》和《回溯算法详解(修订版)》已经动笔了,教大家用模板的力量来对抗变化无穷的算法题,敬请期待。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[renxiaoyao](https://github.com/tianzhongwei) 提供C++解法代码:
1) 状态定义一:动态规划 + 二分查找
```C++
class Solution {
public:
int superEggDrop(int K,int N) {
vector<vector<int>> dp(N + 1,vector<int>(K + 1,0));
for(int j = 1 ; j <= K ; ++j)
dp[1][j] = 1;
for(int i = 1 ; i <= N ; ++i)
dp[i][1] = i;
for(int i = 2 ; i <= N ; ++i)
for(int j = 2 ; j <= K ; ++j)
dp[i][j] = binary_Valley(i,j,dp);
return dp[N][K];
}
private:
int binary_Valley(int floors,int eggs,vector<vector<int>>& dp) {
int l = 1;
int r = floors;
while(l < r) {
int LMid = l + (r - l) / 2;
int broken = dp[LMid - 1][eggs - 1];
int not_broken = dp[floors - LMid][eggs];
if(not_broken > broken)
l = LMid + 1;
else
r = LMid;
}
return max(dp[r - 1][eggs - 1],dp[floors - r][eggs]) + 1;
}
};
```
**_____________**
[上一篇:编辑距离](../动态规划系列/编辑距离.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:经典动态规划问题:高楼扔鸡蛋(进阶)](../动态规划系列/高楼扔鸡蛋进阶.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 关于 Linux shell 你必须知道的技巧
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo)
**-----------**
我个人很喜欢使用 Linux 系统,虽然说 Windows 的图形化界面做的确实比 Linux 好,但是对脚本的支持太差了。一开始有点不习惯命令行操作,但是熟悉了之后反而发现移动鼠标点点点才是浪费时间的罪魁祸首。。。
**那么对于 Linux 命令行,本文不是介绍某些命令的用法,而是说明一些简单却特别容易让人迷惑的细节问题**
......@@ -85,10 +103,10 @@ $ logout
类似的,还有一种后台运行常用的做法是这样:
```shell
$ nohup some_cmd &
$ nohub some_cmd &
```
`nohup`命令也是类似的原理,不过通过我的测试,还是`(cmd &)`这种形式更加稳定。
`nohub`命令也是类似的原理,不过通过我的测试,还是`(cmd &)`这种形式更加稳定。
### 三、单引号和双引号的区别
......@@ -104,7 +122,7 @@ shell 的行为可以测试,使用`set -x`命令,会开启 shell 的命令
### 四、sudo 找不到命令
有时候我们普通用户可以用的命令,用`sudo`加权限之后却报错 command not found:
有时候我们普通用户可以用的命令,用 `sudo` 加权限之后却报错 command not found:
```shell
$ connect.sh
......@@ -114,28 +132,27 @@ $ sudo connect.sh
sudo: command not found
```
原因在于,`connect.sh`这个脚本仅存在于该用户的环境变量中:
原因在于,`connect.sh` 这个脚本仅存在于该用户的环境变量中:
```shell
$ where connect.sh
/home/fdl/bin/connect.sh
```
**当使用`sudo`时,系统认为是 root 用户在执行命令,所以会去搜索 root 用户的环境变量**,而这个脚本在 root 的环境变量目录中当然是找不到的。
**当使用 `sudo` 时,系统会使用 `/etc/sudoers` 这个文件中规定的该用户的权限和环境变量**,而这个脚本在 `/etc/sudoers` 环境变量目录中当然是找不到的。
解决方法是使用脚本文件的绝对路径,而不是用脚本的相对路径
解决方法是使用脚本文件的路径,而不是仅仅通过脚本名称
```shell
$ sudo /home/fdl/bin/connect.sh
```
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:一文看懂 session 和 cookie](../技术/session和cookie.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:加密算法的前身今世](../技术/密码技术.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# Linux的进程、线程、文件描述符是什么
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo)
* [Union-Find算法详解](https://labuladong.gitbook.io/algo)
**-----------**
说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案:**在 Linux 系统中,进程和线程几乎没有区别**
Linux 中的进程就是一个数据结构,看明白就可以理解文件描述符、重定向、管道命令的底层工作原理,最后我们从操作系统的角度看看为什么说线程和进程基本没有区别。
......@@ -115,13 +131,12 @@ $ cmd1 | cmd2 | cmd3
在 Linux 中新建线程和进程的效率都是很高的,对于新建进程时内存区域拷贝的问题,Linux 采用了 copy-on-write 的策略优化,也就是并不真正复制父进程的内存空间,而是等到需要写操作时才去复制。**所以 Linux 中新建进程和新建线程都是很迅速的**
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:双指针技巧解题框架](../算法思维系列/双指针技巧.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:Git/SQL/正则表达式的在线练习平台](../技术/在线练习平台.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# Redis 入侵
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [烧饼排序](https://labuladong.gitbook.io/algo)
* [动态规划之正则表达](https://labuladong.gitbook.io/algo)
**-----------**
好吧,我也做了回标题党,像我这么细心的同学,怎么可能让服务器被入侵呢?
其实是这样的,昨天我和一个朋友聊天,他说他自己有一台云服务器运行了 Redis 数据库,有一天突然发现数据库里的**数据全没了**,只剩下一个奇奇怪怪的键值对,其中值看起来像一个 RSA 公钥的字符串,他以为是误操作删库了,幸好自己的服务器里没啥重要的数据,也就没在意。
......@@ -78,6 +96,12 @@ Redis 监听的默认端口是 6379,我们设置它接收网卡 127.0.0.1 的
3、利用 rename 功能伪装 flushall 这种危险命令,以防被删库,丢失数据。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
![labuladong](../pictures/labuladong.jpg)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
\ No newline at end of file
# 一文读懂 session 和 cookie
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo)
* [Linux的进程、线程、文件描述符是什么](https://labuladong.gitbook.io/algo)
**-----------**
cookie 大家应该都熟悉,比如说登录某些网站一段时间后,就要求你重新登录;再比如有的同学很喜欢玩爬虫技术,有时候网站就是可以拦截住你的爬虫,这些都和 cookie 有关。如果你明白了服务器后端对于 cookie 和 session 的处理逻辑,就可以解释这些现象,甚至钻一些空子无限白嫖,待我慢慢道来。
### 一、session 和 cookie 简介
......@@ -127,13 +145,12 @@ https://github.com/alexedwards/scs
https://github.com/astaxie/build-web-application-with-golang
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:Linux的进程、线程、文件描述符是什么](../技术/linux进程.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:关于 Linux shell 你必须知道的](../技术/linuxshell.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
\ No newline at end of file
# 在线刷题学习平台
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [区间调度之区间合并问题](https://labuladong.gitbook.io/algo)
* [别再说你不懂Linux内存管理了,10张图给你安排的明明白白](https://labuladong.gitbook.io/algo)
**-----------**
虽说我没事就喜欢喷应试教育,但我也从应试教育中发现了一个窍门:如果能够以刷题的形式学习某项技能,效率和效果是最佳的。对于技术的学习,我经常面临的困境是,**理论知识知道的不少,但是有的场景实在无法模拟,缺少亲自动手实践的机会**,如果能有一本带标准答案的习题册让我刷刷就好了。
所以在学习新技术时,我首先会去搜索是否有在线刷题平台,你还别说,有的大神真就做了很不错的在线练习平台,下面就介绍几个平台,分别是学习 Git、SQL、正则表达式的在线练习平台。
......@@ -91,13 +109,12 @@ SQLZOO 是一款很好用的 SQL 练习平台,英文不难理解,可以直
https://sqlzoo.net/
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:Linux的进程、线程、文件描述符是什么](../技术/linux进程.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划详解](../动态规划系列/动态规划详解进阶.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
\ No newline at end of file
# 密码算法的前世今生
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo)
* [一个方法团灭 nSum 问题](https://labuladong.gitbook.io/algo)
**-----------**
说到密码,我们第一个想到的就是登陆账户的密码,但是从密码学的角度来看,这种根本就不算合格的密码。
为什么呢,因为我们的账户密码,是依靠隐蔽性来达到加密作用:密码藏在我心里,你不知道,所以你登不上我的账户。
......@@ -174,13 +192,14 @@ HTTPS 协议中的 SSL/TLS 安全层会组合使用以上几种加密方式,**
密码技术只是安全的一小部分,即便是通过正规机构认证的 HTTPS 站点,也不意味着可信任,只能说明其数据传输是安全的。技术永远不可能真正保护你,最重要的还是得提高个人的安全防范意识,多留心眼儿,谨慎处理敏感数据。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[上一篇:关于 Linux shell 你必须知道的](../技术/linuxshell.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[下一篇:Git/SQL/正则表达式的在线练习平台](../技术/在线练习平台.md)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
[目录](../README.md#目录)
\ No newline at end of file
[test ad](https://labuladong.gitbook.io/algo)
\ No newline at end of file
......@@ -2,6 +2,6 @@
这一章主要是一些特殊的数据结构设计,比如单调栈解决 Next Greater Number,单调队列解决滑动窗口问题;还有常用数据结构的操作,比如链表、树、二叉堆。
欢迎关注我的公众号 labuladong,方便获得最新的优质文章:
欢迎关注我的公众号 labuladong,查看全部文章:
![labuladong二维码](../pictures/qrcode.jpg)
\ No newline at end of file
# 二叉堆详解实现优先级队列
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [算法就像搭乐高:带你手撸 LRU 算法](https://labuladong.gitbook.io/algo)
* [拆解复杂问题:实现计算器](https://labuladong.gitbook.io/algo)
**-----------**
二叉堆(Binary Heap)没什么神秘,性质比二叉搜索树 BST 还简单。其主要操作就两个,`sink`(下沉)和 `swim`(上浮),用以维护二叉堆的性质。其主要应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。
本文就以实现优先级队列(Priority Queue)为例,通过图片和人类的语言来描述一下二叉堆怎么运作的。
......@@ -198,7 +214,7 @@ public Key delMax() {
![5](../pictures/heap/delete.gif)
至此,一个优先级队列就实现了,插入和删除元素的时间复杂度为 $O(logK)$,$K$ 为当前二叉堆(优先级队列)中的元素总数。因为我们时间复杂度主要花费在 `sink` 或者 `swim` 上,而不管上浮还是下沉,最多也就树(堆)的高度,也就是 log 级别。
至此,一个优先级队列就实现了,插入和删除元素的时间复杂度为 `O(logK)``K` 为当前二叉堆(优先级队列)中的元素总数。因为我们时间复杂度主要花费在 `sink` 或者 `swim` 上,而不管上浮还是下沉,最多也就树(堆)的高度,也就是 log 级别。
### 五、最后总结
......@@ -211,12 +227,14 @@ public Key delMax() {
也许这就是数据结构的威力,简单的操作就能实现巧妙的功能,真心佩服发明二叉堆算法的人!
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:学习数据结构和算法读什么书](../算法思维系列/为什么推荐算法4.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:LRU算法详解](../高频面试系列/LRU算法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 二叉搜索树操作集锦
通过之前的文章[框架思维](../算法思维系列/学习数据结构和算法的高效方法.md),二叉树的遍历框架应该已经印到你的脑子里了,这篇文章就来实操一下,看看框架思维是怎么灵活运用,秒杀一切二叉树问题的。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[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)
**-----------**
通过之前的文章[框架思维](https://labuladong.gitbook.io/algo),二叉树的遍历框架应该已经印到你的脑子里了,这篇文章就来实操一下,看看框架思维是怎么灵活运用,秒杀一切二叉树问题的。
二叉树算法的设计的总路线:明确一个节点要做的事情,然后剩下的事抛给框架。
......@@ -272,13 +300,12 @@ void BST(TreeNode root, int target) {
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:LRU算法详解](../高频面试系列/LRU算法.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:特殊数据结构:单调栈](../数据结构系列/单调栈.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
### 如何使用单调栈解题
# 如何使用单调栈解题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[496.下一个更大元素I](https://leetcode-cn.com/problems/next-greater-element-i)
[503.下一个更大元素II](https://leetcode-cn.com/problems/next-greater-element-ii)
[1118.一月有多少天](https://leetcode-cn.com/problems/number-of-days-in-a-month)
**-----------**
栈(stack)是很简单的一种数据结构,先进后出的逻辑顺序,符合某些问题的特点,比如说函数调用栈。
......@@ -6,80 +30,104 @@
听起来有点像堆(heap)?不是的,单调栈用途不太广泛,只处理一种典型的问题,叫做 Next Greater Element。本文用讲解单调队列的算法模版解决这类问题,并且探讨处理「循环数组」的策略。
首先,讲解 Next Greater Number 的原始问题:给你一个数组,返回一个等长的数组,对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。不好用语言解释清楚,直接上一个例子:
### 单调栈模板
首先,看一下 Next Greater Number 的原始问题,这是力扣第 496 题「下一个更大元素 I」:
给你一个数组,返回一个等长的数组,对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。
函数签名如下:
```cpp
vector<int> nextGreaterElement(vector<int>& nums);
```
给你一个数组 [2,1,2,4,3],你返回数组 [4,2,4,-1,-1]
比如说,输入一个数组 `nums = [2,1,2,4,3]`,你返回数组 `[4,2,4,-1,-1]`
解释:第一个 2 后面比 2 大的数是 4; 1 后面比 1 大的数是 2;第二个 2 后面比 2 大的数是 4; 4 后面没有比 4 大的数,填 -1;3 后面没有比 3 大的数,填 -1。
这道题的暴力解法很好想到,就是对每个元素后面都进行扫描,找到第一个更大的元素就行了。但是暴力解法的时间复杂度是 O(n^2)
这道题的暴力解法很好想到,就是对每个元素后面都进行扫描,找到第一个更大的元素就行了。但是暴力解法的时间复杂度是 `O(n^2)`
这个问题可以这样抽象思考:把数组的元素想象成并列站立的人,元素大小想象成人的身高。这些人面对你站成一列,如何求元素「2」的 Next Greater Number 呢?很简单,如果能够看到元素「2」,那么他后面可见的第一个人就是「2」的 Next Greater Number,因为比「2」小的元素身高不够,都被「2」挡住了,第一个露出来的就是答案。
![ink-image](../pictures/%E5%8D%95%E8%B0%83%E6%A0%88/1.png)
![](../pictures/%E5%8D%95%E8%B0%83%E6%A0%88/1.jpeg)
这个情景很好理解吧?带着这个抽象的情景,先来看下代码。
```cpp
vector<int> nextGreaterElement(vector<int>& nums) {
vector<int> ans(nums.size()); // 存放答案的数组
vector<int> res(nums.size()); // 存放答案的数组
stack<int> s;
for (int i = nums.size() - 1; i >= 0; i--) { // 倒着往栈里放
while (!s.empty() && s.top() <= nums[i]) { // 判定个子高矮
s.pop(); // 矮个起开,反正也被挡着了。。。
// 倒着往栈里放
for (int i = nums.size() - 1; i >= 0; i--) {
// 判定个子高矮
while (!s.empty() && s.top() <= nums[i]) {
// 矮个起开,反正也被挡着了。。。
s.pop();
}
ans[i] = s.empty() ? -1 : s.top(); // 这个元素身后的第一个高个
s.push(nums[i]); // 进队,接受之后的身高判定吧!
// nums[i] 身后的 next great number
res[i] = s.empty() ? -1 : s.top();
//
s.push(nums[i]);
}
return ans;
return res;
}
```
这就是单调队列解决问题的模板。for 循环要从后往前扫描元素,因为我们借助的是栈的结构,倒着入栈,其实是正着出栈。while 循环是把两个“高个”元素之间的元素排除,因为他们的存在没有意义,前面挡着个“更高”的元素,所以他们不可能被作为后续进来的元素的 Next Great Number 了。
这就是单调队列解决问题的模板。for 循环要从后往前扫描元素,因为我们借助的是栈的结构,倒着入栈,其实是正着出栈。while 循环是把两个「个子高」元素之间的元素排除,因为他们的存在没有意义,前面挡着个「更高」的元素,所以他们不可能被作为后续进来的元素的 Next Great Number 了。
这个算法的时间复杂度不是那么直观,如果你看到 for 循环嵌套 while 循环,可能认为这个算法的复杂度也是 `O(n^2)`,但是实际上这个算法的复杂度只有 `O(n)`
这个算法的时间复杂度不是那么直观,如果你看到 for 循环嵌套 while 循环,可能认为这个算法的复杂度也是 O(n^2),但是实际上这个算法的复杂度只有 O(n)
分析它的时间复杂度,要从整体来看:总共有 `n` 个元素,每个元素都被 `push` 入栈了一次,而最多会被 `pop` 一次,没有任何冗余操作。所以总的计算规模是和元素规模 `n` 成正比的,也就是 `O(n)` 的复杂度
分析它的时间复杂度,要从整体来看:总共有 n 个元素,每个元素都被 push 入栈了一次,而最多会被 pop 一次,没有任何冗余操作。所以总的计算规模是和元素规模 n 成正比的,也就是 O(n) 的复杂度。
### 问题变形
现在,你已经掌握了单调栈的使用技巧,来一个简单的变形来加深一下理解。
单调栈的使用技巧差不多了,来一个简单的变形,力扣第 1118 题「一月有多少天」:
给你一个数组 T = [73, 74, 75, 71, 69, 72, 76, 73],这个数组存放的是近几天的天气气温(这气温是铁板烧?不是的,这里用的华氏度)。你返回一个数组,计算:对于每一天,你还要至少等多少天才能等到一个更暖和的气温;如果等不到那一天,填 0 。
给你一个数组 `T`,这个数组存放的是近几天的天气气温,你返回一个等长的数组,计算:**对于每一天,你还要至少等多少天才能等到一个更暖和的气温;如果等不到那一天,填 0**
函数签名如下:
```cpp
vector<int> dailyTemperatures(vector<int>& T);
```
举例:给你 T = [73, 74, 75, 71, 69, 72, 76, 73],你返回 [1, 1, 4, 2, 1, 1, 0, 0]
比如说给你输入 `T = [73,74,75,71,69,76]`,你返回 `[1,1,3,2,1,0]`
解释:第一天 73 华氏度,第二天 74 华氏度,比 73 大,所以对于第一天,只要等一天就能等到一个更暖和的气温后面的同理。
解释:第一天 73 华氏度,第二天 74 华氏度,比 73 大,所以对于第一天,只要等一天就能等到一个更暖和的气温后面的同理。
你已经对 Next Greater Number 类型问题有些敏感了,这个问题本质上也是找 Next Greater Number,只不过现在不是问你 Next Greater Number 是多少,而是问你当前距离 Next Greater Number 的距离而已。
这个问题本质上也是找 Next Greater Number,只不过现在不是问你 Next Greater Number 是多少,而是问你当前距离 Next Greater Number 的距离而已。
相同类型的问题,相同的思路,直接调用单调栈的算法模板,稍作改动就可以啦,直接上代码把。
相同的思路,直接调用单调栈的算法模板,稍作改动就可以,直接上代码吧:
```cpp
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> ans(T.size());
stack<int> s; // 这里放元素索引,而不是元素
vector<int> res(T.size());
// 这里放元素索引,而不是元素
stack<int> s;
/* 单调栈模板 */
for (int i = T.size() - 1; i >= 0; i--) {
while (!s.empty() && T[s.top()] <= T[i]) {
s.pop();
}
ans[i] = s.empty() ? 0 : (s.top() - i); // 得到索引间距
s.push(i); // 加入索引,而不是元素
// 得到索引间距
res[i] = s.empty() ? 0 : (s.top() - i);
// 将索引入栈,而不是元素
s.push(i);
}
return ans;
return res;
}
```
单调栈讲解完毕,下面开始另一个重点:如何处理「循环数组」。
### 如何处理环形数组
单调栈讲解完毕。下面开始另一个重点:如何处理「循环数组」。
同样是 Next Greater Number,现在假设给你的数组是个环形的,如何处理?
给你一个数组 [2,1,2,4,3],你返回数组 [4,2,4,-1,4]。拥有了环形属性,最后一个元素 3 绕了一圈后找到了比自己大的元素 4 。
同样是 Next Greater Number,现在假设给你的数组是个环形的,如何处理?力扣第 503 题「下一个更大元素 II」就是这个问题:
![ink-image](../pictures/%E5%8D%95%E8%B0%83%E6%A0%88/2.png)
比如输入一个数组 `[2,1,2,4,3]`,你返回数组 `[4,2,4,-1,4]`。拥有了环形属性,**最后一个元素 3 绕了一圈后找到了比自己大的元素 4**
首先,计算机的内存都是线性的,没有真正意义上的环形数组,但是我们可以模拟出环形数组的效果,一般是通过 % 运算符求模(余数),获得环形特效:
一般是通过 % 运算符求模(余数),来获得环形特效:
```java
int[] arr = {1,2,3,4,5};
......@@ -90,21 +138,26 @@ while (true) {
}
```
回到 Next Greater Number 的问题,增加了环形属性后,问题的难点在于:这个 Next 的意义不仅仅是当前元素的右边了,有可能出现在当前元素的左边(如上例)。
这个问题肯定还是要用单调栈的解题模板,但难点在于,比如输入是 `[2,1,2,4,3]`,对于最后一个元素 3,如何找到元素 4 作为 Next Greater Number。
**对于这种需求,常用套路就是将数组长度翻倍**
![](../pictures/%E5%8D%95%E8%B0%83%E6%A0%88/2.jpeg)
明确问题,问题就已经解决了一半了。我们可以考虑这样的思路:将原始数组“翻倍”,就是在后面再接一个原始数组,这样的话,按照之前“比身高”的流程,每个元素不仅可以比较自己右边的元素,而且也可以和左边的元素比较了
这样,元素 3 就可以找到元素 4 作为 Next Greater Number 了,而且其他的元素都可以被正确地计算
![ink-image (2)](../pictures/%E5%8D%95%E8%B0%83%E6%A0%88/3.png)
有了思路,最简单的实现方式当然可以把这个双倍长度的数组构造出来,然后套用算法模板。但是,**我们可以不用构造新数组,而是利用循环数组的技巧来模拟数组长度翻倍的效果**
怎么实现呢?你当然可以把这个双倍长度的数组构造出来,然后套用算法模板。但是,我们可以不用构造新数组,而是利用循环数组的技巧来模拟。直接看代码吧:
直接看代码吧:
```cpp
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> res(n); // 存放结果
vector<int> res(n);
stack<int> s;
// 假装这个数组长度翻倍了
for (int i = 2 * n - 1; i >= 0; i--) {
// 索引要求模,其他的和模板一样
while (!s.empty() && s.top() <= nums[i % n])
s.pop();
res[i % n] = s.empty() ? -1 : s.top();
......@@ -114,13 +167,16 @@ vector<int> nextGreaterElements(vector<int>& nums) {
}
```
至此,你已经掌握了单调栈的设计方法及代码模板,学会了解决 Next Greater Number,并能够处理循环数组了
这样,就可以巧妙解决环形数组的问题,时间复杂度 `O(N)`
你的在看,是对我的鼓励。关注公众号:labuladong
如果本文对你有帮助,请三连,这次一定。
**_____________**
[上一篇:二叉搜索树操作集锦](../数据结构系列/二叉搜索树操作集锦.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:特殊数据结构:单调队列](../数据结构系列/单调队列.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
\ No newline at end of file
# 特殊数据结构:单调队列
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [几个反直觉的概率问题](https://labuladong.gitbook.io/algo)
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[239.滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum)
**-----------**
前文讲了一种特殊的数据结构「单调栈」monotonic stack,解决了一类问题「Next Greater Number」,本文写一个类似的数据结构「单调队列」。
也许这种数据结构的名字你没听过,其实没啥难的,就是一个「队列」,只是使用了一点巧妙的方法,使得队列中的元素单调递增(或递减)。这个数据结构有什么用?可以解决滑动窗口的一系列问题。
......@@ -180,13 +200,12 @@ vector<int> maxSlidingWindow(vector<int>& nums, int k) {
赶紧去拿下 LeetCode 第 239 道题吧~
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:特殊数据结构:单调栈](../数据结构系列/单调栈.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:设计Twitter](../数据结构系列/设计Twitter.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 拆解复杂问题:实现计算器
**学好算法全靠套路,认准 labuladong 就够了**
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[224.基本计算器](https://leetcode-cn.com/problems/basic-calculator)
[227.基本计算器II](https://leetcode-cn.com/problems/basic-calculator-ii)
[772.基本计算器III](https://leetcode-cn.com/problems/basic-calculator-iii)
**-----------**
我们最终要实现的计算器功能如下:
1、输入一个字符串,可以包含`+ - * /`、数字、括号以及空格,你的算法返回运算结
1、输入一个字符串,可以包含`+ - * /`、数字、括号以及空格,你的算法返回运算结
2、要符合运算法则,括号的优先级最高,先乘除后加减。
......@@ -270,12 +295,13 @@ def calculate(s: str) -> int:
**退而求其次是一种很聪明策略**。你想想啊,假设这是一道考试题,你不会实现这个计算器,但是你写了字符串转整数的算法并指出了容易溢出的陷阱,那起码可以得 20 分吧;如果你能够处理加减法,那可以得 40 分吧;如果你能处理加减乘除四则运算,那起码够 70 分了;再加上处理空格字符,80 有了吧。我就是不会处理括号,那就算了,80 已经很 OK 了好不好。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
**_____________**
[上一篇:常用的位操作](../算法思维系列/常用的位操作.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:烧饼排序](../算法思维系列/烧饼排序.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 设计Twitter
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [面试官:你说对MySQL事务很熟?那我问你10个问题](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[355.设计推特](https://leetcode-cn.com/problems/design-twitter)
**-----------**
「design Twitter」是 LeetCode 上第 355 道题目,不仅题目本身很有意思,而且把合并多个有序链表的算法和面向对象设计(OO design)结合起来了,很有实际意义,本文就带大家来看看这道题。
至于 Twitter 的什么功能跟算法有关系,等我们描述一下题目要求就知道了。
......@@ -268,17 +288,16 @@ public List<Integer> getNewsFeed(int userId) {
我们解决的问题应该只能算 Timeline Service 模块的一小部分,功能越多,系统的复杂性可能是指数级增长的。所以说合理的顶层设计十分重要,其作用是远超某一个算法的。
最后,Github 上有一个优秀的开源项目[system-design-primer](https://github.com/donnemartin/system-design-primer),专门收集了很多大型系统设计的案例和解析,而且有中文版本,上面这个图也出自该项目。对系统设计感兴趣的读者可以点击链接查看。
最后,Github 上有一个优秀的开源项目,专门收集了很多大型系统设计的案例和解析,而且有中文版本,上面这个图也出自该项目。对系统设计感兴趣的读者可以点击 [这里](https://github.com/donnemartin/system-design-primer) 查看。
PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件制作的,花了很多时间,尤其是 GIF 图,需要一帧一帧制作。如果本文内容对你有帮助,点个赞分个享,鼓励一下我呗!
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:特殊数据结构:单调队列](../数据结构系列/单调队列.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:递归反转链表的一部分](../数据结构系列/递归反转链表的一部分.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 递归反转链表的一部分
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [回溯算法最佳实践:括号生成](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[92.反转链表II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)
**-----------**
反转单链表的迭代实现不是一个困难的事情,但是递归实现就有点难度了,如果再加一点难度,让你仅仅反转单链表中的一部分,你是否能**够递归实现**呢?
本文就来由浅入深,step by step 地解决这个问题。如果你还不会递归地反转单链表也没关系,**本文会从递归反转整个单链表开始拓展**,只要你明白单链表的结构,相信你能够有所收获。
......@@ -188,13 +208,12 @@ ListNode reverseBetween(ListNode head, int m, int n) {
值得一提的是,递归操作链表并不高效。和迭代解法相比,虽然时间复杂度都是 O(N),但是迭代解法的空间复杂度是 O(1),而递归解法需要堆栈,空间复杂度是 O(N)。所以递归操作链表可以作为对递归算法的练习或者拿去和小伙伴装逼,但是考虑效率的话还是使用迭代算法更好。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:设计Twitter](../数据结构系列/设计Twitter.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:队列实现栈\|栈实现队列](../数据结构系列/队列实现栈栈实现队列.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 队列实现栈|栈实现队列
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo)
* [高性能短链设计](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[232.用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks)
[225.用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues)
**-----------**
队列是一种先进先出的数据结构,栈是一种先进后出的数据结构,形象一点就是这样:
![](../pictures/%E6%A0%88%E9%98%9F%E5%88%97/1.jpg)
......@@ -198,109 +220,12 @@ public boolean empty() {
希望本文对你有帮助。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[Xingsheng Qi](https://github.com/ziggy7) 提供 用栈实现队列 C++解法代码:
```CPP
class MyQueue {
private:
stack<int> s1;
stack<int> s2;
public:
MyQueue() {
}
/** 添加元素到队尾 */
void push(int x) {
s1.push(x);
}
/** 删除队头的元素并返回 */
int pop() {
// 先调用 peek 保证 s2 非空
peek();
//保存 s2 的栈顶元素用于返回
int tmp = s2.top();
s2.pop();
return tmp;
}
/** 返回队头元素 */
int peek() {
if (s2.empty())
// 把 s1 元素压入 s2
while (!s1.empty()){
s2.push(s1.top());
s1.pop();
}
return s2.top();
}
/** 判断队列是否为空 */
bool empty() {
return s1.empty()&& s2.empty();
}
};
```
[Xingsheng Qi](https://github.com/ziggy7) 提供 用队列实现栈 C++解法代码:
```CPP
class MyStack {
private:
queue<int>q;
int top_elem = 0;
public:
MyStack() {
}
/** 添加元素到栈顶 */
void push(int x) {
// x 是队列的队尾,是栈的栈顶
q.push(x);
top_elem = x;
}
/** 删除栈顶的元素并返回 */
int pop() {
int size = q.size();
// 留下队尾 2 个元素
while (size > 2) {
q.push(q.front());
q.pop();
size--;
}
// 记录新的队尾元素
top_elem = q.front();
q.push(q.front());
q.pop();
// 删除之前的队尾元素
int tmp = q.front();
q.pop();
return tmp;
}
/** 返回栈顶元素 */
int top() {
return top_elem;
}
/** 判断栈是否为空 */
bool empty() {
return q.empty();
}
};
```
**_____________**
[上一篇:递归反转链表的一部分](../数据结构系列/递归反转链表的一部分.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:算法学习之路](../算法思维系列/算法学习之路.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# FloodFill算法详解及应用
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何高效进行模幂运算](https://labuladong.gitbook.io/algo)
* [经典动态规划:0-1 背包问题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[733.图像渲染](https://leetcode-cn.com/problems/flood-fill)
**-----------**
啥是 FloodFill 算法呢,最直接的一个应用就是「颜色填充」,就是 Windows 绘画本中那个小油漆桶的标志,可以把一块被圈起来的区域全部染色。
![floodfill](../pictures/floodfill/floodfill.gif)
......@@ -106,7 +126,7 @@ image[x][y] = newColor;
完全 OK,这也是处理「图」的一种常用手段。不过对于此题,不用开数组,我们有一种更好的方法,那就是回溯算法。
前文「回溯算法详解」讲过,这里不再赘述,直接套回溯算法框架:
前文 [回溯算法框架套路](https://labuladong.gitbook.io/algo)讲过,这里不再赘述,直接套回溯算法框架:
```java
void fill(int[][] image, int x, int y,
......@@ -209,20 +229,12 @@ int fill(int[][] image, int x, int y,
以上详细讲解了 FloodFill 算法的框架设计,**二维矩阵中的搜索问题,都逃不出这个算法框架**
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[上一篇:字符串乘法](../算法思维系列/字符串乘法.md)
[下一篇:区间调度之区间合并问题](../算法思维系列/区间调度问题之区间合并.md)
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
......@@ -2,6 +2,7 @@
本章包含一些常用的算法技巧,比如前缀和、回溯思想、位操作、双指针、如何正确书写二分查找等等。
欢迎关注我的公众号 labuladong,方便获得最新的优质文章:
欢迎关注我的公众号 labuladong,查看全部文章:
![labuladong二维码](../pictures/table_qr2.jpg)
![labuladong二维码](../pictures/qrcode.jpg)
\ No newline at end of file
# Union-Find算法应用
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第一期)](https://labuladong.gitbook.io/algo)
* [二分查找详解](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions)
[990.等式方程的可满足性](https://leetcode-cn.com/problems/surrounded-regions)
**-----------**
上篇文章很多读者对于 Union-Find 算法的应用表示很感兴趣,这篇文章就拿几道 LeetCode 题目来讲讲这个算法的巧妙用法。
首先,复习一下,Union-Find 算法解决的是图的动态连通性问题,这个算法本身不难,能不能应用出来主要是看你抽象问题的能力,是否能够把原始问题抽象成一个有关图论的问题。
......@@ -218,13 +240,12 @@ boolean equationsPossible(String[] equations) {
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:Union-Find算法详解](../算法思维系列/UnionFind算法详解.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:一行代码就能解决的算法题](../高频面试系列/一行代码解决的智力题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# Union-Find算法详解
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [一文秒杀四道原地修改数组的算法题](https://labuladong.gitbook.io/algo)
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo)
**-----------**
今天讲讲 Union-Find 算法,也就是常说的并查集算法,主要是解决图论中「动态连通性」问题的。名词很高端,其实特别好理解,等会解释,另外这个算法的应用都非常有趣。
说起这个 Union-Find,应该算是我的「启蒙算法」了,因为《算法4》的开头就介绍了这款算法,可是把我秀翻了,感觉好精妙啊!后来刷了 LeetCode,并查集相关的算法题目都非常有意思,而且《算法4》给的解法竟然还可以进一步优化,只要加一个微小的修改就可以把时间复杂度降到 O(1)。
......@@ -289,12 +305,14 @@ class UF {
Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要 O(N) 的时间和空间复杂度;连通两个节点`union`、判断两个节点的连通性`connected`、计算连通分量`count`所需的时间复杂度均为 O(1)。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:如何调度考生的座位](../高频面试系列/座位调度.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:Union-Find算法应用](../算法思维系列/UnionFind算法应用.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# twoSum问题的核心思想
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo)
* [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[1.两数之和](https://leetcode-cn.com/problems/two-sum)
[170.两数之和 III - 数据结构设计](https://leetcode-cn.com/problems/two-sum-iii-data-structure-design)
**-----------**
Two Sum 系列问题在 LeetCode 上有好几道,这篇文章就挑出有代表性的几道,介绍一下这种问题怎么解决。
### TwoSum I
......@@ -153,112 +175,12 @@ int[] twoSum(int[] nums, int target) {
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[labuladong](https://github.com/labuladong) 提供TwoSum I JAVA解法代码:
```JAVA
int[] twoSum(int[] nums, int target) {
int n = nums.length;
index<Integer, Integer> index = new HashMap<>();
// 构造一个哈希表:元素映射到相应的索引
for (int i = 0; i < n; i++)
index.put(nums[i], i);
for (int i = 0; i < n; i++) {
int other = target - nums[i];
// 如果 other 存在且不是 nums[i] 本身
if (index.containsKey(other) && index.get(other) != i)
return new int[] {i, index.get(other)};
}
return new int[] {-1, -1};
}
```
[Jinglun Zhou](https://github.com/Jasper-Joe) 提供TwoSum I C++解法代码:
```CPP
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n=nums.size();
unordered_map<int,int> index;
// 构造一个哈希表: 元素映射到相应的索引
for(int i=0;i<n;i++) // 进行预处理
index[nums[i]]=i; // 当前的元素作为key, 当前的索引作为value
for(int i=0;i<n;i++)
{
int other=target-nums[i];// 得到能与nums[i]相加凑成target的另外那个数
// 如果other存在且不是nums[i]本身
if(index.count(other) && index[other]!=i)
return {i,index[other]};
}
return {-1,-1};// 如果不存在返回{-1,-1}, 当然根据题意必然存在解
}
};
```
[labuladong](https://github.com/labuladong) 提供TwoSum II JAVA解法代码:
```JAVA
class TwoSum {
Map<Integer, Integer> freq = new HashMap<>();
public void add(int number) {
// 记录 number 出现的次数
freq.put(number, freq.getOrDefault(number, 0) + 1);
}
public boolean find(int value) {
for (Integer key : freq.keySet()) {
int other = value - key;
// 情况一
if (other == key && freq.get(key) > 1)
return true;
// 情况二
if (other != key && freq.containsKey(other))
return true;
}
return false;
}
}
```
[Jinglun Zhou](https://github.com/Jasper-Joe) 提供TwoSum II C++解法代码:
```CPP
class TwoSum {
public:
unordered_map<int,int> freq; // key为当前加入的元素,value为当前加入元素一共出现的频率
TwoSum() {} // constructor
void add(int number) {
// 记录number出现的次数
freq[number]++;
}
bool find(int value) {
for(auto& cur:freq)
{
int other=value-cur.first;
// 情况一: other和当前这个元素一样大,所以需要两个这个的元素才能构成value
if(other==cur.first && cur.second>1)
return true;
// 情况二: other和当前这个元素不一样,other在freq中需要至少出现一次,与twoSum I道理一样
if(other!=cur.first && freq.count(other))
return true;
}
return false;
}
};
```
**_____________**
[上一篇:滑动窗口技巧](../算法思维系列/滑动窗口技巧.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:常用的位操作](../算法思维系列/常用的位操作.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 为什么我推荐《算法4》
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [递归反转链表的一部分](https://labuladong.gitbook.io/algo)
* [25 张图解:键入网址后,到网页显示,其间发生了什么](https://labuladong.gitbook.io/algo)
**-----------**
通知:如果本站对你学习算法有帮助,**请收藏网址,并推荐给你的朋友**。由于 **labuladong** 的算法套路太火,很多人直接拿我的 GitHub 文章去开付费专栏,价格还不便宜。我这免费写给你看,**多宣传原创作者是你唯一能做的**,谁也不希望劣币驱逐良币对吧?
咱们的公众号有很多硬核的算法文章,今天就聊点轻松的,就具体聊聊我非常“鼓吹”的《算法4》。这本书我在之前的文章多次推荐过,但是没有具体的介绍,今天就来正式介绍一下。。
我的推荐不会直接甩一大堆书目,而是会联系实际生活,讲一些书中有趣有用的知识,无论你最后会不会去看这本书,本文都会给你带来一些收获。
......@@ -58,7 +76,7 @@
图论中有一个经典算法叫做 **Bellman-Ford 算法,可以用于寻找负权重环**。对于我们说的套汇问题,可以先把所有边的权重 w 替换成 -ln(w),这样「寻找权重乘积大于 1 的环」就转化成了「寻找权重和小于 0 的环」,就可以使用 Bellman-Ford 算法在 O(EV) 的时间内寻找负权重环,也就是寻找套汇机会。
《算法4》就介绍到这里,关于上面两个例子的具体内容,可以自己去看书,公众号后台回复关键词「算法4」就有 PDF
《算法4》就介绍到这里,关于上面两个例子的具体内容,可以自己去看书,**公众号后台回复关键词「算法4」就有 PDF**
### 三、最后说几句
......@@ -71,12 +89,14 @@
另外,读书在精不在多。你花时间《算法4》过个大半(最后小半部分有点困难),同时刷点题,看看咱们的公众号文章,算法这块真就够了,别对细节问题太较真。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**,公众号后台回复关键词「算法4」可以获得 PDF 下载:
![labuladong](../pictures/labuladong.png)
[上一篇:学习算法和刷题的框架思维](../算法思维系列/学习数据结构和算法的高效方法.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:动态规划解题框架](../动态规划系列/动态规划详解进阶.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 二分查找详解
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo)
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[704.二分查找](https://leetcode-cn.com/problems/binary-search)
[34.在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
**-----------**
先给大家讲个笑话乐呵一下:
有一天阿东到图书馆借了 N 本书,出图书馆的时候,警报响了,于是保安把阿东拦下,要检查一下哪本书没有登记出借。阿东正准备把每一本书在报警器下过一下,以找出引发警报的书,但是保安露出不屑的眼神:你连二分查找都不会吗?于是保安把书分成两堆,让第一堆过一下报警器,报警器响;于是再把这堆书分成两堆…… 最终,检测了 logN 次之后,保安成功的找到了那本引起警报的书,露出了得意和嘲讽的笑容。于是阿东背着剩下的书走了。
......@@ -474,12 +496,14 @@ int right_bound(int[] nums, int target) {
4、如果将「搜索区间」全都统一成两端都闭,好记,只要稍改 `nums[mid] == target` 条件处的代码和返回的逻辑即可,**推荐拿小本本记下,作为二分搜索模板**
呵呵,此文对二分查找的问题无敌好吧!**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:回溯算法解题框架](../算法思维系列/回溯算法详解修订版.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:滑动窗口解题框架](../算法思维系列/滑动窗口技巧.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 信封嵌套问题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [讲两道常考的阶乘算法题](https://labuladong.gitbook.io/algo)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[354.俄罗斯套娃信封问题](https://leetcode-cn.com/problems/russian-doll-envelopes)
**-----------**
很多算法问题都需要排序技巧,其难点不在于排序本身,而是需要巧妙地排序进行预处理,将算法问题进行转换,为之后的操作打下基础。
信封嵌套问题就需要先按特定的规则排序,之后就转换为一个 [最长递增子序列问题](../动态规划系列/动态规划设计:最长递增子序列.md),可以用前文 [二分查找详解](二分查找详解.md) 的技巧来解决了。
信封嵌套问题就需要先按特定的规则排序,之后就转换为一个 [最长递增子序列问题](https://labuladong.gitbook.io/algo),可以用前文 [二分查找详解](https://labuladong.gitbook.io/algo) 的技巧来解决了。
### 一、题目概述
......@@ -89,9 +109,9 @@ public int lengthOfLIS(int[] nums) {
为了清晰,我将代码分为了两个函数, 你也可以合并,这样可以节省下 `height` 数组的空间。
此算法的时间复杂度为 $O(NlogN)$,因为排序和计算 LIS 各需要 $O(NlogN)$ 的时间。
此算法的时间复杂度为 `O(NlogN)`,因为排序和计算 LIS 各需要 `O(NlogN)` 的时间。
空间复杂度为 $O(N)$,因为计算 LIS 的函数中需要一个 `top` 数组。
空间复杂度为 `O(N)`,因为计算 LIS 的函数中需要一个 `top` 数组。
### 三、总结
......@@ -105,13 +125,12 @@ public int lengthOfLIS(int[] nums) {
有很多算法问题都需要排序后进行处理,阿东正在进行整理总结。希望本文对你有帮助。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:区间调度之区间交集问题](../算法思维系列/区间交集问题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:几个反直觉的概率问题](../算法思维系列/几个反直觉的概率问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 几个反直觉的概率问题
上篇文章 [洗牌算法详解](./洗牌算法.md) 讲到了验证概率算法的蒙特卡罗方法,今天聊点轻松的内容:几个和概率相关的有趣问题。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo)
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo)
**-----------**
上篇文章 [洗牌算法详解](https://labuladong.gitbook.io/algo) 讲到了验证概率算法的蒙特卡罗方法,今天聊点轻松的内容:几个和概率相关的有趣问题。
计算概率有下面两个最简单的原则:
......@@ -64,11 +80,12 @@
不是的,就像中奖率 50% 的游戏,你玩两次的中奖率就是 100% 吗?显然不是,你玩两次的中奖率是 75%:
$P(两次能中奖) = P(第一次就中了) + P(第一次没中但第二次中了) = 1/2 + 1/2*1/2 = 75\%$
`P(两次能中奖) = P(第一次就中了) + P(第一次没中但第二次中了) = 1/2 + 1/2*1/2 = 75%`
那么换到生日悖论也是一个道理,概率不是简单叠加,而要考虑一个连续的过程,所以这个结论并没有什么不合常理之处。
那为什么只要 23 个人出现相同生日的概率就能大于 50% 了呢?我们先计算 23 个人生日都唯一(不重复)的概率。只有 1 个人的时候,生日唯一的概率是 $365/365$,2 个人时,生日唯一的概率是 $365/365 × 364/365$,以此类推可知 23 人的生日都唯一的概率:
那为什么只要 23 个人出现相同生日的概率就能大于 50% 了呢?我们先计算 23 个人生日都唯一(不重复)的概率。只有 1 个人的时候,生日唯一的概率是 `365/365`,2 个人时,生日唯一的概率是 `365/365 × 364/365`,以此类推可知 23 人的生日都唯一的概率:
![](../pictures/概率问题/p.png)
......@@ -114,13 +131,12 @@ $P(两次能中奖) = P(第一次就中了) + P(第一次没中但第二次中
当然,运用此策略蒙题的前提是你真的抓瞎,真的随机乱选答案,这样概率才能作为最后的杀手锏。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:信封嵌套问题](../算法思维系列/信封嵌套问题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:洗牌算法](../算法思维系列/洗牌算法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 前缀和技巧
今天来聊一道简单却十分巧妙的算法问题:算出一共有几个和为 k 的子数组。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何去除有序数组的重复元素](https://labuladong.gitbook.io/algo)
* [区间调度之区间合并问题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[560.和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k)
**-----------**
今天来聊一道简单却十分巧妙的算法问题:算出一共有几个和为 `k` 的子数组。
![](../pictures/%E5%89%8D%E7%BC%80%E5%92%8C/title.png)
那我把所有子数组都穷举出来,算它们的和,看看谁的和等于 k 不就行了。
那我把所有子数组都穷举出来,算它们的和,看看谁的和等于 `k` 不就行了。
关键是,**如何快速得到某个子数组的和呢**,比如说给你一个数组 `nums`,让你实现一个接口 `sum(i, j)`,这个接口要返回 `nums[i..j]` 的和,而且会被多次调用,你怎么实现这个接口呢?
......@@ -50,7 +70,7 @@ int subarraySum(int[] nums, int k) {
}
```
这个解法的时间复杂度 $O(N^2)$ 空间复杂度 $O(N)$,并不是最优的解法。不过通过这个解法理解了前缀和数组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。
这个解法的时间复杂度 `O(N^2)` 空间复杂度 `O(N)`,并不是最优的解法。不过通过这个解法理解了前缀和数组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。
### 二、优化解法
......@@ -103,7 +123,7 @@ int subarraySum(int[] nums, int k) {
![](../pictures/%E5%89%8D%E7%BC%80%E5%92%8C/2.jpg)
这样,就把时间复杂度降到了 $O(N)$,是最优解法了。
这样,就把时间复杂度降到了 `O(N)`,是最优解法了。
### 三、总结
......@@ -129,66 +149,12 @@ for (int i = 1; i < count.length; i++)
希望本文对你有帮助。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[labuladong](https://github.com/labuladong) 提供JAVA解法代码:
```JAVA
int subarraySum(int[] nums, int k) {
int n = nums.length;
// map:前缀和 -> 该前缀和出现的次数
HashMap<Integer, Integer>
preSum = new HashMap<>();
// base case
preSum.put(0, 1);
int ans = 0, sum0_i = 0;
for (int i = 0; i < n; i++) {
sum0_i += nums[i];
// 这是我们想找的前缀和 nums[0..j]
int sum0_j = sum0_i - k;
// 如果前面有这个前缀和,则直接更新答案
if (preSum.containsKey(sum0_j))
ans += preSum.get(sum0_j);
// 把前缀和 nums[0..i] 加入并记录出现次数
preSum.put(sum0_i,
preSum.getOrDefault(sum0_i, 0) + 1);
}
return ans;
}
```
[Jinglun Zhou](https://github.com/Jasper-Joe) 提供C++解法代码:
```CPP
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n=nums.size();
unordered_map<int,int> preSum;
// map: 前缀和 -> 该前缀和出现的次数
preSum[0]=1; // base case: 例如当数组中只有一个元素, 而k恰好等于这个元素
int ans=0, sum0_i=0; // sum0_i 表示前缀和 nums[0...i]
for(int i=0;i<n;i++)
{
sum0_i +=nums[i];
// 这是我们想找的前缀和 nums[0...j]
int sum0_j=sum0_i-k;
// 如果前面有这个前缀和,则直接更新答案
if(preSum.count(sum0_j))
ans+=preSum[sum0_j];
//把前缀和 nums[0...i] 加入并记录出现次数
preSum[sum0_i]++; // 把当前的前缀和加入到map中
}
return ans;
}
};
```
**_____________**
[上一篇:烧饼排序](../算法思维系列/烧饼排序.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:字符串乘法](../算法思维系列/字符串乘法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 区间交集问题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [经典动态规划:编辑距离](https://labuladong.gitbook.io/algo)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[986.区间列表的交集](https://leetcode-cn.com/problems/interval-list-intersections)
**-----------**
本文是区间系列问题的第三篇,前两篇分别讲了区间的最大不相交子集和重叠区间的合并,今天再写一个算法,可以快速找出两组区间的交集。
先看下题目,LeetCode 第 986 题就是这个问题:
......@@ -101,51 +121,14 @@ def intervalIntersection(A, B):
总结一下,区间类问题看起来都比较复杂,情况很多难以处理,但实际上通过观察各种不同情况之间的共性可以发现规律,用简洁的代码就能处理。
另外,区间问题没啥特别厉害的奇技淫巧,其操作也朴实无华,但其应用却十分广泛,接之前的几篇文章:
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[kingkong1111](https://github.com/kingkong1111)提供 Java 代码:
```java
public int[][] intervalIntersection(int[][] A, int[][] B) {
List<int[]> res = new ArrayList<>();
if (A == null || B == null) {
return res.toArray(new int[0][]);
}
// 定义两个指针遍历两个数组
int i =0;
int j =0;
while (i < A.length && j < B.length) {
int a1 = A[i][0];
int a2 = A[i][1];
int b1 = B[j][0];
int b2 = B[j][1];
//1 说明有重合
if (a1 <= b2 && a2 >= b1) {
//2 重合区间:前面的最大值 到 后面的最小值
int start = Math.max(a1, b1);
int end = Math.min(a2, b2);
res.add(new int[]{start, end});
}
//3 哪个区间大,哪个不动,另一个指针动,看是否能扩大重合区间
if (a2 > b2) {
j++;
} else {
i++;
}
}
return res.toArray(new int[0][]);
}
```
[上一篇:区间调度之区间合并问题](../算法思维系列/区间调度问题之区间合并.md)
[下一篇:信封嵌套问题](../算法思维系列/信封嵌套问题.md)
另外,区间问题没啥特别厉害的奇技淫巧,其操作也朴实无华,但其应用却十分广泛。
[目录](../README.md#目录)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 区间调度问题之区间合并
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [Git原理之最近公共祖先](https://labuladong.gitbook.io/algo)
* [洗牌算法](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[56.合并区间](https://leetcode-cn.com/problems/merge-intervals)
**-----------**
上篇文章用贪心算法解决了区间调度问题:给你很多区间,让你求其中的最大不重叠子集。
其实对于区间相关的问题,还有很多其他类型,本文就来讲讲区间合并问题(Merge Interval)。
......@@ -61,13 +81,12 @@ def merge(intervals):
本文终,希望对你有帮助。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:FloodFill算法详解及应用](../算法思维系列/FloodFill算法详解及应用.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:区间调度之区间交集问题](../算法思维系列/区间交集问题.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 双指针技巧总结
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [一文秒杀四道原地修改数组的算法题](https://labuladong.gitbook.io/algo)
* [Linux的进程、线程、文件描述符是什么](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[141.环形链表](https://leetcode-cn.com/problems/linked-list-cycle)
[141.环形链表II](https://leetcode-cn.com/problems/linked-list-cycle-ii)
[167.两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum)
**-----------**
我把双指针技巧再分为两类,一类是「快慢指针」,一类是「左右指针」。前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环;后者主要解决数组(或者字符串)中的问题,比如二分查找。
### 一、快慢指针的常见算法
......@@ -67,7 +91,7 @@ ListNode detectCycle(ListNode head) {
可以看到,当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。这是为什么呢?
第一次相遇时,假设慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步,也就是说比 slow 多走了 k 步(环长度的倍数)。
第一次相遇时,假设慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步,也就是说比 slow 多走了 k 步(也就是环的长度)。
![2](../pictures/%E5%8F%8C%E6%8C%87%E9%92%88/2.png)
......@@ -192,14 +216,16 @@ void reverse(int[] nums) {
这也许是双指针技巧的最高境界了,如果掌握了此算法,可以解决一大类子字符串匹配的问题,不过「滑动窗口」稍微比上述的这些算法复杂些。
幸运的是,这类算法是有框架模板的,而且[这篇文章](滑动窗口技巧.md)就讲解了「滑动窗口」算法模板,帮大家秒杀几道 LeetCode 子串匹配的问题。
幸运的是,这类算法是有框架模板的,而且[这篇文章](https://labuladong.gitbook.io/algo)就讲解了「滑动窗口」算法模板,帮大家秒杀几道 LeetCode 子串匹配的问题。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
**_____________**
[上一篇:滑动窗口解题框架](../算法思维系列/滑动窗口技巧.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:Linux的进程、线程、文件描述符是什么](../技术/linux进程.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 回溯算法详解
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [我写了首诗,把滑动窗口算法算法变成了默写题](https://labuladong.gitbook.io/algo)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[46.全排列](https://leetcode-cn.com/problems/permutations)
[51.N皇后](https://leetcode-cn.com/problems/n-queens)
**-----------**
这篇文章是很久之前的一篇《回溯算法详解》的进阶版,之前那篇不够清楚,就不必看了,看这篇就行。把框架给你讲清楚,你会发现回溯算法问题都是一个套路。
废话不多说,直接上回溯算法框架。**解决一个回溯问题,实际上就是一个决策树的遍历过程**。你只需要思考 3 个问题:
......@@ -138,7 +161,7 @@ void backtrack(int[] nums, LinkedList<Integer> track) {
![](../pictures/backtracking/6.jpg)
至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,为对链表使用 `contains` 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。
至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,为对链表使用 `contains` 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。
但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。**这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高**
......@@ -274,34 +297,14 @@ def backtrack(...):
某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[Zongshuai](https://github.com/zongshuai818) 提供全排列 Python3解法代码:
```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# 回溯算法
result = []
track = [] # 可行路径
def trackBack(nums_, track_):
if len(track_) == len(nums_): # 满足终止条件
result.append(track_[:])
return
for i in nums_: #所有可选项
if i in track_: # 判断是否可选
continue
track.append(i) # 选择
trackBack(nums_, track_) # 递归
track.pop() # 回溯
trackBack(nums, track)
return result
```
**_____________**
[上一篇:动态规划答疑篇](../动态规划系列/最优子结构.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:二分查找解题框架](../算法思维系列/二分查找详解.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 字符串乘法
对于比较小的数字,做运算可以直接使用编程语言提供的运算符,但是如果相乘的两个因数非常大,语言提供的数据类型可能就会溢出。一种替代方案就是,运算数以字符串的形式输入,然后模仿我们小学学习的乘法算术过程计算出结果,并且也用字符串表示。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [关于 Linux shell 你必须知道的](https://labuladong.gitbook.io/algo)
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[43.字符串相乘](https://leetcode-cn.com/problems/multiply-strings)
**-----------**
对于比较小的数字,做运算可以直接使用编程语言提供的运算符,但是如果相乘的两个因数非常大,语言提供的数据类型可能就会溢出。一种替代方案就是,运算数以字符串的形式输入,然后模仿我们小学学习的乘法算术过程计算出结果,并且也用字符串表示。
![](../pictures/%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B9%98%E6%B3%95/title.png)
需要注意的是,`num1``num2` 可以非常长,所以不可以把他们直接转成整型然后运算,唯一的思路就是模仿我们手算乘法。
......@@ -71,13 +91,12 @@ string multiply(string num1, string num2) {
也许算法就是一种**寻找思维定式的思维**吧,希望本文对你有帮助。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:前缀和技巧](../算法思维系列/前缀和技巧.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:FloodFill算法详解及应用](../算法思维系列/FloodFill算法详解及应用.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
\ No newline at end of file
# 学习数据结构和算法的框架思维
# 学习算法和刷题的思路指南
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [twoSum问题的核心思想](https://labuladong.gitbook.io/algo)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo)
**-----------**
通知:如果本站对你学习算法有帮助,**请收藏网址,并推荐给你的朋友**。由于 **labuladong** 的算法套路太火,很多人直接拿我的 GitHub 文章去开付费专栏,价格还不便宜。我这免费写给你看,**多宣传原创作者是你唯一能做的**,谁也不希望劣币驱逐良币对吧?
这是好久之前的一篇文章「学习数据结构和算法的框架思维」的修订版。之前那篇文章收到广泛好评,没看过也没关系,这篇文章会涵盖之前的所有内容,并且会举很多代码的实例,教你如何使用框架思维。
......@@ -100,7 +118,7 @@ class TreeNode {
void traverse(TreeNode root) {
for (TreeNode child : root.children)
traverse(child)
traverse(child);
}
```
......@@ -112,7 +130,7 @@ N 叉树的遍历又可以扩展为图的遍历,因为图就是好几 N 叉棵
首先要明确的是,**数据结构是工具,算法是通过合适的工具解决特定问题的方法**。也就是说,学习算法之前,最起码得了解那些常用的数据结构,了解它们的特性和缺陷。
那么该如何在 LeetCode 刷题呢?之前的文章[算法学习之路](算法学习之路.md)写过一些,什么按标签刷,坚持下去云云。现在距那篇文章已经过去将近一年了,我不说那些不痛不痒的话,直接说具体的建议:
那么该如何在 LeetCode 刷题呢?之前的文章[算法学习之路](https://labuladong.gitbook.io/algo)写过一些,什么按标签刷,坚持下去云云。现在距那篇文章已经过去将近一年了,我不说那些不痛不痒的话,直接说具体的建议:
**先刷二叉树,先刷二叉树,先刷二叉树**
......@@ -196,7 +214,7 @@ void traverse(TreeNode* node) {
再举例吧,说几道我们之前文章写过的问题。
[动态规划详解](../动态规划系列/动态规划详解进阶.md)说过凑零钱问题,暴力解法就是遍历一棵 N 叉树:
[动态规划详解](https://labuladong.gitbook.io/algo)说过凑零钱问题,暴力解法就是遍历一棵 N 叉树:
![](../pictures/动态规划详解进阶/5.jpg)
......@@ -229,7 +247,7 @@ def dp(n):
其实很多动态规划问题就是在遍历一棵树,你如果对树的遍历操作烂熟于心,起码知道怎么把思路转化成代码,也知道如何提取别人解法的核心思路。
再看看回溯算法,前文[回溯算法详解](回溯算法详解修订版.md)干脆直接说了,回溯算法就是个 N 叉树的前后序遍历问题,没有例外。
再看看回溯算法,前文[回溯算法详解](https://labuladong.gitbook.io/algo)干脆直接说了,回溯算法就是个 N 叉树的前后序遍历问题,没有例外。
比如 N 皇后问题吧,主要代码如下:
......@@ -268,7 +286,7 @@ N 叉树的遍历框架,找出来了把~你说,树这种结构重不重要
但是,你要是心中没有框架,那么你根本无法解题,给了你答案,你也不会发现这就是个树的遍历问题。
这种思维是很重要的,[动态规划详解](../动态规划系列/动态规划详解进阶.md)中总结的找状态转移方程的几步流程,有时候按照流程写出解法,说实话我自己都不知道为啥是对的,反正它就是对了。。。
这种思维是很重要的,[动态规划详解](https://labuladong.gitbook.io/algo)中总结的找状态转移方程的几步流程,有时候按照流程写出解法,说实话我自己都不知道为啥是对的,反正它就是对了。。。
**这就是框架的力量,能够保证你在快睡着的时候,依然能写出正确的程序;就算你啥都不会,都能比别人高一个级别。**
......@@ -279,12 +297,14 @@ N 叉树的遍历框架,找出来了把~你说,树这种结构重不重要
刷算法题建议从「树」分类开始刷,结合框架思维,把这几十道题刷完,对于树结构的理解应该就到位了。这时候去看回溯、动规、分治等算法专题,对思路的理解可能会更加深刻一些。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:最长公共子序列](../动态规划系列/最长公共子序列.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:学习数据结构和算法读什么书](../算法思维系列/为什么推荐算法4.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 常用的位操作
本文分两部分,第一部分列举几个有趣的位操作,第二部分讲解算法中常用的 n & (n - 1) 操作,顺便把用到这个技巧的算法题列出来讲解一下。因为位操作很简单,所以假设读者已经了解与、或、异或这三种基本操作。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [40张图解:TCP三次握手和四次挥手面试题](https://labuladong.gitbook.io/algo)
* [动态规划答疑篇](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[191.位1的个数](https://leetcode-cn.com/problems/number-of-1-bits)
[231.2的幂](https://leetcode-cn.com/problems/power-of-two/)
**-----------**
本文分两部分,第一部分列举几个有趣的位操作,第二部分讲解算法中常用的 `n & (n - 1)` 操作,顺便把用到这个技巧的算法题列出来讲解一下。因为位操作很简单,所以假设读者已经了解与、或、异或这三种基本操作。
位操作(Bit Manipulation)可以玩出很多奇技淫巧,但是这些技巧大部分都过于晦涩,没必要深究,读者只要记住一些有用的操作即可。
### 一、几个有趣的位操作
1. 利用或操作 `|` 和空格将英文字符转换为小写
1. **利用或操作 `|` 和空格将英文字符转换为小写**
```c
('a' | ' ') = 'a'
('A' | ' ') = 'a'
```
2. 利用与操作 `&` 和下划线将英文字符转换为大写
2. **利用与操作 `&` 和下划线将英文字符转换为大写**
```c
('b' & '_') = 'B'
('B' & '_') = 'B'
```
3. 利用异或操作 `^` 和空格进行英文字符大小写互换
3. **利用异或操作 `^` 和空格进行英文字符大小写互换**
```c
('d' ^ ' ') = 'D'
('D' ^ ' ') = 'd'
```
PS:以上操作能够产生奇特效果的原因在于 ASCII 编码。字符其实就是数字,恰巧这些字符对应的数字通过位运算就能得到正确的结果,有兴趣的读者可以查 ASCII 码表自己算算,本文就不展开讲了。
以上操作能够产生奇特效果的原因在于 ASCII 编码。字符其实就是数字,恰巧这些字符对应的数字通过位运算就能得到正确的结果,有兴趣的读者可以查 ASCII 码表自己算算,本文就不展开讲了。
4. 判断两个数是否异号
4. **判断两个数是否异号**
```c
```cpp
int x = -1, y = 2;
bool f = ((x ^ y) < 0); // true
......@@ -39,9 +61,9 @@ int x = 3, y = 2;
bool f = ((x ^ y) < 0); // false
```
PS:这个技巧还是很实用的,利用的是补码编码的符号位。如果不用位运算来判断是否异号,需要使用 if else 分支,还挺麻烦的。读者可能想利用乘积或者商来判断两个数是否异号,但是这种处理方式可能造成溢出,从而出现错误。(关于补码编码和溢出,参见前文)
这个技巧还是很实用的,利用的是补码编码的符号位。如果不用位运算来判断是否异号,需要使用 if else 分支,还挺麻烦的。读者可能想利用乘积或者商来判断两个数是否异号,但是这种处理方式可能造成溢出,从而出现错误。
5. 交换两个数
5. **不用临时变量交换两个数**
```c
int a = 1, b = 2;
......@@ -51,7 +73,7 @@ a ^= b;
// 现在 a = 2, b = 1
```
6. 加一
6. **加一**
```c
int n = 1;
......@@ -59,7 +81,7 @@ n = -~n;
// 现在 n = 2
```
7. 减一
7. **减一**
```c
int n = 2;
......@@ -69,17 +91,19 @@ n = ~-n;
PS:上面这三个操作就纯属装逼用的,没啥实际用处,大家了解了解乐呵一下就行。
### 二、算法常用操作 n&(n-1)
### 二、算法常用操作
这个操作是算法中常见的,作用是消除数字 n 的二进制表示中的最后一个 1。
`n&(n-1)` 这个操作是算法中常见的,作用是消除数字 `n` 的二进制表示中的最后一个 1。
看个图就很容易理解了:
![n](../pictures/%E4%BD%8D%E6%93%8D%E4%BD%9C/1.png)
![](../pictures/%E4%BD%8D%E6%93%8D%E4%BD%9C/1.png)
其核心逻辑就是,`n - 1` 一定可以消除最后一个 1,同时把其后的 0 都变成 1,这样再和 `n` 做一次 `&` 运算,就可以仅仅把最后一个 1 变成 0 了。
1. 计算汉明权重(Hamming Weight)
1. **计算汉明权重(Hamming Weight)**
![title](../pictures/%E4%BD%8D%E6%93%8D%E4%BD%9C/title.png)
![](../pictures/%E4%BD%8D%E6%93%8D%E4%BD%9C/title.png)
就是让你返回 n 的二进制表示中有几个 1。因为 n & (n - 1) 可以消除最后一个 1,所以可以用一个循环不停地消除 1 同时计数,直到 n 变成 0 为止。
......@@ -94,7 +118,7 @@ int hammingWeight(uint32_t n) {
}
```
1. 判断一个数是不是 2 的指数
2. **判断一个数是不是 2 的指数**
一个数如果是 2 的指数,那么它的二进制表示一定只含有一个 1:
......@@ -104,7 +128,7 @@ int hammingWeight(uint32_t n) {
2^2 = 4 = 0b0100
```
如果使用位运算技巧就很简单了(注意运算符优先级,括号不可以省略):
如果使用 `n&(n-1)`技巧就很简单了(注意运算符优先级,括号不可以省略):
```cpp
bool isPowerOfTwo(int n) {
......@@ -113,15 +137,37 @@ bool isPowerOfTwo(int n) {
}
```
以上便是一些有趣/常用的位操作。其实位操作的技巧很多,有一个叫做 Bit Twiddling Hacks 的外国网站收集了几乎所有位操作的黑科技玩法,感兴趣的读者可以点击「阅读原文」按钮查看。
**3、查找只出现一次的元素**
![](../pictures/位操作/title1.png)
这里就可以运用异或运算的性质:
一个数和它本身做异或运算结果为 0,即 `a ^ a = 0`;一个数和 0 做异或运算的结果为它本身,即 `a ^ 0 = a`
对于这道题目,我们只要把所有数字进行异或,成对儿的数字就会变成 0,落单的数字和 0 做异或还是它本身,所以最后异或的结果就是只出现一次的元素:
```cpp
int singleNumber(vector<int>& nums) {
int res = 0;
for (int n : nums) {
res ^= n;
}
return res;
}
```
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章
以上便是一些有趣/常用的位操作。其实位操作的技巧很多,有一个叫做 Bit Twiddling Hacks 的外国网站收集了几乎所有位操作的黑科技玩法,感兴趣的读者可以查看
![labuladong](../pictures/labuladong.jpg)
http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
**_____________**
[上一篇:twoSum问题的核心思想](../算法思维系列/twoSum问题的核心思想.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:拆解复杂问题:实现计算器](../数据结构系列/实现计算器.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 洗牌算法
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [二叉搜索树操作集锦](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[384.打乱数组](https://leetcode-cn.com/problems/shuffle-an-array)
**-----------**
我知道大家会各种花式排序算法,但是如果叫你打乱一个数组,你是否能做到胸有成竹?即便你拍脑袋想出一个算法,怎么证明你的算法就是正确的呢?乱序算法不像排序算法,结果唯一可以很容易检验,因为「乱」可以有很多种,你怎么能证明你的算法是「真的乱」呢?
所以我们面临两个问题:
......@@ -102,9 +122,9 @@ void shuffle(int[] arr) {
}
```
现在你应该明白这种写法为什么会错误了。因为这种写法得到的所有可能结果有 $n^n$ 种,而不是 $n!$ 种,而且 $n^n$ 不可能是 $n!$ 的整数倍。
现在你应该明白这种写法为什么会错误了。因为这种写法得到的所有可能结果有 `n^n` 种,而不是 `n!` 种,而且 `n^n` 不可能是 `n!` 的整数倍。
比如说 `arr = {1,2,3}`,正确的结果应该有 $3!= 6$ 种可能,而这种写法总共有 $3^3 = 27$ 种可能结果。因为 27 不能被 6 整除,所以一定有某些情况被「偏袒」了,也就是说某些情况出现的概率会大一些,所以这种打乱结果不算「真的乱」。
比如说 `arr = {1,2,3}`,正确的结果应该有 `3!= 6` 种可能,而这种写法总共有 `3^3 = 27` 种可能结果。因为 27 不能被 6 整除,所以一定有某些情况被「偏袒」了,也就是说某些情况出现的概率会大一些,所以这种打乱结果不算「真的乱」。
上面我们从直觉上简单解释了洗牌算法正确的准则,没有数学证明,我想大家也懒得证明。对于概率问题我们可以使用「蒙特卡罗方法」进行简单验证。
......@@ -181,13 +201,12 @@ for (int feq : count)
第二部分写了洗牌算法正确性的衡量标准,即每种随机结果出现的概率必须相等。如果我们不用严格的数学证明,可以通过蒙特卡罗方法大力出奇迹,粗略验证算法的正确性。蒙特卡罗方法也有不同的思路,不过要求不必太严格,因为我们只是寻求一个简单的验证。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:几个反直觉的概率问题](../算法思维系列/几个反直觉的概率问题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:递归详解](../算法思维系列/递归详解.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
......@@ -334,8 +334,8 @@ class Solution:
return s[start:start+min_len] if min_len != float("Inf") else ""
```
[上一篇:二分查找解题框架](../算法思维系列/二分查找详解.md)
[上一篇:二分查找解题框架](https://labuladong.gitbook.io/algo)
[下一篇:双指针技巧解题框架](../算法思维系列/双指针技巧.md)
[下一篇:双指针技巧解题框架](https://labuladong.gitbook.io/algo)
[目录](../README.md#目录)
[目录](https://labuladong.gitbook.io/algo#目录)
# 烧饼排序
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo)
* [Union-Find算法应用](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[969.煎饼排序](https://leetcode-cn.com/problems/pancake-sorting)
**-----------**
烧饼排序是个很有意思的实际问题:假设盘子上有 `n`**面积大小不一**的烧饼,你如何用一把锅铲进行若干次翻转,让这些烧饼的大小有序(小的在上,大的在下)?
![](../pictures/pancakeSort/1.jpg)
......@@ -14,7 +34,7 @@
![](../pictures/pancakeSort/title.png)
如何解决这个问题呢?其实类似上篇文章 [递归反转链表的一部分](../数据结构系列/递归反转链表的一部分.md),这也是需要**递归思想**的。
如何解决这个问题呢?其实类似上篇文章 [递归反转链表的一部分](https://labuladong.gitbook.io/algo),这也是需要**递归思想**的。
### 一、思路分析
......@@ -119,49 +139,12 @@ void reverse(int[] arr, int i, int j) {
不妨分享一下你的思考。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[AkiJoey](https://github.com/AkiJoey) 提供 C++ 解法代码:
```c++
class Solution {
public:
vector<int> pancakeSort(vector<int>& A) {
sort(A, A.size());
return res;
}
private:
vector<int> res;
void sort(vector<int>& arr, int n) {
// base case
if (n == 1)
return;
// 寻找最大饼的索引
int max = 0, index = 0;
for(int i = 0;i < n;i++)
if (arr[i] > max) {
max = arr[i];
index = i;
}
// 第一次翻转,将最大饼翻到最上面
reverse(arr.begin(), arr.begin() + index + 1);
res.emplace_back(index + 1);
// 第二次翻转,将最大饼翻到最下面
reverse(arr.begin(), arr.begin() + n);
res.emplace_back(n);
// 递归调用
sort(arr, n - 1);
}
};
```
**_____________**
[上一篇:拆解复杂问题:实现计算器](../数据结构系列/实现计算器.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:前缀和技巧](../算法思维系列/前缀和技巧.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 算法学习之路
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo)
* [回溯算法团灭子集、排列、组合问题](https://labuladong.gitbook.io/algo)
**-----------**
之前发的那篇关于框架性思维的文章,我也发到了不少其他圈子,受到了大家的普遍好评,这一点我真的没想到,首先感谢大家的认可,我会更加努力,写出通俗易懂的算法文章。
有很多朋友问我数据结构和算法到底该怎么学,尤其是很多朋友说自己是「小白」,感觉这些东西好难啊,就算看了之前的「框架思维」,也感觉自己刷题乏力,希望我能聊聊我从一个非科班小白一路是怎么学过来的。
......@@ -76,12 +92,14 @@ PS:**如果有的英文题目实在看不懂,有个小技巧**,你在题
以上,不光是坚持刷算法题吧,很多场景都适用。执行力是要靠「欲望」支撑的,我也是一凡人,只有那些看得见摸得着的东西才能使我快乐呀。读者不妨也尝试把刷题学习和自己的切身利益联系起来,这恐怕是坚持下去最简单直白的理由了。
**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**
![labuladong](../pictures/labuladong.png)
[上一篇:队列实现栈\|栈实现队列](../数据结构系列/队列实现栈栈实现队列.md)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:回溯算法详解](../算法思维系列/回溯算法详解修订版.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 递归详解
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [设计Twitter](https://labuladong.gitbook.io/algo)
**-----------**
首先说明一个问题,简单阐述一下递归,分治算法,动态规划,贪心算法这几个东西的区别和联系,心里有个印象就好。
递归是一种编程技巧,一种解决问题的思维方式;分治算法和动态规划很大程度上是递归思想基础上的(虽然动态规划的最终版本大都不是递归了,但解题思想还是离不开递归),解决更具体问题的两类算法思想;贪心算法是动态规划算法的一个子集,可以更高效解决一部分更特殊的问题。
......@@ -249,13 +265,12 @@ https://leetcode.com/tag/divide-and-conquer/
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:洗牌算法](../算法思维系列/洗牌算法.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:如何实现LRU算法](../高频面试系列/LRU算法.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
此差异已折叠。
......@@ -2,6 +2,6 @@
8 说了,本章都是高频面试题,配合前面的动态规划系列,祝各位马到成功!
欢迎关注我的公众号 labuladong,方便获得最新的优质文章:
欢迎关注我的公众号 labuladong,查看全部文章:
![labuladong二维码](../pictures/qrcode.jpg)
\ No newline at end of file
# 如何运用二分查找算法
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [如何运用贪心思想玩跳跃游戏](https://labuladong.gitbook.io/algo)
* [如何寻找最长回文子串](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[875.爱吃香蕉的珂珂](https://leetcode-cn.com/problems/koko-eating-bananas)
[1011.在D天内送达包裹的能力](https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days)
**-----------**
二分查找到底有能运用在哪里?
最常见的就是教科书上的例子,在**有序数组**中搜索给定的某个目标值的索引。再推广一点,如果目标值存在重复,修改版的二分查找可以返回目标值的左侧边界索引或者右侧边界索引。
......@@ -137,13 +159,12 @@ for (int i = 0; i < n; i++)
return ans;
```
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:如何计算编辑距离](../动态规划系列/编辑距离.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:如何高效解决接雨水问题](../高频面试系列/接雨水.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 如何k个一组反转链表
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [区间调度之区间交集问题](https://labuladong.gitbook.io/algo)
* [动态规划和回溯算法到底谁是谁爹?](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[25.K个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group)
**-----------**
之前的文章「递归反转链表的一部分」讲了如何递归地反转一部分链表,有读者就问如何迭代地反转链表,这篇文章解决的问题也需要反转链表的函数,我们不妨就用迭代方式来解决。
本文要解决「K 个一组反转链表」,不难理解:
......@@ -12,7 +32,7 @@
### 一、分析问题
首先,前文[学习数据结构的框架思维](../算法思维系列/学习数据结构和算法的框架思维.md)提到过,链表是一种兼具递归和迭代性质的数据结构,认真思考一下可以发现**这个问题具有递归性质**
首先,前文[学习数据结构的框架思维](https://labuladong.gitbook.io/algo)提到过,链表是一种兼具递归和迭代性质的数据结构,认真思考一下可以发现**这个问题具有递归性质**
什么叫递归性质?直接上图理解,比如说我们对这个链表调用 `reverseKGroup(head, 2)`,即以 2 个节点为一组反转链表:
......@@ -71,7 +91,7 @@ ListNode reverse(ListNode a) {
「反转以 `a` 为头结点的链表」其实就是「反转 `a` 到 null 之间的结点」,那么如果让你「反转 `a``b` 之间的结点」,你会不会?
只要更改函数签名,并把上面的代码中 `null` 改成 `b` 即可:
[labuladong](https://github.com/labuladong) 提供Java解法代码:
```java
/** 反转区间 [a, b) 的元素,注意是左闭右开 */
ListNode reverse(ListNode a, ListNode b) {
......@@ -88,23 +108,9 @@ ListNode reverse(ListNode a, ListNode b) {
return pre;
}
```
[renxiaoyao](https://github.com/tianzhongwei) 提供C++解法代码:
```C++
ListNode* reverse(ListNode* begin,ListNode* end) {
ListNode* newHead = nullptr;
ListNode* cur = begin;
while(cur != end) {
ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
```
现在我们迭代实现了反转部分链表的功能,接下来就按照之前的逻辑编写 `reverseKGroup` 函数即可:
[labuladong](https://github.com/labuladong) 提供Java解法代码:
```java
ListNode reverseKGroup(ListNode head, int k) {
if (head == null) return null;
......@@ -123,37 +129,7 @@ ListNode reverseKGroup(ListNode head, int k) {
return newHead;
}
```
[renxiaoyao](https://github.com/tianzhongwei) 提供C++解法代码:
```C++
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(!head) return head;
ListNode* begin = head;
ListNode* end = head;
for(int i = 0 ; i < k ; ++i) {
if(!end)
return head;
end = end->next;
}
ListNode* newHead = reverse(begin,end);
begin->next = reverseKGroup(end,k);
return newHead;
}
private:
ListNode* reverse(ListNode* begin,ListNode* end) {
ListNode* newHead = nullptr;
ListNode* cur = begin;
while(cur != end) {
ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
};
```
解释一下 `for` 循环之后的几句代码,注意 `reverse` 函数是反转区间 `[a, b)`,所以情形是这样的:
![](../pictures/kgroup/6.jpg)
......@@ -170,56 +146,12 @@ private:
那么如何分解问题、发现递归性质呢?这个只能多练习,也许后续可以专门写一篇文章来探讨一下,本文就到此为止吧,希望对大家有帮助!
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
[KAGAWA317](https://github.com/KAGAWA317) 提供Python3解法代码:
```python
# 反转区间 [a, b) 的元素
def reverse(a, b):
pre = None
cur = a
while cur != b:
cur.next, pre, cur = pre, cur, cur.next
return pre
```
[KAGAWA317](https://github.com/KAGAWA317) 提供Python3解法代码:
```python
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
if not head:
return
# 区间 [a, b) 包含 k 个待反转元素
a = b = head
for _ in range(k):
# 不足 k 个,不需要反转,base case
if not b:
return head
b = b.next
# 反转区间 [a, b) 的元素
def reverse(a, b):
pre = None
cur = a
while cur != b:
cur.next, pre, cur = pre, cur, cur.next
return pre
# 反转前 k 个元素
newHead = reverse(a, b)
# 递归反转后续链表并连接起来
a.next = self.reverseKGroup(b, k)
return newHead
```
**_____________**
[上一篇:如何寻找最长回文子串](../高频面试系列/最长回文子串.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:如何判定括号合法性](../高频面试系列/合法括号判定.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 一行代码就能解决的算法题
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo)
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[292.Nim游戏](https://leetcode-cn.com/problems/nim-game)
[877.石子游戏](https://leetcode-cn.com/problems/stone-game)
[319.灯泡开关](https://leetcode-cn.com/problems/bulb-switcher)
**-----------**
下文是我在 LeetCode 刷题过程中总结的三道有趣的「脑筋急转弯」题目,可以使用算法编程解决,但只要稍加思考,就能找到规律,直接想出答案。
### 一、Nim 游戏
......@@ -101,27 +125,26 @@ int bulbSwitch(int n) {
我们假设只有 6 盏灯,而且我们只看第 6 盏灯。需要进行 6 轮操作对吧,请问对于第 6 盏灯,会被按下几次开关呢?这不难得出,第 1 轮会被按,第 2 轮,第 3 轮,第 6 轮都会被按。
为什么第 1、2、3、6 轮会被按呢?因为 `6=1x6=2x3`。一般情况下,因子都是成对出现的,也就是说开关被按的次数一般是偶数次。但是有特殊情况,比如说总共有 16 盏灯,那么第 16 盏灯会被按几次?
为什么第 1、2、3、6 轮会被按呢?因为 `6=1*6=2*3`。一般情况下,因子都是成对出现的,也就是说开关被按的次数一般是偶数次。但是有特殊情况,比如说总共有 16 盏灯,那么第 16 盏灯会被按几次?
`16=1x16=2x8=4x4`
`16=1*16=2*8=4*4`
其中因子 4 重复出现,所以第 16 盏灯会被按 5 次,奇数次。现在你应该理解这个问题为什么和平方根有关了吧?
不过,我们不是要算最后有几盏灯亮着吗,这样直接平方根一下是啥意思呢?稍微思考一下就能理解了。
就假设现在总共有 16 盏灯,我们求 16 的平方根,等于 4,这就说明最后会有 4 盏灯亮着,它们分别是第 `1x1=1` 盏、第 `2x2=4` 盏、第 `3x3=9` 盏和第 `4x4=16` 盏。
就假设现在总共有 16 盏灯,我们求 16 的平方根,等于 4,这就说明最后会有 4 盏灯亮着,它们分别是第 `1*1=1` 盏、第 `2*2=4` 盏、第 `3*3=9` 盏和第 `4*4=16` 盏。
就算有的 n 平方根结果是小数,强转成 int 型,也相当于一个最大整数上界,比这个上界小的所有整数,平方后的索引都是最后亮着的灯的索引。所以说我们直接把平方根转成整数,就是这个问题的答案。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:Union-Find算法应用](../算法思维系列/UnionFind算法应用.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:二分查找高效判定子序列](../高频面试系列/二分查找判定子序列.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
# 二分查找高效判定子序列
二分查找本身不难理解,难在巧妙地运用二分查找技巧。对于一个问题,你可能都很难想到它跟二分查找有关,比如前文 [最长递增子序列](../动态规划系列/动态规划设计:最长递增子序列.md) 就借助一个纸牌游戏衍生出二分查找解法。
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://i.loli.net/2020/10/10/MhRTyUKfXZOlQYN.jpg"><img src="https://img.shields.io/badge/公众号-@labuladong-000000.svg?style=flat-square&logo=WeChat"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](../pictures/souyisou.png)
相关推荐:
* [twoSum问题的核心思想](https://labuladong.gitbook.io/algo)
* [经典动态规划:子集背包问题](https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[392.判断子序列](https://leetcode-cn.com/problems/is-subsequence)
**-----------**
二分查找本身不难理解,难在巧妙地运用二分查找技巧。对于一个问题,你可能都很难想到它跟二分查找有关,比如前文 [最长递增子序列](https://labuladong.gitbook.io/algo) 就借助一个纸牌游戏衍生出二分查找解法。
今天再讲一道巧用二分查找的算法问题:如何判定字符串 `s` 是否是字符串 `t` 的子序列(可以假定 `s` 长度比较小,且 `t` 的长度非常大)。举两个例子:
......@@ -75,7 +95,7 @@ for (int i = 0; i < n; i++) {
### 三、再谈二分查找
在前文 [二分查找详解](../算法思维系列/二分查找详解.md) 中,详解了如何正确写出三种二分查找算法的细节。二分查找返回目标值 `val` 的索引,对于搜索**左侧边界**的二分查找,有一个特殊性质:
在前文 [二分查找详解](https://labuladong.gitbook.io/algo) 中,详解了如何正确写出三种二分查找算法的细节。二分查找返回目标值 `val` 的索引,对于搜索**左侧边界**的二分查找,有一个特殊性质:
**当 `val` 不存在时,得到的索引恰好是比 `val` 大的最小元素索引**
......@@ -138,13 +158,12 @@ boolean isSubsequence(String s, String t) {
可见借助二分查找,算法的效率是可以大幅提升的。
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
![labuladong](../pictures/labuladong.jpg)
**_____________**
[上一篇:一行代码就能解决的算法题](../高频面试系列/一行代码解决的智力题.md)
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
[下一篇:Linux的进程、线程、文件描述符是什么](../技术/linux进程.md)
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
[目录](../README.md#目录)
\ No newline at end of file
<p align='center'>
<img src="../pictures/table_qr2.jpg" width=500 >
</p>
......@@ -108,8 +108,8 @@ def deleteDuplicates(self, head: ListNode) -> ListNode:
return head
```
[上一篇:如何高效解决接雨水问题](../高频面试系列/接雨水.md)
[上一篇:如何高效解决接雨水问题](https://labuladong.gitbook.io/algo)
[下一篇:如何寻找最长回文子串](../高频面试系列/最长回文子串.md)
[下一篇:如何寻找最长回文子串](https://labuladong.gitbook.io/algo)
[目录](../README.md#目录)
[目录](https://labuladong.gitbook.io/algo#目录)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册