diff --git a/README.md b/README.md index e706c4b144..c969002390 100644 --- a/README.md +++ b/README.md @@ -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**,坚持高质量原创,说是最良心最硬核的技术公众号都不为过。本仓库的文章就是从公众号里整理出来的**一部分**内容,公众号后台回复关键词【电子书】可以获得这份小抄的完整版本;回复【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
diff --git "a/pictures/LRU\347\256\227\346\263\225/put.jpg" "b/pictures/LRU\347\256\227\346\263\225/put.jpg" new file mode 100644 index 0000000000..ca580eb634 Binary files /dev/null and "b/pictures/LRU\347\256\227\346\263\225/put.jpg" differ diff --git a/pictures/souyisou.png b/pictures/souyisou.png new file mode 100644 index 0000000000..15372ce6f6 Binary files /dev/null and b/pictures/souyisou.png differ diff --git a/pictures/table_qr2.jpg b/pictures/table_qr2.jpg new file mode 100644 index 0000000000..039e577284 Binary files /dev/null and b/pictures/table_qr2.jpg differ diff --git "a/pictures/\346\255\243\345\210\231/1.jpeg" "b/pictures/\346\255\243\345\210\231/1.jpeg" new file mode 100644 index 0000000000..e5977fcae6 Binary files /dev/null and "b/pictures/\346\255\243\345\210\231/1.jpeg" differ diff --git "a/pictures/\346\255\243\345\210\231/2.jpeg" "b/pictures/\346\255\243\345\210\231/2.jpeg" new file mode 100644 index 0000000000..f3fa3711a5 Binary files /dev/null and "b/pictures/\346\255\243\345\210\231/2.jpeg" differ diff --git "a/pictures/\346\255\243\345\210\231/3.jpeg" "b/pictures/\346\255\243\345\210\231/3.jpeg" new file mode 100644 index 0000000000..11d9838ddc Binary files /dev/null and "b/pictures/\346\255\243\345\210\231/3.jpeg" differ diff --git "a/pictures/\346\255\243\345\210\231/4.jpeg" "b/pictures/\346\255\243\345\210\231/4.jpeg" new file mode 100644 index 0000000000..24de168927 Binary files /dev/null and "b/pictures/\346\255\243\345\210\231/4.jpeg" differ diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/README.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/README.md" index 5075ab2b62..8dae5afb4b 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/README.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/README.md" @@ -8,6 +8,6 @@ 这就是思维模式的框架,**本章都会按照以上的模式来解决问题,辅助读者养成这种模式思维**,有了方向遇到问题就不会抓瞎,足以解决一般的动态规划问题。 -欢迎关注我的公众号 labuladong,方便获得最新的优质文章: +欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) \ No newline at end of file diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213KMP\345\255\227\347\254\246\345\214\271\351\205\215\347\256\227\346\263\225.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213KMP\345\255\227\347\254\246\345\214\271\351\205\215\347\256\227\346\263\225.md" index 7276292f5e..c757fff832 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213KMP\345\255\227\347\254\246\345\214\271\351\205\215\347\256\227\346\263\225.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213KMP\345\255\227\347\254\246\345\214\271\351\205\215\347\256\227\346\263\225.md" @@ -1,5 +1,25 @@ # 动态规划之KMP字符匹配算法 + +
+ +![](../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 ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\215\232\345\274\210\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\215\232\345\274\210\351\227\256\351\242\230.md" index d474e003e8..9b0c22f40a 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\215\232\345\274\210\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\215\232\345\274\210\351\227\256\351\242\230.md" @@ -1,6 +1,26 @@ # 动态规划之博弈问题 -上一篇文章 [几道智力题](../高频面试系列/一行代码解决的智力题.md) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。 + + + +![](../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#目录) ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\233\233\351\224\256\351\224\256\347\233\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\233\233\351\224\256\351\224\256\347\233\230.md" index 2605d4c9f2..6dc1c93778 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\233\233\351\224\256\351\224\256\347\233\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\233\233\351\224\256\351\224\256\347\233\230.md" @@ -1,5 +1,27 @@ # 动态规划之四键键盘 + + + +![](../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 ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\255\243\345\210\231\350\241\250\350\276\276.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\255\243\345\210\231\350\241\250\350\276\276.md" index 605bccc2c9..ba289f56d0 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\255\243\345\210\231\350\241\250\350\276\276.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\255\243\345\210\231\350\241\250\350\276\276.md" @@ -1,177 +1,297 @@ # 动态规划之正则表达 -之前的文章「动态规划详解」收到了普遍的好评,今天写一个动态规划的实际应用:正则表达式。如果有读者对「动态规划」还不了解,建议先看一下上面那篇文章。 + -正则表达式匹配是一个很精妙的算法,而且难度也不小。本文主要写两个正则符号的算法实现:点号「.」和星号「*」,如果你用过正则表达式,应该明白他们的用法,不明白也没关系,等会会介绍。文章的最后,介绍了一种快速看出重叠子问题的技巧。 +![](../pictures/souyisou.png) -本文还有一个重要目的,就是教会读者如何设计算法。我们平时看别人的解法,直接看到一个面面俱到的完整答案,总觉得无法理解,以至觉得问题太难,自己太菜。我力求向读者展示,算法的设计是一个螺旋上升、逐步求精的过程,绝不是一步到位就能写出正确算法。本文会带你解决这个较为复杂的问题,让你明白如何化繁为简,逐个击破,从最简单的框架搭建出最终的答案。 +相关推荐: + * [我写了首诗,把滑动窗口算法算法变成了默写题](https://labuladong.gitbook.io/algo) + * [二分查找高效判定子序列](https://labuladong.gitbook.io/algo) -前文无数次强调的框架思维,就是在这种设计过程中逐步培养的。下面进入正题,首先看一下题目: +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: -![title](../pictures/%E6%AD%A3%E5%88%99/title.png) +[10.正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching/) -### 一、热身 +**-----------** -第一步,我们暂时不管正则符号,如果是两个普通的字符串进行比较,如何进行匹配?我想这个算法应该谁都会写: +正则表达式是一个非常强力的工具,本文就来具体看一看正则表达式的底层原理是什么。力扣第 10 题「正则表达式匹配」就要求我们实现一个简单的正则匹配算法,包括「.」通配符和「*」通配符。 + +这两个通配符是最常用的,其中点号「.」可以匹配任意一个字符,星号「*」可以让之前的那个字符重复任意次数(包括 0 次)。 + +比如说模式串 `".a*b"` 就可以匹配文本 `"zaaab"`,也可以匹配 `"cb"`;模式串 `"a..b"` 可以匹配文本 `"amnb"`;而模式串 `".*"` 就比较牛逼了,它可以匹配任何文本。 + +题目会给我们输入两个字符串 `s` 和 `p`,`s` 代表文本,`p` 代表模式串,请你判断模式串 `p` 是否可以匹配文本 `s`。我们可以假设模式串只包含小写字母和上述两种通配符且一定合法,不会出现 `*a` 或者 `b**` 这种不合法的模式串, + +函数签名如下: ```cpp -bool isMatch(string text, string pattern) { - if (text.size() != pattern.size()) - return false; - for (int j = 0; j < pattern.size(); j++) { - if (pattern[j] != text[j]) - return false; - } - return true; -} +bool isMatch(string s, string p); ``` -然后,我稍微改造一下上面的代码,略微复杂了一点,但意思还是一样的,很容易理解吧: +对于我们将要实现的这个正则表达式,难点在那里呢? + +点号通配符其实很好实现,`s` 中的任何字符,只要遇到 `.` 通配符,无脑匹配就完事了。主要是这个星号通配符不好实现,一旦遇到 `*` 通配符,前面的那个字符可以选择重复一次,可以重复多次,也可以一次都不出现,这该怎么办? + +对于这个问题,答案很简单,对于所有可能出现的情况,全部穷举一遍,只要有一种情况可以完成匹配,就认为 `p` 可以匹配 `s`。那么一旦涉及两个字符串的穷举,我们就应该条件反射地想到动态规划的技巧了。 + +### 一、思路分析 + +我们先脑补一下,`s` 和 `p` 相互匹配的过程大致是,两个指针 `i, j` 分别在 `s` 和 `p` 上移动,如果最后两个指针都能移动到字符串的末尾,那么久匹配成功,反之则匹配失败。 + +**如果不考虑 `*` 通配符,面对两个待匹配字符 `s[i]` 和 `p[j]`,我们唯一能做的就是看他俩是否匹配**: ```cpp -bool isMatch(string text, string pattern) { - int i = 0; // text 的索引位置 - int j = 0; // pattern 的索引位置 - while (j < pattern.size()) { - if (i >= text.size()) - return false; - if (pattern[j++] != text[i++]) +bool isMatch(string s, string p) { + int i = 0, j = 0; + while (i < s.size() && j < p.size()) { + // 「.」通配符就是万金油 + if (s[i] == p[j] || p[j] == '.') { + // 匹配,接着匹配 s[i+1..] 和 p[j+1..] + i++; j++; + } else { + // 不匹配 return false; + } } - // 相等则说明完成匹配 - return j == text.size(); + return i == j; } ``` -如上改写,是为了将这个算法改造成递归算法(伪码): +那么考虑一下,如果加入 `*` 通配符,局面就会稍微复杂一些,不过只要分情况来分析,也不难理解。 + +**当 `p[j + 1]` 为 `*` 通配符时,我们分情况讨论下**: + +1、如果 `s[i] == p[j]`,那么有两种情况: + +1.1 `p[j]` 有可能会匹配多个字符,比如 `s = "aaa", p = "a*"`,那么 `p[0]` 会通过 `*` 匹配 3 个字符 `"a"`。 -```python -def isMatch(text, pattern) -> bool: - if pattern is empty: return (text is empty?) - first_match = (text not empty) and pattern[0] == text[0] - return first_match and isMatch(text[1:], pattern[1:]) +1.2 `p[i]` 也有可能匹配 0 个字符,比如 `s = "aa", p = "a*aa"`,由于后面的字符可以匹配 `s`,所以 `p[0]` 只能匹配 0 次。 + +2、如果 `s[i] != p[j]`,只有一种情况: + +`p[j]` 只能匹配 0 次,然后看下一个字符是否能和 `s[i]` 匹配。比如说 `s = "aa", p = "b*aa"`,此时 `p[0]` 只能匹配 0 次。 + +综上,可以把之前的代码针对 `*` 通配符进行一下改造: + +```cpp +if (s[i] == p[j] || p[j] == '.') { + // 匹配 + if (j < p.size() - 1 && p[j + 1] == '*') { + // 有 * 通配符,可以匹配 0 次或多次 + } else { + // 无 * 通配符,老老实实匹配 1 次 + i++; j++; + } +} else { + // 不匹配 + if (j < p.size() - 1 && p[j + 1] == '*') { + // 有 * 通配符,只能匹配 0 次 + } else { + // 无 * 通配符,匹配无法进行下去了 + return false; + } +} ``` -如果你能够理解这段代码,恭喜你,你的递归思想已经到位,正则表达式算法虽然有点复杂,其实是基于这段递归代码逐步改造而成的。 +整体的思路已经很清晰了,但现在的问题是,遇到 `*` 通配符时,到底应该匹配 0 次还是匹配多次?多次是几次? -### 二、处理点号「.」通配符 +你看,这就是一个做「选择」的问题,要把所有可能的选择都穷举一遍才能得出结果。动态规划算法的核心就是「状态」和「选择」,**「状态」无非就是 `i` 和 `j` 两个指针的位置,「选择」就是 `p[j]` 选择匹配几个字符**。 -点号可以匹配任意一个字符,万金油嘛,其实是最简单的,稍加改造即可: +### 二、动态规划解法 -```python -def isMatch(text, pattern) -> bool: - if not pattern: return not text - first_match = bool(text) and pattern[0] in {text[0], '.'} - return first_match and isMatch(text[1:], pattern[1:]) +根据「状态」,我们可以定义一个 `dp` 函数: + +```cpp +bool dp(string& s, int i, string& p, int j); ``` -### 三、处理「*」通配符 +`dp` 函数的定义如下: -星号通配符可以让前一个字符重复任意次数,包括零次。那到底是重复几次呢?这似乎有点困难,不过不要着急,我们起码可以把框架的搭建再进一步: +**若 `dp(s, i, p, j) = true`,则表示 `s[i..]` 可以匹配 `p[j..]`;若 `dp(s, i, p, j) = false`,则表示 `s[i..]` 无法匹配 `p[j..]`**。 -```python -def isMatch(text, pattern) -> bool: - if not pattern: return not text - first_match = bool(text) and pattern[0] in {text[0], '.'} - if len(pattern) >= 2 and pattern[1] == '*': - # 发现 '*' 通配符 - else: - return first_match and isMatch(text[1:], pattern[1:]) +根据这个定义,我们想要的答案就是 `i = 0, j = 0` 时 `dp` 函数的结果,所以可以这样使用这个 `dp` 函数: + +```cpp +bool isMatch(string s, string p) { + // 指针 i,j 从索引 0 开始移动 + return dp(s, 0, p, 0); ``` -星号前面的那个字符到底要重复几次呢?这需要计算机暴力穷举来算,假设重复 N 次吧。前文多次强调过,写递归的技巧是管好当下,之后的事抛给递归。具体到这里,不管 N 是多少,当前的选择只有两个:匹配 0 次、匹配 1 次。所以可以这样处理: +可以根据之前的代码写出 `dp` 函数的主要逻辑: -```py -if len(pattern) >= 2 and pattern[1] == '*': - return isMatch(text, pattern[2:]) or \ - first_match and isMatch(text[1:], pattern) -# 解释:如果发现有字符和 '*' 结合, - # 或者匹配该字符 0 次,然后跳过该字符和 '*' - # 或者当 pattern[0] 和 text[0] 匹配后,移动 text +```cpp +bool dp(string& s, int i, string& p, int j) { + if (s[i] == p[j] || p[j] == '.') { + // 匹配 + if (j < p.size() - 1 && p[j + 1] == '*') { + // 1.1 通配符匹配 0 次或多次 + return dp(s, i, p, j + 2) + || dp(s, i + 1, p, j); + } else { + // 1.2 常规匹配 1 次 + return dp(s, i + 1, p, j + 1); + } + } else { + // 不匹配 + if (j < p.size() - 1 && p[j + 1] == '*') { + // 2.1 通配符匹配 0 次 + return dp(s, i, p, j + 2); + } else { + // 2.2 无法继续匹配 + return false; + } + } +} ``` -可以看到,我们是通过保留 pattern 中的「\*」,同时向后推移 text,来实现「*」将字符重复匹配多次的功能。举个简单的例子就能理解这个逻辑了。假设 `pattern = a*`, `text = aaa`,画个图看看匹配过程: -![example](../pictures/%E6%AD%A3%E5%88%99/example.png) +**根据 `dp` 函数的定义**,这几种情况都很好解释: -至此,正则表达式算法就基本完成了, +1.1 通配符匹配 0 次或多次 -### 四、动态规划 +将 `j` 加 2,`i` 不变,含义就是直接跳过 `p[j]` 和之后的通配符,即通配符匹配 0 次: -我选择使用「备忘录」递归的方法来降低复杂度。有了暴力解法,优化的过程及其简单,就是使用两个变量 i, j 记录当前匹配到的位置,从而避免使用子字符串切片,并且将 i, j 存入备忘录,避免重复计算即可。 +![](../pictures/正则/1.jpeg) -我将暴力解法和优化解法放在一起,方便你对比,你可以发现优化解法无非就是把暴力解法「翻译」了一遍,加了个 memo 作为备忘录,仅此而已。 +将 `i` 加 1,`j` 不变,含义就是 `p[j]` 匹配了 `s[i]`,但 `p[j]` 还可以继续匹配,即通配符匹配多次的情况: -```py -# 带备忘录的递归 -def isMatch(text, pattern) -> bool: - memo = dict() # 备忘录 - def dp(i, j): - if (i, j) in memo: return memo[(i, j)] - if j == len(pattern): return i == len(text) +![](../pictures/正则/2.jpeg) - first = i < len(text) and pattern[j] in {text[i], '.'} - - if j <= len(pattern) - 2 and pattern[j + 1] == '*': - ans = dp(i, j + 2) or \ - first and dp(i + 1, j) - else: - ans = first and dp(i + 1, j + 1) - - memo[(i, j)] = ans - return ans - - return dp(0, 0) +两种情况只要有一种可以完成匹配即可,所以对上面两种情况求或运算。 + +1.2 常规匹配 1 次 + +由于这个条件分支是无 `*` 的常规匹配,那么如果 `s[i] == p[j]`,就是 `i` 和 `j` 分别加一: + +![](../pictures/正则/3.jpeg) + +2.1 通配符匹配 0 次 + +类似情况 1.1,将 `j` 加 2,`i` 不变: + +![](../pictures/正则/1.jpeg) + +2.2 如果没有 `*` 通配符,也无法匹配,那只能说明匹配失败了: + +![](../pictures/正则/4.jpeg) -# 暴力递归 -def isMatch(text, pattern) -> bool: - if not pattern: return not text +看图理解应该很容易了,现在可以思考一下 `dp` 函数的 base case: - first = bool(text) and pattern[0] in {text[0], '.'} +**一个 base case 是 `j == p.size()` 时**,按照 `dp` 函数的定义,这意味着模式串 `p` 已经被匹配完了,那么应该看看文本串 `s` 匹配到哪里了,如果 `s` 也恰好被匹配完,则说明匹配成功: - if len(pattern) >= 2 and pattern[1] == '*': - return isMatch(text, pattern[2:]) or \ - first and isMatch(text[1:], pattern) - else: - return first and isMatch(text[1:], pattern[1:]) +```cpp +if (j == p.size()) { + return i == s.size(); +} ``` -**有的读者也许会问,你怎么知道这个问题是个动态规划问题呢,你怎么知道它就存在「重叠子问题」呢,这似乎不容易看出来呀?** +**另一个 base case 是 `i == s.size()` 时**,按照 `dp` 函数的定义,这种情况意味着文本串 `s` 已经全部被匹配了,那么是不是只要简单地检查一下 `p` 是否也匹配完就行了呢? -解答这个问题,最直观的应该是随便假设一个输入,然后画递归树,肯定是可以发现相同节点的。这属于定量分析,其实不用这么麻烦,下面我来教你定性分析,一眼就能看出「重叠子问题」性质。 +```cpp +if (i == s.size()) { + // 这样行吗? + return j == p.size(); +} +``` -先拿最简单的斐波那契数列举例,我们抽象出递归算法的框架: +**这是不正确的,此时并不能根据 `j` 是否等于 `p.size()` 来判断是否完成匹配,只要 `p[j..]` 能够匹配空串,就可以算完成匹配**。比如说 `s = "a", p = "ab*c*"`,当 `i` 走到 `s` 末尾的时候,`j` 并没有走到 `p` 的末尾,但是 `p` 依然可以匹配 `s`。 -```py -def fib(n): - fib(n - 1) #1 - fib(n - 2) #2 +所以我们可以写出如下代码: + +```cpp +int m = s.size(), n = p.size(); + +if (i == s.size()) { + // 如果能匹配空串,一定是字符和 * 成对儿出现 + if ((n - j) % 2 == 1) { + return false; + } + // 检查是否为 x*y*z* 这种形式 + for (; j + 1 < p.size(); j += 2) { + if (p[j + 1] != '*') { + return false; + } + } + return true; +} ``` -看着这个框架,请问原问题 f(n) 如何触达子问题 f(n - 2) ?有两种路径,一是 f(n) -> #1 -> #1, 二是 f(n) -> #2。前者经过两次递归,后者进过一次递归而已。两条不同的计算路径都到达了同一个问题,这就是「重叠子问题」,而且可以肯定的是,**只要你发现一条重复路径,这样的重复路径一定存在千万条,意味着巨量子问题重叠。** +根据以上思路,就可以写出完整的代码: -同理,对于本问题,我们依然先抽象出算法框架: +```cpp +/* 计算 p[j..] 是否匹配 s[i..] */ +bool dp(string& s, int i, string& p, int j) { + int m = s.size(), n = p.size(); + // base case + if (j == n) { + return i == m; + } + if (i == m) { + if ((n - j) % 2 == 1) { + return false; + } + for (; j + 1 < n; j += 2) { + if (p[j + 1] != '*') { + return false; + } + } + return true; + } -```py -def dp(i, j): - dp(i, j + 2) #1 - dp(i + 1, j) #2 - dp(i + 1, j + 1) #3 + // 记录状态 (i, j),消除重叠子问题 + string key = to_string(i) + "," + to_string(j); + if (memo.count(key)) return memo[key]; + + bool res = false; + + if (s[i] == p[j] || p[j] == '.') { + if (j < n - 1 && p[j + 1] == '*') { + res = dp(s, i, p, j + 2) + || dp(s, i + 1, p, j); + } else { + res = dp(s, i + 1, p, j + 1); + } + } else { + if (j < n - 1 && p[j + 1] == '*') { + res = dp(s, i, p, j + 2); + } else { + res = false; + } + } + // 将当前结果记入备忘录 + memo[key] = res; + + return res; +} ``` -提出类似的问题,请问如何从原问题 dp(i, j) 触达子问题 dp(i + 2, j + 2) ?至少有两种路径,一是 dp(i, j) -> #3 -> #3,二是 dp(i, j) -> #1 -> #2 -> #2。因此,本问题一定存在重叠子问题,一定需要动态规划的优化技巧来处理。 +代码中用了一个哈希表 `memo` 消除重叠子问题,因为正则表达算法的递归框架如下: -### 五、最后总结 +```cpp +bool dp(string& s, int i, string& p, int j) { + dp(s, i, p, j + 2); // 1 + dp(s, i + 1, p, j); // 2 + dp(s, i + 1, p, j + 1); // 3 +} +``` -通过本文,你深入理解了正则表达式的两种常用通配符的算法实现。其实点号「.」的实现及其简单,关键是星号「*」的实现需要用到动态规划技巧,稍微复杂些,但是也架不住我们对问题的层层拆解,逐个击破。另外,你掌握了一种快速分析「重叠子问题」性质的技巧,可以快速判断一个问题是否可以使用动态规划套路解决。 +那么,如果让你从 `dp(s, i, p, j)` 得到 `dp(s, i+2, p, j+2)`,至少有两条路径:`1 -> 2 -> 2` 和 `3 -> 3`,那么就说明 `(i+2, j+2)` 这个状态存在重复,这就说明存在重叠子问题。 -回顾整个解题过程,你应该能够体会到算法设计的流程:从简单的类似问题入手,给基本的框架逐渐组装新的逻辑,最终成为一个比较复杂、精巧的算法。所以说,读者不必畏惧一些比较复杂的算法问题,多思考多类比,再高大上的算法在你眼里也不过一个脆皮。 +动态规划的时间复杂度为「状态的总数」*「每次递归花费的时间」,本题中状态的总数当然就是 `i` 和 `j` 的组合,也就是 `M * N`(`M` 为 `s` 的长度,`N` 为 `p` 的长度);递归函数 `dp` 中没有循环(base case 中的不考虑,因为 base case 的触发次数有限),所以一次递归花费的时间为常数。二者相乘,总的时间复杂度为 `O(MN)`。 -如果本文对你有帮助,欢迎关注我的公众号 labuladong,致力于把算法问题讲清楚~ +空间复杂度很简单,就是备忘录 `memo` 的大小,即 `O(MN)`。 +**_____________** +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[上一篇:动态规划之四键键盘](../动态规划系列/动态规划之四键键盘.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[下一篇:最长公共子序列](../动态规划系列/最长公共子序列.md) ++ +
-[目录](../README.md#目录) \ No newline at end of file diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\256\276\350\256\241\357\274\232\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\256\276\350\256\241\357\274\232\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" index a90557e80c..a5e0a86de6 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\256\276\350\256\241\357\274\232\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\256\276\350\256\241\357\274\232\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" @@ -1,26 +1,51 @@ # 动态规划设计:最长递增子序列 -很多读者反应,就算看了前文[动态规划详解](动态规划详解进阶.md),了解了动态规划的套路,也不会写状态转移方程,没有思路,怎么办?本文就借助「最长递增子序列」来讲一种设计动态规划的通用技巧:数学归纳思想。 + -最长递增子序列(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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\257\246\350\247\243\350\277\233\351\230\266.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\257\246\350\247\243\350\277\233\351\230\266.md" index 99d17caaa0..0181c186df 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\257\246\350\247\243\350\277\233\351\230\266.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\212\250\346\200\201\350\247\204\345\210\222\350\257\246\350\247\243\350\277\233\351\230\266.md" @@ -1,33 +1,67 @@ # 动态规划详解 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo) + * [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[509.斐波那契数](https://leetcode-cn.com/problems/fibonacci-number) + +[322.零钱兑换](https://leetcode-cn.com/problems/coin-change) + +**-----------** + 这篇文章是我们号半年前一篇 200 多赞赏的成名之作「动态规划详解」的进阶版。由于账号迁移的原因,旧文无法被搜索到,所以我润色了本文,并添加了更多干货内容,希望本文成为解决动态规划的一部「指导方针」。 -再说句题外话,我们的公众号开号至今写了起码十几篇文章拆解动态规划问题,我都整理到了公众号菜单的「文章目录」中,**它们都提到了动态规划的解题框架思维,本文就系统总结一下**。这段时间本人也从非科班小白成长到刷通半个 LeetCode,所以我总结的套路可能不适合各路大神,但是应该适合大众,毕竟我自己也是一路摸爬滚打过来的。 +动态规划问题(Dynamic Programming)应该是很多读者头疼的,不过这类问题也是最具有技巧性,最有意思的。本书使用了整整一个章节专门来写这个算法,动态规划的重要性也可见一斑。 -算法技巧就那几个套路,如果你心里有数,就会轻松很多,本文就来扒一扒动态规划的裤子,形成一套解决这类问题的思维框架。废话不多说了,上干货。 +刷题刷多了就会发现,算法技巧就那几个套路,**我们后续的动态规划系列章节,都在使用本文的解题框架思维**,如果你心里有数,就会轻松很多。所以本文放在第一章,来扒一扒动态规划的裤子,形成一套解决这类问题的思维框架,希望能够成为解决动态规划问题的一部指导方针。本文就来讲解该算法的基本套路框架,下面上干货。 -**动态规划问题的一般形式就是求最值**。动态规划其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说让你求**最长**递增子序列呀,**最小**编辑距离呀等等。 +**首先,动态规划问题的一般形式就是求最值**。动态规划其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说让你求**最长**递增子序列呀,**最小**编辑距离呀等等。 既然是要求最值,核心问题是什么呢?**求解动态规划的核心问题是穷举**。因为要求最值,肯定要把所有可行的答案穷举出来,然后在其中找最值呗。 -动态规划就这么简单,就是穷举就完事了?我看到的动态规划问题都很难啊! +动态规划这么简单,就是穷举就完事了?我看到的动态规划问题都很难啊! 首先,动态规划的穷举有点特别,因为这类问题**存在「重叠子问题」**,如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。 而且,动态规划问题一定会**具备「最优子结构」**,才能通过子问题的最值得到原问题的最值。 -另外,虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,只有列出**正确的「状态转移方程**」才能正确地穷举。 +另外,虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,只有列出**正确的「状态转移方程」**才能正确地穷举。 以上提到的重叠子问题、最优子结构、状态转移方程就是动态规划三要素。具体什么意思等会会举例详解,但是在实际的算法问题中,**写出状态转移方程是最困难的**,这也就是为什么很多朋友觉得动态规划问题困难的原因,我来提供我研究出来的一个思维框架,辅助你思考状态转移方程: -明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case。 +**明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义**。 -下面通过斐波那契数列问题和凑零钱问题来详解动态规划的基本原理。前者主要是让你明白什么是重叠子问题(斐波那契数列严格来说不是动态规划问题),后者主要举集中于如何列出状态转移方程。 +按上面的套路走,最后的结果就可以套这个框架: -请读者不要嫌弃这个例子简单,**只有简单的例子才能让你把精力充分集中在算法背后的通用思想和技巧上,而不会被那些隐晦的细节问题搞的莫名其妙**。想要困难的例子,历史文章里有的是。 +```python +# 初始化 base case +dp[0][0][...] = base +# 进行状态转移 +for 状态1 in 状态1的所有取值: + for 状态2 in 状态2的所有取值: + for ... + dp[状态1][状态2][...] = 求最值(选择1,选择2...) +``` + +下面通过斐波那契数列问题和凑零钱问题来详解动态规划的基本原理。前者主要是让你明白什么是重叠子问题(斐波那契数列没有求最值,所以严格来说不是动态规划问题),后者主要举集中于如何列出状态转移方程。 ### 一、斐波那契数列 +请读者不要嫌弃这个例子简单,**只有简单的例子才能让你把精力充分集中在算法背后的通用思想和技巧上,而不会被那些隐晦的细节问题搞的莫名其妙**。想要困难的例子,历史文章里有的是。 + **1、暴力递归** 斐波那契数列的数学形式就是递归的,写成代码就是这样: @@ -39,21 +73,21 @@ int fib(int N) { } ``` -这个不用多说了,学校老师讲递归的时候似乎都是拿这个举例。我们也知道这样写代码虽然简洁易懂,但是十分低效,低效在哪里?假设 n = 20,请画出递归树。 - -PS:但凡遇到需要递归的问题,最好都画出递归树,这对你分析算法的复杂度,寻找算法低效的原因都有巨大帮助。 +这个不用多说了,学校老师讲递归的时候似乎都是拿这个举例。我们也知道这样写代码虽然简洁易懂,但是十分低效,低效在哪里?假设 n = 20,请画出递归树: ![](../pictures/动态规划详解进阶/1.jpg) +PS:但凡遇到需要递归的问题,最好都画出递归树,这对你分析算法的复杂度,寻找算法低效的原因都有巨大帮助。 + 这个递归树怎么理解?就是说想要计算原问题 `f(20)`,我就得先计算出子问题 `f(19)` 和 `f(18)`,然后要计算 `f(19)`,我就要先算出子问题 `f(18)` 和 `f(17)`,以此类推。最后遇到 `f(1)` 或者 `f(2)` 的时候,结果已知,就能直接返回结果,递归树不再向下生长了。 -**递归算法的时间复杂度怎么计算?子问题个数乘以解决一个子问题需要的时间。** +**递归算法的时间复杂度怎么计算?就是用子问题个数乘以解决一个子问题需要的时间。** -子问题个数,即递归树中节点的总数。显然二叉树节点总数为指数级别,所以子问题个数为 O(2^n)。 +首先计算子问题个数,即递归树中节点的总数。显然二叉树节点总数为指数级别,所以子问题个数为 O(2^n)。 -解决一个子问题的时间,在本算法中,没有循环,只有 f(n - 1) + f(n - 2) 一个加法操作,时间为 O(1)。 +然后计算解决一个子问题的时间,在本算法中,没有循环,只有 `f(n - 1) + f(n - 2)` 一个加法操作,时间为 O(1)。 -所以,这个算法的时间复杂度为 O(2^n),指数级别,爆炸。 +所以,这个算法的时间复杂度为二者相乘,即 O(2^n),指数级别,爆炸。 观察递归树,很明显发现了算法低效的原因:存在大量重复计算,比如 `f(18)` 被计算了两次,而且你可以看到,以 `f(18)` 为根的这个递归树体量巨大,多算一遍,会耗费巨大的时间。更何况,还不止 `f(18)` 这一个节点被重复计算,所以这个算法及其低效。 @@ -70,7 +104,7 @@ int fib(int N) { if (N < 1) return 0; // 备忘录全初始化为 0 vector+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\233\242\347\201\255\350\202\241\347\245\250\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\233\242\347\201\255\350\202\241\347\245\250\351\227\256\351\242\230.md" index 6bd0de9774..b00b96cd50 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\233\242\347\201\255\350\202\241\347\245\250\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\233\242\347\201\255\350\202\241\347\245\250\351\227\256\351\242\230.md" @@ -1,11 +1,39 @@ # 团灭 LeetCode 股票买卖问题 + + + +![](../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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\250\241\346\235\277.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\250\241\346\235\277.md" index 9e837d3c4e..60d836f0ea 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\250\241\346\235\277.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\250\241\346\235\277.md" @@ -1,5 +1,26 @@ # 动态规划之子序列问题解题模板 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../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#目录) ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\212\242\346\210\277\345\255\220.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\212\242\346\210\277\345\255\220.md" index e931b4e524..8dae329bc6 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\212\242\346\210\277\345\255\220.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\212\242\346\210\277\345\255\220.md" @@ -1,5 +1,30 @@ # 团灭 LeetCode 打家劫舍问题 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\344\274\230\345\255\220\347\273\223\346\236\204.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\344\274\230\345\255\220\347\273\223\346\236\204.md" index d544a4b16d..8ca9dc1a42 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\344\274\230\345\255\220\347\273\223\346\236\204.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\344\274\230\345\255\220\347\273\223\346\236\204.md" @@ -1,5 +1,22 @@ # 动态规划答疑篇 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../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#目录) ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" index 86b697610e..d158110d30 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" @@ -1,5 +1,25 @@ # 最长公共子序列 + + + +![](../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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\347\274\226\350\276\221\350\267\235\347\246\273.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\347\274\226\350\276\221\350\267\235\347\246\273.md" index eb1eb30e1d..27b8c223aa 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\347\274\226\350\276\221\350\267\235\347\246\273.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\347\274\226\350\276\221\350\267\235\347\246\273.md" @@ -1,5 +1,25 @@ # 编辑距离 + + + +![](../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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\264\252\345\277\203\347\256\227\346\263\225\344\271\213\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\264\252\345\277\203\347\256\227\346\263\225\344\271\213\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230.md" index 75157d83e0..1ae5516b00 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\264\252\345\277\203\347\256\227\346\263\225\344\271\213\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\350\264\252\345\277\203\347\256\227\346\263\225\344\271\213\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230.md" @@ -1,5 +1,27 @@ # 贪心算法之区间调度问题 + + + +![](../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+ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" index 50ff9efecc..3211f7cf71 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\350\277\233\351\230\266.md" @@ -1,5 +1,25 @@ # 经典动态规划问题:高楼扔鸡蛋(进阶) + + + +![](../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\}$$ + + +![](../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 ++ +
diff --git "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" index 9bcb6cce43..8b80075c8d 100644 --- "a/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" +++ "b/\345\212\250\346\200\201\350\247\204\345\210\222\347\263\273\345\210\227/\351\253\230\346\245\274\346\211\224\351\270\241\350\233\213\351\227\256\351\242\230.md" @@ -1,5 +1,26 @@ # 经典动态规划问题:高楼扔鸡蛋 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../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\}$$ + + +![](../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+ +
diff --git "a/\346\212\200\346\234\257/linuxshell.md" "b/\346\212\200\346\234\257/linuxshell.md" index 470cbf7c9f..2f3f3a64c6 100644 --- "a/\346\212\200\346\234\257/linuxshell.md" +++ "b/\346\212\200\346\234\257/linuxshell.md" @@ -1,3 +1,21 @@ +# 关于 Linux shell 你必须知道的技巧 + + + + +![](../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#目录) ++ +
diff --git "a/\346\212\200\346\234\257/linux\350\277\233\347\250\213.md" "b/\346\212\200\346\234\257/linux\350\277\233\347\250\213.md" index 2d6c6ba879..7b400c1d36 100644 --- "a/\346\212\200\346\234\257/linux\350\277\233\347\250\213.md" +++ "b/\346\212\200\346\234\257/linux\350\277\233\347\250\213.md" @@ -1,5 +1,21 @@ # Linux的进程、线程、文件描述符是什么 + + + +![](../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 ++ +
diff --git "a/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" "b/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" index e971a88586..67cb321871 100644 --- "a/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" +++ "b/\346\212\200\346\234\257/redis\345\205\245\344\276\265.md" @@ -1,3 +1,21 @@ +# Redis 入侵 + + + + +![](../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) ++ +
\ No newline at end of file diff --git "a/\346\212\200\346\234\257/session\345\222\214cookie.md" "b/\346\212\200\346\234\257/session\345\222\214cookie.md" index bc7b994e88..85b46266dc 100644 --- "a/\346\212\200\346\234\257/session\345\222\214cookie.md" +++ "b/\346\212\200\346\234\257/session\345\222\214cookie.md" @@ -1,3 +1,21 @@ +# 一文读懂 session 和 cookie + + + + +![](../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 ++ +
\ No newline at end of file diff --git "a/\346\212\200\346\234\257/\345\234\250\347\272\277\347\273\203\344\271\240\345\271\263\345\217\260.md" "b/\346\212\200\346\234\257/\345\234\250\347\272\277\347\273\203\344\271\240\345\271\263\345\217\260.md" index f05434b0bd..7aaaf46295 100644 --- "a/\346\212\200\346\234\257/\345\234\250\347\272\277\347\273\203\344\271\240\345\271\263\345\217\260.md" +++ "b/\346\212\200\346\234\257/\345\234\250\347\272\277\347\273\203\344\271\240\345\271\263\345\217\260.md" @@ -1,3 +1,21 @@ +# 在线刷题学习平台 + + + + +![](../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 ++ +
\ No newline at end of file diff --git "a/\346\212\200\346\234\257/\345\257\206\347\240\201\346\212\200\346\234\257.md" "b/\346\212\200\346\234\257/\345\257\206\347\240\201\346\212\200\346\234\257.md" index 6203408d24..8a88315300 100644 --- "a/\346\212\200\346\234\257/\345\257\206\347\240\201\346\212\200\346\234\257.md" +++ "b/\346\212\200\346\234\257/\345\257\206\347\240\201\346\212\200\346\234\257.md" @@ -1,3 +1,21 @@ +# 密码算法的前世今生 + + + + +![](../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) ++ +
-[目录](../README.md#目录) \ No newline at end of file +[test ad](https://labuladong.gitbook.io/algo) \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/README.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/README.md" index 736832579e..4b64bc4743 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/README.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/README.md" @@ -2,6 +2,6 @@ 这一章主要是一些特殊的数据结构设计,比如单调栈解决 Next Greater Number,单调队列解决滑动窗口问题;还有常用数据结构的操作,比如链表、树、二叉堆。 -欢迎关注我的公众号 labuladong,方便获得最新的优质文章: +欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\345\240\206\350\257\246\350\247\243\345\256\236\347\216\260\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\345\240\206\350\257\246\350\247\243\345\256\236\347\216\260\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227.md" index bb3262bc2d..99e0b060f6 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\345\240\206\350\257\246\350\247\243\345\256\236\347\216\260\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\345\240\206\350\257\246\350\247\243\345\256\236\347\216\260\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227.md" @@ -1,5 +1,21 @@ # 二叉堆详解实现优先级队列 + + + +![](../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 ++ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\346\223\215\344\275\234\351\233\206\351\224\246.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\346\223\215\344\275\234\351\233\206\351\224\246.md" index 3fd21e6d74..6c056262d7 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\346\223\215\344\275\234\351\233\206\351\224\246.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\346\223\215\344\275\234\351\233\206\351\224\246.md" @@ -1,6 +1,34 @@ # 二叉搜索树操作集锦 -通过之前的文章[框架思维](../算法思维系列/学习数据结构和算法的高效方法.md),二叉树的遍历框架应该已经印到你的脑子里了,这篇文章就来实操一下,看看框架思维是怎么灵活运用,秒杀一切二叉树问题的。 + + + +![](../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 ++ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\346\240\210.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\346\240\210.md" index e91c439012..19600ded46 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\346\240\210.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\346\240\210.md" @@ -1,4 +1,28 @@ -### 如何使用单调栈解题 +# 如何使用单调栈解题 + + + + +![](../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+ +
\ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\351\230\237\345\210\227.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\351\230\237\345\210\227.md" index 5c466f07cc..e30ddda4cb 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\351\230\237\345\210\227.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\215\225\350\260\203\351\230\237\345\210\227.md" @@ -1,5 +1,25 @@ # 特殊数据结构:单调队列 + + + +![](../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+ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\256\236\347\216\260\350\256\241\347\256\227\345\231\250.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\256\236\347\216\260\350\256\241\347\256\227\345\231\250.md" index dfc9094a4a..dcebc0391d 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\256\236\347\216\260\350\256\241\347\256\227\345\231\250.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\345\256\236\347\216\260\350\256\241\347\256\227\345\231\250.md" @@ -1,8 +1,33 @@ # 拆解复杂问题:实现计算器 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../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 ++ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\350\256\276\350\256\241Twitter.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\350\256\276\350\256\241Twitter.md" index 1fb45750b1..e5cee44c3e 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\350\256\276\350\256\241Twitter.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\350\256\276\350\256\241Twitter.md" @@ -1,5 +1,25 @@ # 设计Twitter + + + +![](../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+ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md" index 1a20d37ea1..7b68d6f7ee 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\200\222\345\275\222\345\217\215\350\275\254\351\223\276\350\241\250\347\232\204\344\270\200\351\203\250\345\210\206.md" @@ -1,5 +1,25 @@ # 递归反转链表的一部分 + + + +![](../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 ++ +
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" index e361f61dac..dd3b37ccc7 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\347\263\273\345\210\227/\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" @@ -1,5 +1,27 @@ # 队列实现栈|栈实现队列 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md" index 1d737cf70a..79c5ac09cd 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/FloodFill\347\256\227\346\263\225\350\257\246\350\247\243\345\217\212\345\272\224\347\224\250.md" @@ -1,5 +1,25 @@ # FloodFill算法详解及应用 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/README.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/README.md" index 02fde7ad2c..d1a6268bc7 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/README.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/README.md" @@ -2,6 +2,7 @@ 本章包含一些常用的算法技巧,比如前缀和、回溯思想、位操作、双指针、如何正确书写二分查找等等。 -欢迎关注我的公众号 labuladong,方便获得最新的优质文章: +欢迎关注我的公众号 labuladong,查看全部文章: +![labuladong二维码](../pictures/table_qr2.jpg) ![labuladong二维码](../pictures/qrcode.jpg) \ No newline at end of file diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md" index 7f250a12e7..c4ebebcc46 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\345\272\224\347\224\250.md" @@ -1,5 +1,27 @@ # Union-Find算法应用 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\350\257\246\350\247\243.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\350\257\246\350\247\243.md" index 3ff8cf4c81..5ea238fa9f 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\350\257\246\350\247\243.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/UnionFind\347\256\227\346\263\225\350\257\246\350\247\243.md" @@ -1,5 +1,21 @@ # Union-Find算法详解 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/twoSum\351\227\256\351\242\230\347\232\204\346\240\270\345\277\203\346\200\235\346\203\263.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/twoSum\351\227\256\351\242\230\347\232\204\346\240\270\345\277\203\346\200\235\346\203\263.md" index 1fd3b72f57..1d2832b21f 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/twoSum\351\227\256\351\242\230\347\232\204\346\240\270\345\277\203\346\200\235\346\203\263.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/twoSum\351\227\256\351\242\230\347\232\204\346\240\270\345\277\203\346\200\235\346\203\263.md" @@ -1,5 +1,27 @@ # twoSum问题的核心思想 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\270\272\344\273\200\344\271\210\346\216\250\350\215\220\347\256\227\346\263\2254.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\270\272\344\273\200\344\271\210\346\216\250\350\215\220\347\256\227\346\263\2254.md" index c400bd528c..3798b389a2 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\270\272\344\273\200\344\271\210\346\216\250\350\215\220\347\256\227\346\263\2254.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\270\272\344\273\200\344\271\210\346\216\250\350\215\220\347\256\227\346\263\2254.md" @@ -1,5 +1,23 @@ # 为什么我推荐《算法4》 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\350\257\246\350\247\243.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\350\257\246\350\247\243.md" index 6aa5431273..f055e006b7 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\350\257\246\350\247\243.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\350\257\246\350\247\243.md" @@ -1,5 +1,27 @@ # 二分查找详解 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\277\241\345\260\201\345\265\214\345\245\227\351\227\256\351\242\230.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\277\241\345\260\201\345\265\214\345\245\227\351\227\256\351\242\230.md" index 4f5e678b40..eed4b72c24 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\277\241\345\260\201\345\265\214\345\245\227\351\227\256\351\242\230.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\344\277\241\345\260\201\345\265\214\345\245\227\351\227\256\351\242\230.md" @@ -1,8 +1,28 @@ # 信封嵌套问题 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\207\240\344\270\252\345\217\215\347\233\264\350\247\211\347\232\204\346\246\202\347\216\207\351\227\256\351\242\230.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\207\240\344\270\252\345\217\215\347\233\264\350\247\211\347\232\204\346\246\202\347\216\207\351\227\256\351\242\230.md" index 33e5426f32..185e7a2504 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\207\240\344\270\252\345\217\215\347\233\264\350\247\211\347\232\204\346\246\202\347\216\207\351\227\256\351\242\230.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\207\240\344\270\252\345\217\215\347\233\264\350\247\211\347\232\204\346\246\202\347\216\207\351\227\256\351\242\230.md" @@ -1,6 +1,22 @@ # 几个反直觉的概率问题 -上篇文章 [洗牌算法详解](./洗牌算法.md) 讲到了验证概率算法的蒙特卡罗方法,今天聊点轻松的内容:几个和概率相关的有趣问题。 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\211\215\347\274\200\345\222\214\346\212\200\345\267\247.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\211\215\347\274\200\345\222\214\346\212\200\345\267\247.md" index d77d0f93c8..a5de61800e 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\211\215\347\274\200\345\222\214\346\212\200\345\267\247.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\211\215\347\274\200\345\222\214\346\212\200\345\267\247.md" @@ -1,10 +1,30 @@ # 前缀和技巧 -今天来聊一道简单却十分巧妙的算法问题:算出一共有几个和为 k 的子数组。 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\344\271\213\345\214\272\351\227\264\345\220\210\345\271\266.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\344\271\213\345\214\272\351\227\264\345\220\210\345\271\266.md" index bc0fd37c12..3a12c58626 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\344\271\213\345\214\272\351\227\264\345\220\210\345\271\266.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\214\272\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\344\271\213\345\214\272\351\227\264\345\220\210\345\271\266.md" @@ -1,5 +1,25 @@ # 区间调度问题之区间合并 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\217\214\346\214\207\351\222\210\346\212\200\345\267\247.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\217\214\346\214\207\351\222\210\346\212\200\345\267\247.md" index e3b308629f..9859ea314f 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\217\214\346\214\207\351\222\210\346\212\200\345\267\247.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\217\214\346\214\207\351\222\210\346\212\200\345\267\247.md" @@ -1,5 +1,29 @@ # 双指针技巧总结 + + + +![](../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#目录) ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\233\236\346\272\257\347\256\227\346\263\225\350\257\246\350\247\243\344\277\256\350\256\242\347\211\210.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\233\236\346\272\257\347\256\227\346\263\225\350\257\246\350\247\243\344\277\256\350\256\242\347\211\210.md" index 23dbaadc74..a614f1f8f9 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\233\236\346\272\257\347\256\227\346\263\225\350\257\246\350\247\243\344\277\256\350\256\242\347\211\210.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\233\236\346\272\257\347\256\227\346\263\225\350\257\246\350\247\243\344\277\256\350\256\242\347\211\210.md" @@ -1,5 +1,28 @@ # 回溯算法详解 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\227\347\254\246\344\270\262\344\271\230\346\263\225.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\227\347\254\246\344\270\262\344\271\230\346\263\225.md" index 9638127110..26ccfe489f 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\227\347\254\246\344\270\262\344\271\230\346\263\225.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\227\347\254\246\344\270\262\344\271\230\346\263\225.md" @@ -1,7 +1,27 @@ # 字符串乘法 -对于比较小的数字,做运算可以直接使用编程语言提供的运算符,但是如果相乘的两个因数非常大,语言提供的数据类型可能就会溢出。一种替代方案就是,运算数以字符串的形式输入,然后模仿我们小学学习的乘法算术过程计算出结果,并且也用字符串表示。 + + +![](../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 ++ +
\ No newline at end of file diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md" index 7b7fcfd594..2ee5b6bc6d 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\347\232\204\351\253\230\346\225\210\346\226\271\346\263\225.md" @@ -1,4 +1,22 @@ -# 学习数据结构和算法的框架思维 +# 学习算法和刷题的思路指南 + + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md" index f239d1b516..a35a3a39f5 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\345\270\270\347\224\250\347\232\204\344\275\215\346\223\215\344\275\234.md" @@ -1,37 +1,59 @@ # 常用的位操作 -本文分两部分,第一部分列举几个有趣的位操作,第二部分讲解算法中常用的 n & (n - 1) 操作,顺便把用到这个技巧的算法题列出来讲解一下。因为位操作很简单,所以假设读者已经了解与、或、异或这三种基本操作。 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\264\227\347\211\214\347\256\227\346\263\225.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\264\227\347\211\214\347\256\227\346\263\225.md" index 5207da39ad..5aa1768584 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\264\227\347\211\214\347\256\227\346\263\225.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\264\227\347\211\214\347\256\227\346\263\225.md" @@ -1,5 +1,25 @@ # 洗牌算法 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md" index 7aac23fdf0..581aca5c98 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\346\273\221\345\212\250\347\252\227\345\217\243\346\212\200\345\267\247.md" @@ -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#目录) diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\203\247\351\245\274\346\216\222\345\272\217.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\203\247\351\245\274\346\216\222\345\272\217.md" index f910b44fa2..5e4e4df029 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\203\247\351\245\274\346\216\222\345\272\217.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\203\247\351\245\274\346\216\222\345\272\217.md" @@ -1,5 +1,25 @@ # 烧饼排序 + + + +![](../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+ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md" index accfede4c4..cd26588f3a 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\347\256\227\346\263\225\345\255\246\344\271\240\344\271\213\350\267\257.md" @@ -1,5 +1,21 @@ # 算法学习之路 + + + +![](../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 ++ +
diff --git "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\351\200\222\345\275\222\350\257\246\350\247\243.md" "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\351\200\222\345\275\222\350\257\246\350\247\243.md" index 1e49bc9ac9..4a0b09fb9d 100644 --- "a/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\351\200\222\345\275\222\350\257\246\350\247\243.md" +++ "b/\347\256\227\346\263\225\346\200\235\347\273\264\347\263\273\345\210\227/\351\200\222\345\275\222\350\257\246\350\247\243.md" @@ -1,5 +1,21 @@ # 递归详解 + + + +![](../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 ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md" index e39c60b3d7..8bdcc69245 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/LRU\347\256\227\346\263\225.md" @@ -1,8 +1,26 @@ -# LRU算法详解 +# 层层拆解,带你手写 LRU 算法 -### 一、什么是 LRU 算法 -就是一种缓存淘汰策略。 + + +![](../pictures/souyisou.png) + +相关推荐: + * [25 张图解:键入网址后,到网页显示,其间发生了什么](https://labuladong.gitbook.io/algo) + * [如何在无限序列中随机抽取元素](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[146.LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/) + +**-----------** + +LRU 算法就是一种缓存淘汰策略,原理不难,但是面试中写出没有 bug 的算法比较有技巧,需要对数据结构进行层层抽象和拆解,本文 labuladong 就给你写一手漂亮的代码。 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢? @@ -10,25 +28,27 @@ LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently 举个简单的例子,安卓手机都可以把软件放到后台运行,比如我先后打开了「设置」「手机管家」「日历」,那么现在他们在后台排列的顺序是这样的: -![jietu](../pictures/LRU%E7%AE%97%E6%B3%95/1.jpg) +![](../pictures/LRU%E7%AE%97%E6%B3%95/1.jpg) 但是这时候如果我访问了一下「设置」界面,那么「设置」就会被提前到第一个,变成这样: -![jietu](../pictures/LRU%E7%AE%97%E6%B3%95/2.jpg) +![](../pictures/LRU%E7%AE%97%E6%B3%95/2.jpg) 假设我的手机只允许我同时开 3 个应用程序,现在已经满了。那么如果我新开了一个应用「时钟」,就必须关闭一个应用为「时钟」腾出一个位置,关那个呢? 按照 LRU 的策略,就关最底下的「手机管家」,因为那是最久未使用的,然后把新开的应用放到最上面: -![jietu](../pictures/LRU%E7%AE%97%E6%B3%95/3.jpg) +![](../pictures/LRU%E7%AE%97%E6%B3%95/3.jpg) 现在你应该理解 LRU(Least Recently Used)策略了。当然还有其他缓存淘汰策略,比如不要按访问的时序来淘汰,而是按访问频率(LFU 策略)来淘汰等等,各有应用场景。本文讲解 LRU 算法策略。 -### 二、LRU 算法描述 +### 一、LRU 算法描述 -LRU 算法实际上是让你设计数据结构:首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。 +力扣第 146 题「LRU缓存机制」就是让你设计数据结构: -注意哦,get 和 put 方法必须都是 $O(1)$ 的时间复杂度,我们举个具体例子来看看 LRU 算法怎么工作。 +首先要接收一个 `capacity` 参数作为缓存的最大容量,然后实现两个 API,一个是 `put(key, val)` 方法存入键值对,另一个是 `get(key)` 方法获取 `key` 对应的 `val`,如果 `key` 不存在则返回 -1。 + +注意哦,`get` 和 `put` 方法必须都是 `O(1)` 的时间复杂度,我们举个具体例子来看看 LRU 算法怎么工作。 ```cpp /* 缓存容量为 2 */ @@ -40,49 +60,64 @@ LRUCache cache = new LRUCache(2); cache.put(1, 1); // cache = [(1, 1)] + cache.put(2, 2); // cache = [(2, 2), (1, 1)] + cache.get(1); // 返回 1 // cache = [(1, 1), (2, 2)] // 解释:因为最近访问了键 1,所以提前至队头 // 返回键 1 对应的值 1 + cache.put(3, 3); // cache = [(3, 3), (1, 1)] // 解释:缓存容量已满,需要删除内容空出位置 // 优先删除久未使用的数据,也就是队尾的数据 // 然后把新的数据插入队头 + cache.get(2); // 返回 -1 (未找到) // cache = [(3, 3), (1, 1)] // 解释:cache 中不存在键为 2 的数据 + cache.put(1, 4); // cache = [(1, 4), (3, 3)] // 解释:键 1 已存在,把原始值 1 覆盖为 4 // 不要忘了也要将键值对提前到队头 ``` -### 三、LRU 算法设计 +### 二、LRU 算法设计 + +分析上面的操作过程,要让 `put` 和 `get` 方法的时间复杂度为 O(1),我们可以总结出 `cache` 这个数据结构必要的条件: + +1、显然 `cache` 中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。 -分析上面的操作过程,要让 put 和 get 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分。 +2、我们要在 `cache` 中快速找某个 `key` 是否已存在并得到对应的 `val`; -因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。 +3、每次访问 `cache` 中的某个 `key`,需要将这个元素变为最近使用的,也就是说 `cache` 要支持在任意位置快速插入和删除元素。 -那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。 +那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 `LinkedHashMap`。 LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样: ![HashLinkedList](../pictures/LRU%E7%AE%97%E6%B3%95/4.jpg) -思想很简单,就是借助哈希表赋予了链表快速查找的特性嘛:可以快速查找某个 key 是否存在缓存(链表)中,同时可以快速删除、添加节点。回想刚才的例子,这种数据结构是不是完美解决了 LRU 缓存的需求? +借助这个结构,我们来逐一分析上面的 3 个条件: + +1、如果我们每次默认从链表尾部添加元素,那么显然越靠尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的。 + +2、对于某一个 `key`,我们可以通过哈希表快速定位到链表中的节点,从而取得对应 `val`。 + +3、链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 `key` 快速映射到任意一个链表节点,然后进行插入和删除。 -也许读者会问,为什么要是双向链表,单链表行不行?另外,既然哈希表中已经存了 key,为什么链表中还要存键值对呢,只存值不就行了? +**也许读者会问,为什么要是双向链表,单链表行不行?另外,既然哈希表中已经存了 `key`,为什么链表中还要存 `key` 和 `val` 呢,只存 `val` 不就行了**? 想的时候都是问题,只有做的时候才有答案。这样设计的原因,必须等我们亲自实现 LRU 算法之后才能理解,所以我们开始看代码吧~ -### 四、代码实现 +### 三、代码实现 -很多编程语言都有内置的哈希链表或者类似 LRU 功能的库函数,但是为了帮大家理解算法的细节,我们用 Java 自己造轮子实现一遍 LRU 算法。 +很多编程语言都有内置的哈希链表或者类似 LRU 功能的库函数,但是为了帮大家理解算法的细节,我们先自己造轮子实现一遍 LRU 算法,然后再使用 Java 内置的 `LinkedHashMap` 来实现一遍。 -首先,我们把双链表的节点类写出来,为了简化,key 和 val 都认为是 int 类型: +首先,我们把双链表的节点类写出来,为了简化,`key` 和 `val` 都认为是 int 类型: ```java class Node { @@ -95,63 +130,61 @@ class Node { } ``` -然后依靠我们的 Node 类型构建一个双链表,实现几个需要的 API(这些操作的时间复杂度均为 $O(1)$): +然后依靠我们的 `Node` 类型构建一个双链表,实现几个 LRU 算法必须的 API: ```java class DoubleList { - // 在链表头部添加节点 x,时间 O(1) - public void addFirst(Node x); + // 头尾虚节点 + private Node head, tail; + // 链表元素数 + private int size; + + public DoubleList() { + // 初始化双向链表的数据 + head = new Node(0, 0); + tail = new Node(0, 0); + head.next = tail; + tail.prev = head; + size = 0; + } + + // 在链表尾部添加节点 x,时间 O(1) + public void addLast(Node x) { + x.prev = tail.prev; + x.next = tail; + tail.prev.next = x; + tail.prev = x; + size++; + } // 删除链表中的 x 节点(x 一定存在) // 由于是双链表且给的是目标 Node 节点,时间 O(1) - public void remove(Node x); - - // 删除链表中最后一个节点,并返回该节点,时间 O(1) - public Node removeLast(); + public void remove(Node x) { + x.prev.next = x.next; + x.next.prev = x.prev; + size--; + } + // 删除链表中第一个节点,并返回该节点,时间 O(1) + public Node removeFirst() { + if (head.next == tail) + return null; + Node first = head.next; + remove(first); + return first; + } + // 返回链表长度,时间 O(1) - public int size(); + public int size() { return size; } + } ``` -PS:这就是普通双向链表的实现,为了让读者集中精力理解 LRU 算法的逻辑,就省略链表的具体代码。 - -到这里就能回答刚才“为什么必须要用双向链表”的问题了,因为我们需要删除操作。删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 $O(1)$。 - -有了双向链表的实现,我们只需要在 LRU 算法中把它和哈希表结合起来即可。我们先把逻辑理清楚: - -```java -// key 映射到 Node(key, val) -HashMap+ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/README.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/README.md" index 5c31a12770..3e9823392d 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/README.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/README.md" @@ -2,6 +2,6 @@ 8 说了,本章都是高频面试题,配合前面的动态规划系列,祝各位马到成功! -欢迎关注我的公众号 labuladong,方便获得最新的优质文章: +欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) \ No newline at end of file diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/koko\345\201\267\351\246\231\350\225\211.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/koko\345\201\267\351\246\231\350\225\211.md" index 13b32db912..c4cb83085e 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/koko\345\201\267\351\246\231\350\225\211.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/koko\345\201\267\351\246\231\350\225\211.md" @@ -1,5 +1,27 @@ # 如何运用二分查找算法 + + + +![](../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 ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/k\344\270\252\344\270\200\347\273\204\345\217\215\350\275\254\351\223\276\350\241\250.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/k\344\270\252\344\270\200\347\273\204\345\217\215\350\275\254\351\223\276\350\241\250.md" index a637312896..7509480a39 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/k\344\270\252\344\270\200\347\273\204\345\217\215\350\275\254\351\223\276\350\241\250.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/k\344\270\252\344\270\200\347\273\204\345\217\215\350\275\254\351\223\276\350\241\250.md" @@ -1,5 +1,25 @@ # 如何k个一组反转链表 + + + +![](../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 ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md" index 5f70d39d78..4ca0b6de4d 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\270\200\350\241\214\344\273\243\347\240\201\350\247\243\345\206\263\347\232\204\346\231\272\345\212\233\351\242\230.md" @@ -1,5 +1,29 @@ # 一行代码就能解决的算法题 + + + +![](../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 ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md" index 4c18690380..797c09836a 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\344\272\214\345\210\206\346\237\245\346\211\276\345\210\244\345\256\232\345\255\220\345\272\217\345\210\227.md" @@ -1,6 +1,26 @@ # 二分查找高效判定子序列 -二分查找本身不难理解,难在巧妙地运用二分查找技巧。对于一个问题,你可能都很难想到它跟二分查找有关,比如前文 [最长递增子序列](../动态规划系列/动态规划设计:最长递增子序列.md) 就借助一个纸牌游戏衍生出二分查找解法。 + + + +![](../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 ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\244\346\226\255\345\233\236\346\226\207\351\223\276\350\241\250.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\244\346\226\255\345\233\236\346\226\207\351\223\276\350\241\250.md" index 5a280b0d5d..d25823dd5d 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\244\346\226\255\345\233\236\346\226\207\351\223\276\350\241\250.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\244\346\226\255\345\233\236\346\226\207\351\223\276\350\241\250.md" @@ -1,3 +1,25 @@ +# 如何高效判断回文链表 + + + + +![](../pictures/souyisou.png) + +相关推荐: + * [如何高效进行模幂运算](https://labuladong.gitbook.io/algo) + * [一文学会递归解题](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[234.回文链表](https://leetcode-cn.com/problems/palindrome-linked-list) + +**-----------** + 我们之前有两篇文章写了回文串和回文序列相关的问题。 **寻找**回文串的核心思想是从中心向两端扩展: @@ -205,13 +227,12 @@ p.next = reverse(q); 具体到回文链表的判断问题,由于回文的特殊性,可以不完全反转链表,而是仅仅反转部分链表,将空间复杂度降到 O(1)。 -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) - +**_____________** -[上一篇:如何寻找缺失和重复的元素](../高频面试系列/缺失和重复的元素.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何在无限序列中随机抽取元素](../高频面试系列/水塘抽样.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) \ No newline at end of file ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\220\210\346\263\225\346\213\254\345\217\267\345\210\244\345\256\232.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\220\210\346\263\225\346\213\254\345\217\267\345\210\244\345\256\232.md" index f56d9fb1cd..ef9cd34f4c 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\220\210\346\263\225\346\213\254\345\217\267\345\210\244\345\256\232.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\220\210\346\263\225\346\213\254\345\217\267\345\210\244\345\256\232.md" @@ -1,5 +1,25 @@ # 如何判定括号合法性 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [回溯算法解题套路框架](https://labuladong.gitbook.io/algo) + * [Linux shell 的实用小技巧](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[20.有效的括号](https://leetcode-cn.com/problems/valid-parentheses) + +**-----------** + 对括号的合法性判断是一个很常见且实用的问题,比如说我们写的代码,编辑器和编译器都会检查括号是否正确闭合。而且我们的代码可能会包含三种括号 `[](){}`,判断起来有一点难度。 本文就来聊一道关于括号合法性判断的算法题,相信能加深你对**栈**这种数据结构的理解。 @@ -45,6 +65,7 @@ bool isValid(string str) { return left == 0; } ``` + 如果只有圆括号,这样就能正确判断合法性。对于三种括号的情况,我一开始想模仿这个思路,定义三个变量 `left1`,`left2`,`left3` 分别处理每种括号,虽然要多写不少 if else 分支,但是似乎可以解决问题。 但实际上直接照搬这种思路是不行的,比如说只有一个括号的情况下 `(())` 是合法的,但是多种括号的情况下, `[(])` 显然是不合法的。 @@ -81,50 +102,12 @@ char leftOf(char c) { } ``` -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) - -[张三](any_link_you_want) 提供 Java 代码: - -```java -public boolean isValid(String str) { - // ... -} - -private char leftOf(char c) { - // ... -} -``` - -[kalok87](https://github.com/kalok87) 提供 Python3 代码: - -```python -def isValid(self, s: str): - left = [] # 定义一个左栈,记录所有的左括号 - match = {'}':'{', ']':'[', ')':'('} # 定义一个字典,检查当前str是否是右括号 - right = {'}', ']', ')'} # 定义一个右括号集合,方便快速检查 - - # 进行循环,如果当前str是左括号,则入栈;如果是右括号,则检查左栈的最后一个元素是不是 - # 与其对应。 - for x in s: - if x in right: - if len(left) == 0 or match[x] != left[-1]: - return(False) # 如果对应的左栈元素不符(括号种类不同或左栈为空),返回False - else: - left.pop() # 移除左栈最顶端的元素 - else: - left.append(x) # 当前str是左括号,入左栈 - - if len(left) == 0: - return(True) # 如果左栈为空(左右括号数相等),返回True - else: - return(False) -``` - +**_____________** -[上一篇:如何k个一组反转链表](../高频面试系列/k个一组反转链表.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何寻找消失的元素](../高频面试系列/消失的元素.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" index 3e1cd14721..e159228312 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\246\202\344\275\225\345\216\273\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" @@ -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#目录) diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\255\220\351\233\206\346\216\222\345\210\227\347\273\204\345\220\210.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\255\220\351\233\206\346\216\222\345\210\227\347\273\204\345\220\210.md" index 41bff4bcae..0d5f905d4e 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\255\220\351\233\206\346\216\222\345\210\227\347\273\204\345\220\210.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\255\220\351\233\206\346\216\222\345\210\227\347\273\204\345\220\210.md" @@ -1,3 +1,30 @@ +# 回溯算法团灭子集、排列、组合问题 + +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [回溯算法解题套路框架](https://labuladong.gitbook.io/algo) + * [twoSum问题的核心思想](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[78.子集](https://leetcode-cn.com/problems/subsets) + +[46.全排列](https://leetcode-cn.com/problems/permutations) + +[77.组合](https://leetcode-cn.com/problems/combinations) + +**-----------** + 今天就来聊三道考察频率高,而且容易让人搞混的算法问题,分别是求子集(subset),求排列(permutation),求组合(combination)。 这几个问题都可以用回溯算法模板解决,同时子集问题还可以用数学归纳思想解决。读者可以记住这几个问题的回溯套路,就不怕搞不清了。 @@ -199,7 +226,7 @@ vector+ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\272\247\344\275\215\350\260\203\345\272\246.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\272\247\344\275\215\350\260\203\345\272\246.md" index 8360e672d7..f2b0306ac9 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\272\247\344\275\215\350\260\203\345\272\246.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\345\272\247\344\275\215\350\260\203\345\272\246.md" @@ -1,5 +1,25 @@ # 如何调度考生的座位 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [一个方法团灭 LeetCode 股票买卖问题](https://labuladong.gitbook.io/algo) + * [Linux shell 的实用小技巧](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[855.考场就座](https://leetcode-cn.com/problems/exam-room) + +**-----------** + 这是 LeetCode 第 855 题,有趣且具有一定技巧性。这种题目并不像动态规划这类算法拼智商,而是看你对常用数据结构的理解和写代码的水平,个人认为值得重视和学习。 另外说句题外话,很多读者都问,算法框架是如何总结出来的,其实框架反而是慢慢从细节里抠出来的。希望大家看了我们的文章之后,最好能抽时间把相关的问题亲自做一做,纸上得来终觉浅,绝知此事要躬行嘛。 @@ -203,13 +223,12 @@ private int distance(int[] intv) { 希望本文对大家有帮助。 -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) - +**_____________** -[上一篇:如何在无限序列中随机抽取元素](../高频面试系列/水塘抽样.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:Union-Find算法详解](../算法思维系列/UnionFind算法详解.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md" index 11420b478e..6ecb9c42f8 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\211\223\345\215\260\347\264\240\346\225\260.md" @@ -1,5 +1,25 @@ # 如何高效寻找素数 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [算法就像搭乐高:带你手撸 LRU 算法](https://labuladong.gitbook.io/algo) + * [区间调度之区间合并问题](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[204.计数质数](https://leetcode-cn.com/problems/count-primes) + +**-----------** + 素数的定义看起来很简单,如果一个数如果只能被 1 和它本身整除,那么这个数就是素数。 不要觉得素数的定义简单,恐怕没多少人真的能把素数相关的算法写得高效。比如让你写这样一个函数: @@ -146,35 +166,14 @@ int countPrimes(int n) { 以上就是素数算法相关的全部内容。怎么样,是不是看似简单的问题却有不少细节可以打磨呀? -**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**: - -![labuladong](../pictures/labuladong.png) - -[ruicore](https://github.com/ruicore/algorithm) 提供 Python3 代码: -```py -class Solution: - def countPrimes(self, n: int): - # 前 2 个数不是素数 - if n < 3: - return 0 - # isprime 数组 - isprime = [1] * n - for i in range(2, int(n ** 0.5) + 1): - if isprime[i]: - # 从 i*i(包括自己) 到 n(不包括n),每步增加 i 的所有数的个数 - tmp = ((n - 1 - i * i) // i + 1) - # 这种方式赋值比用 for 循环更高效 - isprime[i * i: n: i] = [0] * tmp - - # 前 2 个数不是素数,去掉 - count = sum(isprime[2:]) - return count -``` +**_____________** -[上一篇:如何实现LRU算法](../高频面试系列/LRU算法.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何计算编辑距离](../动态规划系列/编辑距离.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) \ No newline at end of file ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\216\245\351\233\250\346\260\264.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\216\245\351\233\250\346\260\264.md" index 3104e2198c..0b3e1e0515 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\216\245\351\233\250\346\260\264.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\216\245\351\233\250\346\260\264.md" @@ -1,5 +1,25 @@ # 接雨水问题详解 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo) + * [45张图解:IP基础知识全家桶](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[42.接雨水](https://leetcode-cn.com/problems/trapping-rain-water) + +**-----------** + 接雨水这道题目挺有意思,在面试题中出现频率还挺高的,本文就来步步优化,讲解一下这道题。 先看一下题目: @@ -16,17 +36,17 @@ int trap(int[] height); ### 一、核心思路 -我第一次看到这个问题,无计可施,完全没有思路,相信很多朋友跟我一样。所以对于这种问题,我们不要想整体,而应该去想局部;就像之前的文章处理字符串问题,不要考虑如何处理整个字符串,而是去思考应该如何处理每一个字符。 +所以对于这种问题,我们不要想整体,而应该去想局部;就像之前的文章写的动态规划问题处理字符串问题,不要考虑如何处理整个字符串,而是去思考应该如何处理每一个字符。 -这么一想,可以发现这道题的思路其实很简单。具体来说,仅仅对于位置 i,能装下多少水呢? +这么一想,可以发现这道题的思路其实很简单。具体来说,仅仅对于位置 `i`,能装下多少水呢? ![](../pictures/接雨水/0.jpg) -能装 2 格水。为什么恰好是两格水呢?因为 height[i] 的高度为 0,而这里最多能盛 2 格水,2-0=2。 +能装 2 格水,因为 `height[i]` 的高度为 0,而这里最多能盛 2 格水,2-0=2。 -为什么位置 i 最多能盛 2 格水呢?因为,位置 i 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为 `l_max` 和 `r_max`;**位置 i 最大的水柱高度就是 `min(l_max, r_max)`。** +为什么位置 `i` 最多能盛 2 格水呢?因为,位置 `i` 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为 `l_max` 和 `r_max`;**位置 i 最大的水柱高度就是 `min(l_max, r_max)`。** -更进一步,对于位置 i,能够装的水为: +更进一步,对于位置 `i`,能够装的水为: ```python water[i] = min( @@ -47,7 +67,7 @@ water[i] = min( ```cpp int trap(vector+ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" index b4ea0c5d8e..5fd2691130 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" @@ -1,5 +1,26 @@ # 如何寻找最长回文子串 +**学好算法全靠套路,认准 labuladong 就够了**。 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [如何运用二分查找算法](https://labuladong.gitbook.io/algo) + * [动态规划答疑篇](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[5.最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring) + +**-----------** + 回文串是面试常常遇到的问题(虽然问题本身没啥意义),本文就告诉你回文串问题的核心思想是什么。 首先,明确一下什:**回文串就是正着读和反着读都一样的字符串**。 @@ -99,12 +120,13 @@ string longestPalindrome(string s) { 另外,这个问题还有一个巧妙的解法,时间复杂度只需要 O(N),不过该解法比较复杂,我个人认为没必要掌握。该算法的名字叫 Manacher's Algorithm(马拉车算法),有兴趣的读者可以自行搜索一下。 -**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**: -![labuladong](../pictures/labuladong.png) +**_____________** -[上一篇:如何去除有序数组的重复元素](../高频面试系列/如何去除有序数组的重复元素.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何k个一组反转链表](../高频面试系列/k个一组反转链表.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) \ No newline at end of file ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\260\264\345\241\230\346\212\275\346\240\267.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\260\264\345\241\230\346\212\275\346\240\267.md" index 0897e48f59..a9cb9ecc8c 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\260\264\345\241\230\346\212\275\346\240\267.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\260\264\345\241\230\346\212\275\346\240\267.md" @@ -1,3 +1,27 @@ +# 随机算法之水塘抽样算法 + + + + +![](../pictures/souyisou.png) + +相关推荐: + * [一文看懂 session 和 cookie](https://labuladong.gitbook.io/algo) + * [算法就像搭乐高:带你手撸 LFU 算法](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[382.链表随机节点](https://leetcode-cn.com/problems/linked-list-random-node) + +[398.随机数索引](https://leetcode-cn.com/problems/random-pick-index) + +**-----------** + 我最近在 LeetCode 上做到两道非常有意思的题目,382 和 398 题,关于水塘抽样算法(Reservoir Sampling),本质上是一种随机概率算法,解法应该说会者不难,难者不会。 我第一次见到这个算法问题是谷歌的一道算法题:给你一个**未知长度**的链表,请你设计一个算法,**只能遍历一次**,随机地返回链表中的一个节点。 @@ -37,13 +61,15 @@ int getRandom(ListNode head) { **证明**:假设总共有 `n` 个元素,我们要的随机性无非就是每个元素被选择的概率都是 `1/n` 对吧,那么对于第 `i` 个元素,它被选择的概率就是: -$$ + + +![](../pictures/水塘抽样/formula1.png) 第 `i` 个元素被选择的概率是 `1/i`,第 `i+1` 次不被替换的概率是 `1 - 1/(i+1)`,以此类推,相乘就是第 `i` 个元素最终被选中的概率,就是 `1/n`。 @@ -81,14 +107,16 @@ int[] getRandom(ListNode head, int k) { 对于数学证明,和上面区别不大: -$$ + + +![](../pictures/水塘抽样/formula2.png) 因为虽然每次更新选择的概率增大了 `k` 倍,但是选到具体第 `i` 个元素的概率还是要乘 `1/k`,也就回到了上一个推导。 @@ -110,13 +138,12 @@ $$ 这两个问题都是比较困难的,以后有时间我会写一写相关的文章。 -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) - +**_____________** -[上一篇:如何判断回文链表](../高频面试系列/判断回文链表.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何调度考生的座位](../高频面试系列/座位调度.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) \ No newline at end of file ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md" index 01bdd63856..843e1153c3 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\346\266\210\345\244\261\347\232\204\345\205\203\347\264\240.md" @@ -1,5 +1,25 @@ # 如何寻找消失的元素 + + + +![](../pictures/souyisou.png) + +相关推荐: + * [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo) + * [回溯算法最佳实践:括号生成](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[448.找到所有数组中消失的数字](https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array) + +**-----------** + 之前也有文章写过几个有趣的智力题,今天再聊一道巧妙的题目。 题目非常简单: @@ -102,13 +122,12 @@ public int missingNumber(int[] nums) { 至此这道算法题目经历九曲十八弯,终于再也没有什么坑了。 -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) - +**_____________** -[上一篇:如何判定括号合法性](../高频面试系列/合法括号判定.md) +**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**。 -[下一篇:如何寻找缺失和重复的元素](../高频面试系列/缺失和重复的元素.md) +**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。 -[目录](../README.md#目录) \ No newline at end of file ++ +
diff --git "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\347\274\272\345\244\261\345\222\214\351\207\215\345\244\215\347\232\204\345\205\203\347\264\240.md" "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\347\274\272\345\244\261\345\222\214\351\207\215\345\244\215\347\232\204\345\205\203\347\264\240.md" index d541e839a2..117578856d 100644 --- "a/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\347\274\272\345\244\261\345\222\214\351\207\215\345\244\215\347\232\204\345\205\203\347\264\240.md" +++ "b/\351\253\230\351\242\221\351\235\242\350\257\225\347\263\273\345\210\227/\347\274\272\345\244\261\345\222\214\351\207\215\345\244\215\347\232\204\345\205\203\347\264\240.md" @@ -1,3 +1,25 @@ +# 如何寻找缺失和重复的元素 + + + + +![](../pictures/souyisou.png) + +相关推荐: + * [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo) + * [25 张图解:键入网址后,到网页显示,其间发生了什么](https://labuladong.gitbook.io/algo) + +读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目: + +[645.错误的集合](https://leetcode-cn.com/problems/set-mismatch) + +**-----------** + 今天就聊一道很看起来简单却十分巧妙的问题,寻找缺失和重复的元素。之前的一篇文章「寻找缺失元素」也写过类似的问题,不过这次的和上次的问题使用的技巧不同。 这是 LeetCode 645 题,我来描述一下这个题目: @@ -107,13 +129,12 @@ vector+ +