diff --git a/README.md b/README.md
index 0b3a5a3f..df397457 100644
--- a/README.md
+++ b/README.md
@@ -1,429 +1,421 @@
-
-
-
+## 一些闲话:
+
+> 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者)
+> 2. **PDF版本** : [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。
+> 3. **学习社区** : 一起学习打卡/面试技巧/如何选择offer/大厂内推/职场规则/简历修改/技术分享/程序人生。欢迎加入[「代码随想录」学习社区](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 。
+> 4. **转载须知** :以下所有文章皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
+
-
-
+
+
+
+
+
+
+
-
-
-目录:
-=================
-
-* [算法面试思维导图](#算法面试思维导图)
-* [B站算法视频讲解](#B站算法视频讲解)
-* [LeetCode 刷题攻略](#LeetCode-刷题攻略)
-* [算法文章精选](#算法文章精选)
-* [算法模板](#算法模板)
-* [LeetCode 最强题解](#LeetCode-最强题解)
-* [关于作者](#关于作者)
-
-# 算法面试思维导图
-
-
-
-# B站算法视频讲解
-
-* [KMP算法(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd)
-* [KMP算法(代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx)
-* [回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM)
-* [回溯算法之组合问题(力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv)
-* [组合问题的剪枝操作(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1wi4y157er)
-* [组合总和(对应力扣题目:39.组合总和)](https://www.bilibili.com/video/BV1KT4y1M7HJ/)
-
-(持续更新中....)
-
# LeetCode 刷题攻略
-> 不少同学和我反应LeetCode 刷题攻略这一栏没有了,首先感谢大家对本仓库的关注!因为我发现一些公众号抄袭我的Github,所以我把刷题攻略隐藏了,但是很多同学就看不到刷题顺序了,为了大家可以继续学习,我把算法精选文章的顺序整理了,和刷题攻略顺序一致的,**文章顺序即刷题顺序**,而且比刷题攻略更全!感谢大家的支持,**这个仓库我每天都会更新的**!
+## 刷题攻略的背景
-刷题顺序:建议先从同一类型里题目开始刷起,同一类型里再从简单到中等到困难刷起,题型顺序建议:**数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心->动态规划->图论**。
+很多刚开始刷题的同学都有一个困惑:面对leetcode上近两千道题目,从何刷起。
-目前大家可以按照下面的「算法文章精选」顺序来刷,里面都是各个类型的经典题目而且题目顺序都是精心设计的,**初学者可以按照这个顺序来刷题**,算法老手可以按照这个list查缺补漏!
+其实我之前在知乎上回答过这个问题,回答内容大概是按照如下类型来刷数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心->动态规划->图论->高级数据结构,再从简单刷起,做了几个类型题目之后,再慢慢做中等题目、困难题目。
-**同时这份刷题列表也在公众号[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)左下角的「算法汇总」里,方便大家用手机查看**,用手机看的好处可以看到每篇文章下都有很多录友(代码随想录的朋友们)的留言,录友会总结每篇文章的重点,如果文章有一些笔误的话,留言区也会及时纠正,所以**刷一下文章留言区会对理解知识点非常有帮助**!而且公众号更新要比Github早2-3天。
+但我能设身处地的感受到:即使有这样一个整体规划,对于一位初学者甚至算法老手寻找合适自己的题目也是很困难,时间成本很高,而且题目还不一定就是经典题目。
-**赶紧去公众号[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)里看看吧,你会发现相见恨晚!**
+对于刷题,我们都是想用最短的时间把经典题目都做一篇,这样效率才是最高的!
+
+所以我整理了leetcode刷题攻略:一个超级详细的刷题顺序,**每道题目都是我精心筛选,都是经典题目高频面试题**,大家只要按照这个顺序刷就可以了,**你没看错,就是题目顺序都排好了,文章顺序就是刷题顺序!挨个刷就可以,不用自己再去题海里选题了!**
+
+而且每道题目我都写了的详细题解(图文并茂,难点配有视频),力扣上我的题解都是排在对应题目的首页,质量是有目共睹的。
+
+**那么现在我把刷题顺序都整理出来,是为了帮助更多的学习算法的同学少走弯路!**
+
+如果你在刷leetcode,强烈建议先按照本攻略刷题顺序来刷,刷完了你会发现对整个知识体系有一个质的飞跃,不用在题海茫然的寻找方向。
+
+最新文章会首发在公众号「代码随想录」,扫码看看吧,你会发现相见恨晚!
+
+
+
+## 如何使用该刷题攻略
+
+电脑端还看不到留言,大家可以在公众号[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png),左下角有「刷题攻略」,这是手机版刷题攻略,看完就会发现有很多录友(代码随想录的朋友们)在文章下留言打卡,这份刷题顺序和题解已经陪伴了上万录友了,同时也说明文章的质量是经过上万人的考验!
+
+欢迎每一位学习算法的小伙伴加入到这个学习阵营来!
+
+**目前已经更新了,数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心,八个专题了,正在讲解动态规划!**
+
+在刷题指南中,每个专题开始都有理论基础篇,并不像是教科书般的理论介绍,而是从实战中归纳需要的基础知识。每个专题结束都有总结篇,最这个专题的归纳总结。
+
+如果你是算法老手,这篇攻略也是复习的最佳资料,如果把每个系列对应的总结篇,快速过一遍,整个算法知识体系以及各种解法就重现脑海了。
+
+在按照如下顺序刷题的过程中,每一道题解一定要看对应文章下面的留言(留言目前只能在手机端查看)。
+
+如果你有疑问或者发现文章哪里有不对的地方,都可以在留言区都能找到答案,还有很多录友的总结非常赞,看完之后也很有收获。
+
+目前「代码随想录」刷题指南更新了:**200多篇文章,精讲了200道经典算法题目,共60w字的详细图解,部分难点题目还搭配了20分钟左右的视频讲解**。
+
+准备好了么,刷题攻略开始咯,go go go!
+
+---------------------------------------------
+
+## 前序
+
+* [「代码随想录」后序安排](https://mp.weixin.qq.com/s/4eeGJREy6E-v6D7cR_5A4g)
+* [「代码随想录」学习社区](https://mp.weixin.qq.com/s/X1XCH-KevURi3LnakJsCkA)
-# 算法文章精选
* 编程语言
* [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP)
* 编程素养
- * [看了这么多代码,谈一谈代码风格!](https://mp.weixin.qq.com/s/UR9ztxz3AyL3qdHn_zMbqw)
+ * [看了这么多代码,谈一谈代码风格!](./problems/前序/代码风格.md)
+ * [力扣上的代码想在本地编译运行?](./problems/前序/力扣上的代码想在本地编译运行?.md)
+ * [什么是核心代码模式,什么又是ACM模式?](./problems/前序/什么是核心代码模式,什么又是ACM模式?.md)
+* 工具
+ * [一站式vim配置](https://github.com/youngyangyang04/PowerVim)
+ * [保姆级Git入门教程,万字详解](https://mp.weixin.qq.com/s/Q_O0ey4C9tryPZaZeJocbA)
+ * [程序员应该用什么用具来写文档?](./problems/前序/程序员写文档工具.md)
* 求职
- * [程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)
- * [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw)
- * [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)
- * [北京有这些互联网公司,你都知道么?]()
- * [上海有这些互联网公司,你都知道么?]()
- * [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ)
- * [广州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Ir_hQP0clbnvHrWzDL-qXg)
+ * [程序员的简历应该这么写!!(附简历模板)](./problems/前序/程序员简历.md)
+ * [BAT级别技术面试流程和注意事项都在这里了](./problems/前序/BAT级别技术面试流程和注意事项都在这里了.md)
+ * [北京有这些互联网公司,你都知道么?](./problems/前序/北京互联网公司总结.md)
+ * [上海有这些互联网公司,你都知道么?](./problems/前序/上海互联网公司总结.md)
+ * [深圳有这些互联网公司,你都知道么?](./problems/前序/深圳互联网公司总结.md)
+ * [广州有这些互联网公司,你都知道么?](./problems/前序/广州互联网公司总结.md)
+ * [成都有这些互联网公司,你都知道么?](./problems/前序/成都互联网公司总结.md)
+ * [杭州有这些互联网公司,你都知道么?](./problems/前序/杭州互联网公司总结.md)
-
* 算法性能分析
- * [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw)
- * [O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA)
- * [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)
- * [本周小结!(算法性能分析系列一)](https://mp.weixin.qq.com/s/5m8xDbGUeGgYJsESeg5ITQ)
+ * [关于时间复杂度,你不知道的都在这里!](./problems/前序/关于时间复杂度,你不知道的都在这里!.md)
+ * [O(n)的算法居然超时了,此时的n究竟是多大?](./problems/前序/On的算法居然超时了,此时的n究竟是多大?.md)
+ * [通过一道面试题目,讲一讲递归算法的时间复杂度!](./problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md)
+ * [本周小结!(算法性能分析系列一)](./problems/周总结/20201210复杂度分析周末总结.md)
+ * [关于空间复杂度,可能有几个疑问?](./problems/前序/关于空间复杂度,可能有几个疑问?.md)
+ * [递归算法的时间与空间复杂度分析!](./problems/前序/递归算法的时间与空间复杂度分析.md)
+ * [刷了这么多题,你了解自己代码的内存消耗么?](./problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md)
-* 数组
- * [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg)
- * [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)
- * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
- * [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg)
- * [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)
- * [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg)
-* 链表
- * [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)
- * [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)
- * [链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/Cf95Lc6brKL4g2j8YyF3Mg)
- * [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
- * [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
- * [链表:总结篇!](https://mp.weixin.qq.com/s/vK0JjSTHfpAbs8evz5hH8A)
+(持续更新中.....)
-* 哈希表
- * [关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)
- * [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)
- * [哈希表:哈希值太大了,还是得用set](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA)
- * [哈希表:今天你快乐了么?](https://mp.weixin.qq.com/s/G4Q2Zfpfe706gLK7HpZHpA)
- * [哈希表:map等候多时了](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)
- * [哈希表:其实需要哈希的地方都能找到map的身影](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)
- * [哈希表:这道题目我做过?](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ)
- * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
- * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
- * [哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg)
+## 数组
+
+1. [数组过于简单,但你该了解这些!](./problems/数组理论基础.md)
+2. [数组:每次遇到二分法,都是一看就会,一写就废](./problems/0704.二分查找.md)
+3. [数组:就移除个元素很难么?](./problems/0027.移除元素.md)
+4. [数组:滑动窗口拯救了你](./problems/0209.长度最小的子数组.md)
+5. [数组:这个循环可以转懵很多人!](./problems/0059.螺旋矩阵II.md)
+6. [数组:总结篇](./problems/数组总结篇.md)
+
+## 链表
+
+1. [关于链表,你该了解这些!](./problems/链表理论基础.md)
+2. [链表:听说用虚拟头节点会方便很多?](./problems/0203.移除链表元素.md)
+3. [链表:一道题目考察了常见的五个操作!](./problems/0707.设计链表.md)
+4. [链表:听说过两天反转链表又写不出来了?](./problems/0206.翻转链表.md)
+5. [链表:删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md)
+5. [链表:环找到了,那入口呢?](./problems/0142.环形链表II.md)
+6. [链表:总结篇!](./problems/链表总结篇.md)
+
+## 哈希表
+
+1. [关于哈希表,你该了解这些!](./problems/哈希表理论基础.md)
+2. [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](./problems/0242.有效的字母异位词.md)
+3. [哈希表:哈希值太大了,还是得用set](./problems/0349.两个数组的交集.md)
+4. [哈希表:用set来判断快乐数](./problems/0202.快乐数.md)
+5. [哈希表:map等候多时了](./problems/0001.两数之和.md)
+6. [哈希表:其实需要哈希的地方都能找到map的身影](./problems/0454.四数相加II.md)
+7. [哈希表:这道题目我做过?](./problems/0383.赎金信.md)
+8. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md)
+9. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md)
+10. [哈希表:总结篇!(每逢总结必经典)](./problems/哈希表总结.md)
-* 字符串
- * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
- * [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)
- * [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
- * [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
- * [字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)
- * [帮你把KMP算法学个通透!(理论篇)B站视频](https://www.bilibili.com/video/BV1PD4y1o7nd)
- * [帮你把KMP算法学个通透!(代码篇)B站视频](https://www.bilibili.com/video/BV1M5411j7Xx)
- * [字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)
- * [字符串:KMP算法还能干这个!](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ)
- * [字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw)
- * [字符串:总结篇!](https://mp.weixin.qq.com/s/gtycjyDtblmytvBRFlCZJg)
+## 字符串
-* 双指针法
- * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
- * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
- * [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
- * [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
- * [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
- * [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
- * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
- * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
- * [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)
+1. [字符串:这道题目,使用库函数一行代码搞定](./problems/0344.反转字符串.md)
+2. [字符串:简单的反转还不够!](./problems/0541.反转字符串II.md)
+3. [字符串:替换空格](./problems/剑指Offer05.替换空格.md)
+4. [字符串:花式反转还不够!](./problems/0151.翻转字符串里的单词.md)
+5. [字符串:反转个字符串还有这个用处?](./problems/剑指Offer58-II.左旋转字符串.md)
+6. [帮你把KMP算法学个通透](./problems/0028.实现strStr.md)
+8. [字符串:KMP算法还能干这个!](./problems/0459.重复的子字符串.md)
+9. [字符串:总结篇!](./problems/字符串总结.md)
-* 栈与队列
- * [栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)
- * [栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg)
- * [栈与队列:用队列实现栈还有点别扭](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw)
- * [栈与队列:系统中处处都是栈的应用](https://mp.weixin.qq.com/s/nLlmPMsDCIWSqAtr0jbrpQ)
- * [栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)
- * [栈与队列:有没有想过计算机是如何处理表达式的?](https://mp.weixin.qq.com/s/hneh2nnLT91rR8ms2fm_kw)
- * [栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)
- * [栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng)
- * [栈与队列:总结篇!](https://mp.weixin.qq.com/s/xBcHyvHlWq4P13fzxEtkPg)
+## 双指针法
-* 二叉树
- * [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)
- * [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
- * [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)
- * [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)
- * [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)
- * [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)
- * [本周小结!(二叉树)](https://mp.weixin.qq.com/s/JWmTeC7aKbBfGx4TY6uwuQ)
- * [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)
- * [二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)
- * [二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)
- * [二叉树:我有多少个节点?](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw)
- * [二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)
- * [二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)
- * [还在玩耍的你,该总结啦!(本周小结之二叉树)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg)
- * [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)
- * [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA)
- * [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)
- * [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)
- * [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
- * [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)
- * [本周小结!(二叉树系列三)](https://mp.weixin.qq.com/s/JLLpx3a_8jurXcz6ovgxtg)
- * [二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ)
- * [二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)
- * [二叉树:我是不是一棵二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)
- * [二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)
- * [二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)
- * [二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)
- * [本周小结!(二叉树系列四)](https://mp.weixin.qq.com/s/CbdtOTP0N-HIP7DR203tSg)
- * [二叉树:搜索树的公共祖先问题](https://mp.weixin.qq.com/s/Ja9dVw2QhBcg_vV-1fkiCg)
- * [二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)
- * [二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)
- * [二叉树:修剪一棵搜索树](https://mp.weixin.qq.com/s/QzmGfYUMUWGkbRj7-ozHoQ)
- * [二叉树:构造一棵搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw)
- * [二叉树:搜索树转成累加树](https://mp.weixin.qq.com/s/hZtJh4T5lIGBarY-lZJf6Q)
- * [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](https://mp.weixin.qq.com/s/-ZJn3jJVdF683ap90yIj4Q)
+双指针法基本都是应用在数组,字符串与链表的题目上
+
+1. [数组:就移除个元素很难么?](./problems/0027.移除元素.md)
+2. [字符串:这道题目,使用库函数一行代码搞定](./problems/0344.反转字符串.md)
+3. [字符串:替换空格](./problems/剑指Offer05.替换空格.md)
+4. [字符串:花式反转还不够!](./problems/0151.翻转字符串里的单词.md)
+5. [链表:听说过两天反转链表又写不出来了?](./problems/0206.翻转链表.md)
+6. [链表:环找到了,那入口呢?](./problems/0142.环形链表II.md)
+7. [链表:删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md)
+8. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md)
+9. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md)
+10. [双指针法:总结篇!](./problems/双指针总结.md)
+
+## 栈与队列
+
+1. [栈与队列:来看看栈和队列不为人知的一面](./problems/栈与队列理论基础.md)
+2. [栈与队列:我用栈来实现队列怎么样?](./problems/0232.用栈实现队列.md)
+3. [栈与队列:用队列实现栈还有点别扭](./problems/0225.用队列实现栈.md)
+4. [栈与队列:系统中处处都是栈的应用](./problems/0020.有效的括号.md)
+5. [栈与队列:匹配问题都是栈的强项](./problems/1047.删除字符串中的所有相邻重复项.md)
+6. [栈与队列:有没有想过计算机是如何处理表达式的?](./problems/0150.逆波兰表达式求值.md)
+7. [栈与队列:滑动窗口里求最大值引出一个重要数据结构](./problems/0239.滑动窗口最大值.md)
+8. [栈与队列:求前 K 个高频元素和队列有啥关系?](./problems/0347.前K个高频元素.md)
+9. [栈与队列:总结篇!](./problems/栈与队列总结.md)
+
+## 二叉树
+
+题目分类大纲如下:
+
+
+1. [关于二叉树,你该了解这些!](./problems/二叉树理论基础.md)
+2. [二叉树:一入递归深似海,从此offer是路人](./problems/二叉树的递归遍历.md)
+3. [二叉树:听说递归能做的,栈也能做!](./problems/二叉树的迭代遍历.md)
+4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](./problems/二叉树的统一迭代法.md)
+5. [二叉树:层序遍历登场!](./problems/0102.二叉树的层序遍历.md)
+6. [二叉树:你真的会翻转二叉树么?](./problems/0226.翻转二叉树.md)
+7. [本周小结!(二叉树)](./problems/周总结/20200927二叉树周末总结.md)
+8. [二叉树:我对称么?](./problems/0101.对称二叉树.md)
+9. [二叉树:看看这些树的最大深度](./problems/0104.二叉树的最大深度.md)
+10. [二叉树:看看这些树的最小深度](./problems/0111.二叉树的最小深度.md)
+11. [二叉树:我有多少个节点?](./problems/0222.完全二叉树的节点个数.md)
+12. [二叉树:我平衡么?](./problems/0110.平衡二叉树.md)
+13. [二叉树:找我的所有路径?](./problems/0257.二叉树的所有路径.md)
+14. [本周总结!二叉树系列二](./problems/周总结/20201003二叉树周末总结.md)
+15. [二叉树:以为使用了递归,其实还隐藏着回溯](./problems/二叉树中递归带着回溯.md)
+16. [二叉树:做了这么多题目了,我的左叶子之和是多少?](./problems/0404.左叶子之和.md)
+17. [二叉树:我的左下角的值是多少?](./problems/0513.找树左下角的值.md)
+18. [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](./problems/0112.路径总和.md)
+19. [二叉树:构造二叉树登场!](./problems/0106.从中序与后序遍历序列构造二叉树.md)
+20. [二叉树:构造一棵最大的二叉树](./problems/0654.最大二叉树.md)
+21. [本周小结!(二叉树系列三)](./problems/周总结/20201010二叉树周末总结.md)
+22. [二叉树:合并两个二叉树](./problems/0617.合并二叉树.md)
+23. [二叉树:二叉搜索树登场!](./problems/0700.二叉搜索树中的搜索.md)
+24. [二叉树:我是不是一棵二叉搜索树](./problems/0098.验证二叉搜索树.md)
+25. [二叉树:搜索树的最小绝对差](./problems/0530.二叉搜索树的最小绝对差.md)
+26. [二叉树:我的众数是多少?](./problems/0501.二叉搜索树中的众数.md)
+27. [二叉树:公共祖先问题](./problems/0236.二叉树的最近公共祖先.md)
+28. [本周小结!(二叉树系列四)](./problems/周总结/20201017二叉树周末总结.md)
+29. [二叉树:搜索树的公共祖先问题](./problems/0235.二叉搜索树的最近公共祖先.md)
+30. [二叉树:搜索树中的插入操作](./problems/0701.二叉搜索树中的插入操作.md)
+31. [二叉树:搜索树中的删除操作](./problems/0450.删除二叉搜索树中的节点.md)
+32. [二叉树:修剪一棵搜索树](./problems/0669.修剪二叉搜索树.md)
+33. [二叉树:构造一棵搜索树](./problems/0108.将有序数组转换为二叉搜索树.md)
+34. [二叉树:搜索树转成累加树](./problems/0538.把二叉搜索树转换为累加树.md)
+35. [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](./problems/二叉树总结篇.md)
-* 回溯算法
- * [关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)
- * [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)
- * [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)
- * [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)
- * [回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
- * [本周小结!(回溯算法系列一)](https://mp.weixin.qq.com/s/m2GnTJdkYhAamustbb6lmw)
- * [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)
- * [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)
- * [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)
- * [回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)
- * [回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)
- * [本周小结!(回溯算法系列二)](https://mp.weixin.qq.com/s/uzDpjrrMCO8DOf-Tl5oBGw)
- * [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)
- * [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)
- * [回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)
- * [回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)
- * [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)
- * [本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)
- * [视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg)
- * [视频来了!!回溯算法:组合问题](https://mp.weixin.qq.com/s/a_r5JR93K_rBKSFplPGNAA)
- * [视频来了!!回溯算法:组合问题的剪枝操作](https://mp.weixin.qq.com/s/CK0kj9lq8-rFajxL4amyEg)
- * [视频来了!!回溯算法:组合总和](https://mp.weixin.qq.com/s/4M4Cr04uFOWosRMc_5--gg)
- * [回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA)
- * [回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)
- * [回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)
- * [一篇总结带你彻底搞透回溯算法!](https://mp.weixin.qq.com/s/r73thpBnK1tXndFDtlsdCQ)
+## 回溯算法
-* 贪心算法
- * [关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)
- * [贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)
- * [贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)
- * [贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)
- * [本周小结!(贪心算法系列一)](https://mp.weixin.qq.com/s/KQ2caT9GoVXgB1t2ExPncQ)
- * [贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)
- * [贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)
- * [贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)
- * [贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)
- * [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ)
- * [贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)
- * [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)
- * [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)
+题目分类大纲如下:
+
-* 动态规划
+1. [关于回溯算法,你该了解这些!](./problems/回溯算法理论基础.md)
+2. [回溯算法:组合问题](./problems/0077.组合.md)
+3. [回溯算法:组合问题再剪剪枝](./problems/0077.组合优化.md)
+4. [回溯算法:求组合总和!](./problems/0216.组合总和III.md)
+5. [回溯算法:电话号码的字母组合](./problems/0017.电话号码的字母组合.md)
+6. [本周小结!(回溯算法系列一)](./problems/周总结/20201030回溯周末总结.md)
+7. [回溯算法:求组合总和(二)](./problems/0039.组合总和.md)
+8. [回溯算法:求组合总和(三)](./problems/0040.组合总和II.md)
+9. [回溯算法:分割回文串](./problems/0131.分割回文串.md)
+10. [回溯算法:复原IP地址](./problems/0093.复原IP地址.md)
+11. [回溯算法:求子集问题!](./problems/0078.子集.md)
+12. [本周小结!(回溯算法系列二)](./problems/周总结/20201107回溯周末总结.md)
+13. [回溯算法:求子集问题(二)](./problems/0090.子集II.md)
+14. [回溯算法:递增子序列](./problems/0491.递增子序列.md)
+15. [回溯算法:排列问题!](./problems/0046.全排列.md)
+16. [回溯算法:排列问题(二)](./problems/0047.全排列II.md)
+17. [本周小结!(回溯算法系列三)](./problems/周总结/20201112回溯周末总结.md)
+18. [回溯算法去重问题的另一种写法](./problems/回溯算法去重问题的另一种写法.md)
+23. [回溯算法:重新安排行程](./problems/0332.重新安排行程.md)
+24. [回溯算法:N皇后问题](./problems/0051.N皇后.md)
+25. [回溯算法:解数独](./problems/0037.解数独.md)
+26. [一篇总结带你彻底搞透回溯算法!](./problems/回溯总结.md)
-* 图论
+## 贪心算法
-* 数论
+题目分类大纲如下:
+
+
+
+1. [关于贪心算法,你该了解这些!](./problems/贪心算法理论基础.md)
+2. [贪心算法:分发饼干](./problems/0455.分发饼干.md)
+3. [贪心算法:摆动序列](./problems/0376.摆动序列.md)
+4. [贪心算法:最大子序和](./problems/0053.最大子序和.md)
+5. [本周小结!(贪心算法系列一)](./problems/周总结/20201126贪心周末总结.md)
+6. [贪心算法:买卖股票的最佳时机II](./problems/0122.买卖股票的最佳时机II.md)
+7. [贪心算法:跳跃游戏](./problems/0055.跳跃游戏.md)
+8. [贪心算法:跳跃游戏II](./problems/0045.跳跃游戏II.md)
+9. [贪心算法:K次取反后最大化的数组和](./problems/1005.K次取反后最大化的数组和.md)
+10. [本周小结!(贪心算法系列二)](./problems/周总结/20201203贪心周末总结.md)
+11. [贪心算法:加油站](./problems/0134.加油站.md)
+12. [贪心算法:分发糖果](./problems/0135.分发糖果.md)
+13. [贪心算法:柠檬水找零](./problems/0860.柠檬水找零.md)
+14. [贪心算法:根据身高重建队列](./problems/0406.根据身高重建队列.md)
+15. [本周小结!(贪心算法系列三)](./problems/周总结/20201217贪心周末总结.md)
+16. [贪心算法:根据身高重建队列(续集)](./problems/根据身高重建队列(vector原理讲解).md)
+17. [贪心算法:用最少数量的箭引爆气球](./problems/0452.用最少数量的箭引爆气球.md)
+18. [贪心算法:无重叠区间](./problems/0435.无重叠区间.md)
+19. [贪心算法:划分字母区间](./problems/0763.划分字母区间.md)
+20. [贪心算法:合并区间](./problems/0056.合并区间.md)
+21. [本周小结!(贪心算法系列四)](./problems/周总结/20201224贪心周末总结.md)
+22. [贪心算法:单调递增的数字](./problems/0738.单调递增的数字.md)
+23. [贪心算法:买卖股票的最佳时机含手续费](./problems/0714.买卖股票的最佳时机含手续费.md)
+24. [贪心算法:我要监控二叉树!](./problems/0968.监控二叉树.md)
+25. [贪心算法:总结篇!(每逢总结必经典)](./problems/贪心算法总结篇.md)
+
+## 动态规划
+
+动态规划专题已经开始啦,来不及解释了,小伙伴们上车别掉队!
+
+1. [关于动态规划,你该了解这些!](./problems/动态规划理论基础.md)
+2. [动态规划:斐波那契数](./problems/0509.斐波那契数.md)
+3. [动态规划:爬楼梯](./problems/0070.爬楼梯.md)
+4. [动态规划:使用最小花费爬楼梯](./problems/0746.使用最小花费爬楼梯.md)
+5. [本周小结!(动态规划系列一)](./problems/周总结/20210107动规周末总结.md)
+6. [动态规划:不同路径](./problems/0062.不同路径.md)
+7. [动态规划:不同路径还不够,要有障碍!](./problems/0063.不同路径II.md)
+8. [动态规划:整数拆分,你要怎么拆?](./problems/0343.整数拆分.md)
+9. [动态规划:不同的二叉搜索树](./problems/0096.不同的二叉搜索树.md)
+10. [本周小结!(动态规划系列二)](./problems/周总结/20210114动规周末总结.md)
+
+背包问题系列:
+
+
+
+11. [动态规划:关于01背包问题,你该了解这些!](./problems/背包理论基础01背包-1.md)
+12. [动态规划:关于01背包问题,你该了解这些!(滚动数组)](./problems/背包理论基础01背包-2.md)
+13. [动态规划:分割等和子集可以用01背包!](./problems/0416.分割等和子集.md)
+14. [动态规划:最后一块石头的重量 II](./problems/1049.最后一块石头的重量II.md)
+15. [本周小结!(动态规划系列三)](./problems/周总结/20210121动规周末总结.md)
+16. [动态规划:目标和!](./problems/0494.目标和.md)
+17. [动态规划:一和零!](./problems/0474.一和零.md)
+18. [动态规划:关于完全背包,你该了解这些!](./problems/背包问题理论基础完全背包.md)
+19. [动态规划:给你一些零钱,你要怎么凑?](./problems/0518.零钱兑换II.md)
+20. [本周小结!(动态规划系列四)](./problems/周总结/20210128动规周末总结.md)
+21. [动态规划:Carl称它为排列总和!](./problems/0377.组合总和Ⅳ.md)
+22. [动态规划:以前我没得选,现在我选择再爬一次!](./problems/0070.爬楼梯完全背包版本.md)
+23. [动态规划: 给我个机会,我再兑换一次零钱](./problems/0322.零钱兑换.md)
+24. [动态规划:一样的套路,再求一次完全平方数](./problems/0279.完全平方数.md)
+25. [本周小结!(动态规划系列五)](./problems/周总结/20210204动规周末总结.md)
+26. [动态规划:单词拆分](./problems/0139.单词拆分.md)
+27. [动态规划:关于多重背包,你该了解这些!](./problems/背包问题理论基础多重背包.md)
+28. [听说背包问题很难? 这篇总结篇来拯救你了](./problems/背包总结篇.md)
+
+打家劫舍系列:
+
+29. [动态规划:开始打家劫舍!](./problems/0198.打家劫舍.md)
+30. [动态规划:继续打家劫舍!](./problems/0213.打家劫舍II.md)
+31. [动态规划:还要打家劫舍!](./problems/0337.打家劫舍III.md)
+
+股票系列:
+
+
+
+32. [动态规划:买卖股票的最佳时机](./problems/0121.买卖股票的最佳时机.md)
+33. [动态规划:本周我们都讲了这些(系列六)](./problems/周总结/20210225动规周末总结.md)
+33. [动态规划:买卖股票的最佳时机II](./problems/0122.买卖股票的最佳时机II(动态规划).md)
+34. [动态规划:买卖股票的最佳时机III](./problems/0123.买卖股票的最佳时机III.md)
+35. [动态规划:买卖股票的最佳时机IV](./problems/0188.买卖股票的最佳时机IV.md)
+36. [动态规划:最佳买卖股票时机含冷冻期](./problems/0309.最佳买卖股票时机含冷冻期.md)
+37. [动态规划:本周我们都讲了这些(系列七)](./problems/周总结/20210304动规周末总结.md)
+38. [动态规划:买卖股票的最佳时机含手续费](./problems/0714.买卖股票的最佳时机含手续费(动态规划).md)
+39. [动态规划:股票系列总结篇](./problems/动态规划-股票问题总结篇.md)
+
+子序列系列:
+
+40. [动态规划:最长递增子序列](./problems/0300.最长上升子序列.md)
+41. [动态规划:最长连续递增序列](./problems/0674.最长连续递增序列.md)
+42. [动态规划:最长重复子数组](./problems/0718.最长重复子数组.md)
+43. [动态规划:最长公共子序列](./problems/1143.最长公共子序列.md)
+45. [动态规划:不相交的线](./problems/1035.不相交的线.md)
+46. [动态规划:最大子序和](./problems/0053.最大子序和(动态规划).md)
+47. [动态规划:判断子序列](./problems/0392.判断子序列.md)
+48. [动态规划:不同的子序列](./problems/0115.不同的子序列.md)
+49. [动态规划:两个字符串的删除操作](./problems/0583.两个字符串的删除操作.md)
+51. [动态规划:编辑距离](./problems/0072.编辑距离.md)
+52. [为了绝杀编辑距离,Carl做了三步铺垫,你都知道么?](./problems/为了绝杀编辑距离,卡尔做了三步铺垫.md)
+53. [动态规划:回文子串](./problems/0647.回文子串.md)
+54. [动态规划:最长回文子序列](./problems/0516.最长回文子序列.md)
-* 高级数据结构经典题目
- * 并查集
- * 最小生成树
- * 线段树
- * 树状数组
- * 字典树
-* 海量数据处理
(持续更新中....)
+## 图论
+
+## 十大排序
+
+## 数论
+
+## 高级数据结构经典题目
+
+* 并查集
+* 最小生成树
+* 线段树
+* 树状数组
+* 字典树
+
+## 海量数据处理
# 算法模板
[各类基础算法模板](https://github.com/youngyangyang04/leetcode/blob/master/problems/算法模板.md)
-# LeetCode 最强题解:
+# 备战秋招
-|题目 | 类型 | 难度 | 解题方法 |
-|---|---| ---| --- |
-|[0001.两数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0001.两数之和.md) | 数组|简单|**暴力** **哈希**|
-|[0015.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md) | 数组 |中等|**双指针** **哈希**|
-|[0017.电话号码的字母组合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0017.电话号码的字母组合.md) | 回溯 |中等|**回溯**|
-|[0018.四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md) | 数组 |中等|**双指针**|
-|[0019.删除链表的倒数第N个节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0019.删除链表的倒数第N个节点.md) | 链表 |中等|**双指针**|
-|[0020.有效的括号](https://github.com/youngyangyang04/leetcode/blob/master/problems/0020.有效的括号.md) | 栈 |简单|**栈**|
-|[0021.合并两个有序链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0021.合并两个有序链表.md) |链表 |简单|**模拟** |
-|[0024.两两交换链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0024.两两交换链表中的节点.md) |链表 |中等|**模拟** |
-|[0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md) |数组 |简单|**暴力** **快慢指针/快慢指针** |
-|[0027.移除元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0027.移除元素.md) |数组 |简单| **暴力** **双指针/快慢指针/双指针**|
-|[0028.实现strStr()](https://github.com/youngyangyang04/leetcode/blob/master/problems/0028.实现strStr().md) |字符串 |简单| **KMP** |
-|[0031.下一个排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0031.下一个排列.md) |数组 |中等| **模拟** 这道题目还是有难度的|
-|[0034.在排序数组中查找元素的第一个和最后一个位置](https://github.com/youngyangyang04/leetcode/blob/master/problems/0031.下一个排列.md) |数组 |中等| **二分查找**比35.搜索插入位置难一些|
-|[0035.搜索插入位置](https://github.com/youngyangyang04/leetcode/blob/master/problems/0035.搜索插入位置.md) |数组 |简单| **暴力** **二分**|
-|[0037.解数独](https://github.com/youngyangyang04/leetcode/blob/master/problems/0037.解数独.md) |回溯 |困难| **回溯**|
-|[0039.组合总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0039.组合总和.md) |数组/回溯 |中等| **回溯**|
-|[0040.组合总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0040.组合总和II.md) |数组/回溯 |中等| **回溯**|
-|[0042.接雨水](https://github.com/youngyangyang04/leetcode/blob/master/problems/0042.接雨水.md) |数组/栈/双指针 |困难| **双指针** **单调栈** **动态规划**|
-|[0045.跳跃游戏II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0045.跳跃游戏II.md) |贪心 |困难| **贪心**|
-|[0046.全排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0046.全排列.md) |回溯|中等| **回溯**|
-|[0047.全排列II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0047.全排列II.md) |回溯|中等| **回溯**|
-|[0051.N皇后](https://github.com/youngyangyang04/leetcode/blob/master/problems/0051.N皇后.md) |回溯|困难| **回溯**|
-|[0052.N皇后II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0052.N皇后II.md) |回溯|困难| **回溯**|
-|[0053.最大子序和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |简单|**暴力** **贪心** 动态规划 分治|
-|[0055.跳跃游戏](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |中等| **贪心** 经典题目|
-|[0056.合并区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0056.合并区间.md) |数组 |中等| **贪心** 以为是模拟题,其实是贪心|
-|[0057.插入区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0057.插入区间.md) |数组 |困难| **模拟** 是一道数组难题|
-|[0059.螺旋矩阵II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0059.螺旋矩阵II.md) |数组 |中等|**模拟**|
-|[0062.不同路径](https://github.com/youngyangyang04/leetcode/blob/master/problems/0062.不同路径.md) |数组、动态规划 |中等|**深搜** **动态规划** **数论**|
-|[0070.爬楼梯](https://github.com/youngyangyang04/leetcode/blob/master/problems/0070.爬楼梯.md) |动态规划|简单|**动态规划** dp里求排列|
-|[0077.组合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0077.组合.md) |回溯 |中等|**回溯**|
-|[0078.子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0078.子集.md) |回溯/数组 |中等|**回溯**|
-|[0083.删除排序链表中的重复元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0083.删除排序链表中的重复元素.md) |链表 |简单|**模拟**|
-|[0084.柱状图中最大的矩形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0084.柱状图中最大的矩形.md) |数组 |困难|**单调栈**|
-|[0090.子集II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0090.子集II.md) |回溯/数组 |中等|**回溯**|
-|[0093.复原IP地址](https://github.com/youngyangyang04/leetcode/blob/master/problems/0093.复原IP地址) |回溯 |中等|**回溯**|
-|[0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md) |树 |中等|**递归** **迭代/栈**|
-|[0098.验证二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0098.验证二叉搜索树.md) |树 |中等|**递归**|
-|[0100.相同的树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0100.相同的树.md) |树 |简单|**递归** |
-|[0101.对称二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0101.对称二叉树.md) |树 |简单|**递归** **迭代/队列/栈** 和100. 相同的树 相似|
-|[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md) |树 |中等|**广度优先搜索/队列**|
-|[0104.二叉树的最大深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0104.二叉树的最大深度.md) |树 |简单|**递归** **迭代/队列/BFS**|
-|[0105.从前序与中序遍历序列构造二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0105.从前序与中序遍历序列构造二叉树.md) |二叉树 |中等|**递归**|
-|[0106.从中序与后序遍历序列构造二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0106.从中序与后序遍历序列构造二叉树.md) |二叉树 |中等|**递归** 根据数组构造二叉树|
-|[0107.二叉树的层次遍历II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0107.二叉树的层次遍历II.md) |树 |简单|**广度优先搜索/队列/BFS**|
-|[0108.将有序数组转换为二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0108.将有序数组转换为二叉搜索树.md) |二叉搜索树 |中等|**递归** **迭代** 通过递归函数返回值构造树|
-|[0110.平衡二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0110.平衡二叉树.md) |二叉树 |简单|**递归**|
-|[0111.二叉树的最小深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0111.二叉树的最小深度.md) |二叉树 |简单|**递归** **队列/BFS**|
-|[0112.路径总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0112.路径总和.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** **栈** 思考递归函数什么时候需要返回值|
-|[0113.路径总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0113.路径总和II.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** **栈**|
-|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**|
-|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**|
-|[0122.买卖股票的最佳时机II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0122.买卖股票的最佳时机II.md) |贪心 |简单|**贪心** |
-|[0127.单词接龙](https://github.com/youngyangyang04/leetcode/blob/master/problems/0127.单词接龙.md) |广度优先搜索 |中等|**广度优先搜索**|
-|[0129.求根到叶子节点数字之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0129.求根到叶子节点数字之和.md) |二叉树 |中等|**递归/回溯** 递归里隐藏着回溯,和113.路径总和II类似|
-|[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**|
-|[0135.分发糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0135.分发糖果.md) |贪心 |困难|**贪心**好题目|
-|[0139.单词拆分](https://github.com/youngyangyang04/leetcode/blob/master/problems/0139.单词拆分.md) |动态规划 |中等|**完全背包** **回溯法**|
-|[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**|
-|[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**|
-|[0143.重排链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0143.重排链表.md) |链表 |中等|**快慢指针/双指针** 也可以用数组,双向队列模拟,考察链表综合操作的好题|
-|[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**|
-|[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**|
-|[0147.对链表进行插入排序](https://github.com/youngyangyang04/leetcode/blob/master/problems/0147.对链表进行插入排序.md) |链表 |中等|**模拟** 考察链表综合操作|
-|[0150.逆波兰表达式求值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0150.逆波兰表达式求值.md) |栈 |中等|**栈**|
-|[0151.翻转字符串里的单词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0151.翻转字符串里的单词.md) |字符串 |中等|**模拟/双指针**|
-|[0155.最小栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0155.最小栈.md) |栈 |简单|**栈**|
-|[0199.二叉树的右视图](https://github.com/youngyangyang04/leetcode/blob/master/problems/0199.二叉树的右视图.md) |二叉树 |中等|**广度优先遍历/队列**|
-|[0202.快乐数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0202.快乐数.md) |哈希表 |简单|**哈希**|
-|[0203.移除链表元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0203.移除链表元素.md) |链表 |简单|**模拟** **虚拟头结点**|
-|[0205.同构字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0205.同构字符串.md) |哈希表 |简单| **哈希**|
-|[0206.翻转链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0206.翻转链表.md) |链表 |简单| **双指针法** **递归**|
-|[0209.长度最小的子数组](https://github.com/youngyangyang04/leetcode/blob/master/problems/0209.长度最小的子数组.md) |数组 |中等| **暴力** **滑动窗口**|
-|[0216.组合总和III](https://github.com/youngyangyang04/leetcode/blob/master/problems/0216.组合总和III.md) |数组/回溯 |中等| **回溯算法**|
-|[0219.存在重复元素II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0219.存在重复元素II.md) | 哈希表 |简单| **哈希** |
-|[0222.完全二叉树的节点个数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0222.完全二叉树的节点个数.md) | 树 |简单| **递归** |
-|[0225.用队列实现栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0225.用队列实现栈.md) | 队列 |简单| **队列** |
-|[0226.翻转二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0226.翻转二叉树.md) |二叉树 |简单| **递归** **迭代**|
-|[0232.用栈实现队列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0232.用栈实现队列.md) | 栈 |简单| **栈** |
-|[0235.二叉搜索树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0235.二叉搜索树的最近公共祖先.md) | 二叉搜索树 |简单| **递归** **迭代** |
-|[0236.二叉树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0236.二叉树的最近公共祖先.md) | 二叉树 |中等| **递归/回溯** 与其说是递归,不如说是回溯|
-|[0237.删除链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0237.删除链表中的节点.md) |链表 |简单| **原链表移除** **添加虚拟节点** 递归|
-|[0239.滑动窗口最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0239.滑动窗口最大值.md) |滑动窗口/队列 |困难| **单调队列**|
-|[0242.有效的字母异位词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0242.有效的字母异位词.md) |哈希表 |简单| **哈希**|
-|[0257.二叉树的所有路径](https://github.com/youngyangyang04/leetcode/blob/master/problems/0257.二叉树的所有路径.md) |树 |简单| **递归/回溯**|
-|[0283.移动零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0283.移动零.md) |数组 |简单| **双指针** 和 27.移除元素 一个套路|
-|[0300.最长上升子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0300.最长上升子序列.md) |动态规划 |中等| **动态规划**|
-|[0316.去除重复字母](https://github.com/youngyangyang04/leetcode/blob/master/problems/0316.去除重复字母.md) |贪心/字符串 |中等| **单调栈** 这道题目处理的情况比较多,属于单调栈中的难题|
-|[0332.重新安排行程](https://github.com/youngyangyang04/leetcode/blob/master/problems/0332.重新安排行程.md) |深度优先搜索/回溯 |中等| **深度优先搜索/回溯算法**|
-|[0343.整数拆分](https://github.com/youngyangyang04/leetcode/blob/master/problems/0343.整数拆分.md) |动态规划/贪心 |中等| **动态规划**|
-|[0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) |字符串 |简单| **双指针**|
-|[0347.前K个高频元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0347.前K个高频元素.md) |哈希/堆/优先级队列 |中等| **哈希/优先级队列**|
-|[0349.两个数组的交集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0349.两个数组的交集.md) |哈希表 |简单|**哈希**|
-|[0350.两个数组的交集II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0350.两个数组的交集II.md) |哈希表 |简单|**哈希**|
-|[0377.组合总和Ⅳ](https://github.com/youngyangyang04/leetcode/blob/master/problems/0377.组合总和Ⅳ.md) |动态规划 |中等|**完全背包** 求排列|
-|[0383.赎金信](https://github.com/youngyangyang04/leetcode/blob/master/problems/0383.赎金信.md) |数组 |简单|**暴力** **字典计数** **哈希**|
-|[0404.左叶子之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0404.左叶子之和.md) |树/二叉树 |简单|**递归** **迭代**|
-|[0406.根据身高重建队列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0406.根据身高重建队列.md) |树/二叉树 |简单|**递归** **迭代**|
-|[0416.分割等和子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0416.分割等和子集.md) |动态规划 |中等|**背包问题/01背包**|
-|[0429.N叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0429.N叉树的层序遍历.md) |树 |简单|**队列/广度优先搜索**|
-|[0434.字符串中的单词数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0434.字符串中的单词数.md) |字符串 |简单|**模拟**|
-|[0435.无重叠区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0435.无重叠区间.md) |贪心 |中等|**贪心** 经典题目,有点难|
-|[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**|
-|[0452.用最少数量的箭引爆气球](https://github.com/youngyangyang04/leetcode/blob/master/problems/0452.用最少数量的箭引爆气球.md) |贪心/排序 |中等|**贪心** 经典题目|
-|[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**|
-|[0455.分发饼干](https://github.com/youngyangyang04/leetcode/blob/master/problems/0455.分发饼干.md) |贪心 |简单| **贪心**|
-|[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**|
-|[0473.火柴拼正方形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0473.火柴拼正方形.md) |深度优先搜索|中等| **回溯算法** 和698.划分为k个相等的子集差不多|
-|[0474.一和零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0474.一和零.md) |动态规划 |中等| **多重背包** 好题目|
-|[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**|
-|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法** 这个去重有意思|
-|[0496.下一个更大元素I](https://github.com/youngyangyang04/leetcode/blob/master/problems/0496.下一个更大元素I.md) |栈 |中等|**单调栈** 入门题目,但是两个数组还是有点绕的|
-|[0501.二叉搜索树中的众数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0501.二叉搜索树中的众数.md) |二叉树 |简单|**递归/中序遍历**|
-|[0513.找树左下角的值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0513.找树左下角的值.md) |二叉树 |中等|**递归** **迭代**|
-|[0515.在每个树行中找最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0515.在每个树行中找最大值.md) |二叉树 |简单|**广度优先搜索/队列**|
-|[0518.零钱兑换II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0518.零钱兑换II.md) |动态规划 |中等|**动态规划** dp里求组合|
-|[0530.二叉搜索树的最小绝对差](https://github.com/youngyangyang04/leetcode/blob/master/problems/0530.二叉搜索树的最小绝对差.md) |二叉树搜索树 |简单|**递归** **迭代**|
-|[0538.把二叉搜索树转换为累加树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0538.把二叉搜索树转换为累加树.md) |二叉搜索树 |简单|**递归** **迭代**|
-|[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**|
-|[0559.N叉树的最大深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0559.N叉树的最大深度.md) |N叉树 |简单| **递归**|
-|[0572.另一个树的子树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0572.另一个树的子树.md) |二叉树 |简单| **递归**|
-|[0575.分糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0575.分糖果.md) |哈希表 |简单|**哈希**|
-|[0589.N叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0589.N叉树的前序遍历.md) |N叉树 |简单|**递归** **栈/迭代**|
-|[0590.N叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0590.N叉树的后序遍历.md) |N叉树 |简单|**递归** **栈/迭代**|
-|[0617.合并二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0617.合并二叉树.md) |树 |简单|**递归** **迭代**|
-|[0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md) |树 |简单|**广度优先搜索/队列**|
-|[0649.Dota2参议院](https://github.com/youngyangyang04/leetcode/blob/master/problems/0649.Dota2参议院.md) |贪心 |简单|**贪心算法** 简单的贪心策略但代码实现很有技巧|
-|[0654.最大二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0654.最大二叉树.md) |树 |中等|**递归**|
-|[0685.冗余连接II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0685.冗余连接II.md) | 并查集/树/图 |困难|**并查集**|
-|[0669.修剪二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0669.修剪二叉搜索树.md) | 二叉搜索树/二叉树 |简单|**递归** **迭代**|
-|[0698.划分为k个相等的子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0698.划分为k个相等的子集.md) |回溯算法|中等|动态规划 **回溯算法** 这其实是组合问题,使用了两次递归,好题|
-|[0700.二叉搜索树中的搜索](https://github.com/youngyangyang04/leetcode/blob/master/problems/0700.二叉搜索树中的搜索.md) |二叉搜索树 |简单|**递归** **迭代**|
-|[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |二叉搜索树 |简单|**递归** **迭代**|
-|[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**|
-|[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**|
-|[0714.买卖股票的最佳时机含手续费](https://github.com/youngyangyang04/leetcode/blob/master/problems/0714.买卖股票的最佳时机含手续费.md) |贪心 动态规划 |中等|**贪心** **动态规划** 和122.买卖股票的最佳时机II类似,贪心的思路很巧妙|
-|[0763.划分字母区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0763.划分字母区间.md) |贪心 |中等|**双指针/贪心** 体现贪心尽可能多的思想|
-|[0738.单调递增的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/0738.单调递增的数字.md) |贪心算法 |中等|**贪心算法** 思路不错,贪心好题|
-|[0739.每日温度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0739.每日温度.md) |栈 |中等|**单调栈** 适合单调栈入门|
-|[0767.重构字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0767.重构字符串.md) |字符串 |中等|**字符串** + 排序+一点贪心|
-|[0841.钥匙和房间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0841.钥匙和房间.md) |孤岛问题 |中等|**bfs** **dfs**|
-|[0844.比较含退格的字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0844.比较含退格的字符串.md) |字符串 |简单|**栈** **双指针优化** 使用栈的思路但没有必要使用栈|
-|[0860.柠檬水找零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0860.柠檬水找零.md) |贪心算法 |简单|**贪心算法** 基础题目|
-|[0925.长按键入](https://github.com/youngyangyang04/leetcode/blob/master/problems/0925.长按键入.md) |字符串 |简单|**双指针/模拟** 是一道模拟类型的题目|
-|[0941.有效的山脉数组](https://github.com/youngyangyang04/leetcode/blob/master/problems/0941.有效的山脉数组.md) |数组 |简单|**双指针**|
-|[0968.监控二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0968.监控二叉树.md) |二叉树 |困难|**贪心** 贪心与二叉树的结合|
-|[0973.最接近原点的K个点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0973.最接近原点的K个点.md) |优先级队列 |中等|**优先级队列**|
-|[0977.有序数组的平方](https://github.com/youngyangyang04/leetcode/blob/master/problems/0977.有序数组的平方.md) |数组 |中等|**双指针** 还是比较巧妙的|
-|[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**|
-|[1005.K次取反后最大化的数组和](https://github.com/youngyangyang04/leetcode/blob/master/problems/1005.K次取反后最大化的数组和.md) |贪心/排序 |简单|**贪心算法** 贪心基础题目|
-|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**|
-|[1049.最后一块石头的重量II](https://github.com/youngyangyang04/leetcode/blob/master/problems/1049.最后一块石头的重量II.md) |动态规划 |中等|**01背包**|
-|[1207.独一无二的出现次数](https://github.com/youngyangyang04/leetcode/blob/master/problems/1207.独一无二的出现次数.md) |哈希表 |简单|**哈希** 两层哈希|
-|[1221.分割平衡字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/1221.分割平衡字符串.md) |贪心 |简单|**贪心算法** 基础题目|
-|[1356.根据数字二进制下1的数目排序](https://github.com/youngyangyang04/leetcode/blob/master/problems/1356.根据数字二进制下1的数目排序.md) |位运算 |简单|**位运算** 巧妙的计算二进制中1的数量|
-|[1365.有多少小于当前数字的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/1365.有多少小于当前数字的数字.md) |数组、哈希表 |简单|**哈希** 从后遍历的技巧很不错|
-|[1382.将二叉搜索树变平衡](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |二叉搜索树 |中等|**递归** **迭代** 98和108的组合题目|
-|[1403.非递增顺序的最小子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/1403.非递增顺序的最小子序列.md) | 贪心算法|简单|**贪心算法** 贪心基础题目|
-|[1518.换酒问题](https://github.com/youngyangyang04/leetcode/blob/master/problems/1518.换酒问题.md) | 贪心算法|简单|**贪心算法** 贪心基础题目|
-|[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**|
-|[ 剑指Offer58-I.翻转单词顺序](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**模拟/双指针**|
-|[剑指Offer58-II.左旋转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer58-II.左旋转字符串.md) |字符串 |简单|**反转操作**|
-|[剑指Offer59-I.滑动窗口的最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer59-I.滑动窗口的最大值.md) |滑动窗口/队列 |困难|**单调队列**|
-|[面试题02.07.链表相交](https://github.com/youngyangyang04/leetcode/blob/master/problems/面试题02.07.链表相交.md) |链表 |简单|**模拟**|
+1. [技术比较弱,也对技术不感兴趣,如何选择方向?](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g)
+2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg)
+3. [关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA)
+4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA)
-持续更新中....
+# B站算法视频讲解
+
+以下为[B站「代码随想录」](https://space.bilibili.com/525438321)算法讲解视频:
+
+* [KMP算法(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd)
+* [KMP算法(代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx)
+* [回溯算法理论基础](https://www.bilibili.com/video/BV1cy4y167mM)
+* [回溯算法之组合问题(力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv)
+* [组合问题的剪枝操作(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1wi4y157er)
+* [组合总和(对应力扣题目:39.组合总和)](https://www.bilibili.com/video/BV1KT4y1M7HJ/)
+* [分割回文串(对应力扣题目:131.分割回文串)](https://www.bilibili.com/video/BV1c54y1e7k6)
+* [二叉树理论基础](https://www.bilibili.com/video/BV1Hy4y1t7ij)
+* [二叉树的递归遍历](https://www.bilibili.com/video/BV1Wh411S7xt)
+* [二叉树的非递归遍历(一)](https://www.bilibili.com/video/BV15f4y1W7i2)
+
+(持续更新中....)
# 关于作者
大家好,我是程序员Carl,哈工大师兄,ACM 校赛、黑龙江省赛、东北四省赛金牌、亚洲区域赛铜牌获得者,先后在腾讯和百度从事后端技术研发,CSDN博客专家。对算法和C++后端技术有一定的见解,利用工作之余重新刷leetcode。
-**加我的微信,备注:「个人简单介绍」+「组队刷题」**, 拉你进刷题群,每天一道经典题目分析,而且题目不是孤立的,每一道题目之间都是有关系的,都是由浅入深一脉相承的,所以学习效果最好是每篇连续着看,也许之前你会某些知识点,但是一直没有把知识点串起来,这里每天一篇文章就会帮你把知识点串起来。
+加入刷题微信群,备注:「个人简单介绍」 + 组队刷题
+
+也欢迎与我交流,备注:「个人简单介绍」 + 交流,围观朋友圈,做点赞之交(备注没有自我介绍不通过哦)
# 我的公众号
-更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:「简历模板」「二叉树总结」「回溯算法总结」「栈与队列总结」等关键字就可以获得我整理的学习资料。
+更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:「666」可以获得所有算法专题原创PDF。
-**每天8:35准时为你推送一篇经典面试题目,帮你梳理算法知识体系,轻松学习算法!**,并且公众号里有大量学习资源,也有我自己的学习心得和方法总结,更有很多志同道合的好伙伴在这里打卡学习,来看看就你知道了,相信一定会有所收获!
+**「代码随想录」每天准时为你推送一篇经典面试题目,帮你梳理算法知识体系,轻松学习算法!**,并且公众号里有大量学习资源,也有我自己的学习心得和方法总结,更有上万录友们在这里打卡学习。
+
+**来看看就知道了,你会发现相见恨晚!**
-
+
diff --git a/pics/100.相同的树.png b/pics/100.相同的树.png
deleted file mode 100644
index 88da7600..00000000
Binary files a/pics/100.相同的树.png and /dev/null differ
diff --git a/pics/1002.查找常用字符.png b/pics/1002.查找常用字符.png
deleted file mode 100644
index 23d75057..00000000
Binary files a/pics/1002.查找常用字符.png and /dev/null differ
diff --git a/pics/101. 对称二叉树.png b/pics/101. 对称二叉树.png
deleted file mode 100644
index 6485026d..00000000
Binary files a/pics/101. 对称二叉树.png and /dev/null differ
diff --git a/pics/101. 对称二叉树1.png b/pics/101. 对称二叉树1.png
deleted file mode 100644
index 98286136..00000000
Binary files a/pics/101. 对称二叉树1.png and /dev/null differ
diff --git a/pics/102.二叉树的层序遍历.png b/pics/102.二叉树的层序遍历.png
deleted file mode 100644
index dcfcf972..00000000
Binary files a/pics/102.二叉树的层序遍历.png and /dev/null differ
diff --git a/pics/104. 二叉树的最大深度.png b/pics/104. 二叉树的最大深度.png
deleted file mode 100644
index 74873c76..00000000
Binary files a/pics/104. 二叉树的最大深度.png and /dev/null differ
diff --git a/pics/1047.删除字符串中的所有相邻重复项.png b/pics/1047.删除字符串中的所有相邻重复项.png
deleted file mode 100644
index 57587100..00000000
Binary files a/pics/1047.删除字符串中的所有相邻重复项.png and /dev/null differ
diff --git a/pics/105. 从前序与中序遍历序列构造二叉树.png b/pics/105. 从前序与中序遍历序列构造二叉树.png
deleted file mode 100644
index 7430be27..00000000
Binary files a/pics/105. 从前序与中序遍历序列构造二叉树.png and /dev/null differ
diff --git a/pics/106. 从中序与后序遍历序列构造二叉树1.png b/pics/106. 从中序与后序遍历序列构造二叉树1.png
deleted file mode 100644
index ab8fa4b9..00000000
Binary files a/pics/106. 从中序与后序遍历序列构造二叉树1.png and /dev/null differ
diff --git a/pics/106.从中序与后序遍历序列构造二叉树.png b/pics/106.从中序与后序遍历序列构造二叉树.png
deleted file mode 100644
index 081a7813..00000000
Binary files a/pics/106.从中序与后序遍历序列构造二叉树.png and /dev/null differ
diff --git a/pics/106.从中序与后序遍历序列构造二叉树2.png b/pics/106.从中序与后序遍历序列构造二叉树2.png
deleted file mode 100644
index 8726f4ce..00000000
Binary files a/pics/106.从中序与后序遍历序列构造二叉树2.png and /dev/null differ
diff --git a/pics/107.二叉树的层次遍历II.png b/pics/107.二叉树的层次遍历II.png
deleted file mode 100644
index b4021c44..00000000
Binary files a/pics/107.二叉树的层次遍历II.png and /dev/null differ
diff --git a/pics/108.将有序数组转换为二叉搜索树.png b/pics/108.将有序数组转换为二叉搜索树.png
deleted file mode 100644
index 7d25f99d..00000000
Binary files a/pics/108.将有序数组转换为二叉搜索树.png and /dev/null differ
diff --git a/pics/110.平衡二叉树.png b/pics/110.平衡二叉树.png
deleted file mode 100644
index f227f1ad..00000000
Binary files a/pics/110.平衡二叉树.png and /dev/null differ
diff --git a/pics/110.平衡二叉树1.png b/pics/110.平衡二叉树1.png
deleted file mode 100644
index 36e0a09c..00000000
Binary files a/pics/110.平衡二叉树1.png and /dev/null differ
diff --git a/pics/110.平衡二叉树2.png b/pics/110.平衡二叉树2.png
deleted file mode 100644
index 53d46be6..00000000
Binary files a/pics/110.平衡二叉树2.png and /dev/null differ
diff --git a/pics/111.二叉树的最小深度.png b/pics/111.二叉树的最小深度.png
deleted file mode 100644
index b1980df8..00000000
Binary files a/pics/111.二叉树的最小深度.png and /dev/null differ
diff --git a/pics/111.二叉树的最小深度1.png b/pics/111.二叉树的最小深度1.png
deleted file mode 100644
index a0ac70cb..00000000
Binary files a/pics/111.二叉树的最小深度1.png and /dev/null differ
diff --git a/pics/112.路径总和.png b/pics/112.路径总和.png
deleted file mode 100644
index 2a1b5100..00000000
Binary files a/pics/112.路径总和.png and /dev/null differ
diff --git a/pics/112.路径总和1.png b/pics/112.路径总和1.png
deleted file mode 100644
index 4c6c0f60..00000000
Binary files a/pics/112.路径总和1.png and /dev/null differ
diff --git a/pics/113.路径总和II.png b/pics/113.路径总和II.png
deleted file mode 100644
index 931c2a9a..00000000
Binary files a/pics/113.路径总和II.png and /dev/null differ
diff --git a/pics/113.路径总和II1.png b/pics/113.路径总和II1.png
deleted file mode 100644
index e1d5a2d1..00000000
Binary files a/pics/113.路径总和II1.png and /dev/null differ
diff --git a/pics/116.填充每个节点的下一个右侧节点指针.png b/pics/116.填充每个节点的下一个右侧节点指针.png
deleted file mode 100644
index bec25c0a..00000000
Binary files a/pics/116.填充每个节点的下一个右侧节点指针.png and /dev/null differ
diff --git a/pics/1207.独一无二的出现次数.png b/pics/1207.独一无二的出现次数.png
deleted file mode 100644
index bbb036e6..00000000
Binary files a/pics/1207.独一无二的出现次数.png and /dev/null differ
diff --git a/pics/122.买卖股票的最佳时机II.png b/pics/122.买卖股票的最佳时机II.png
deleted file mode 100644
index 9799abfd..00000000
Binary files a/pics/122.买卖股票的最佳时机II.png and /dev/null differ
diff --git a/pics/127.单词接龙.png b/pics/127.单词接龙.png
deleted file mode 100644
index 581bb558..00000000
Binary files a/pics/127.单词接龙.png and /dev/null differ
diff --git a/pics/129.求根到叶子节点数字之和.png b/pics/129.求根到叶子节点数字之和.png
deleted file mode 100644
index 58288a9d..00000000
Binary files a/pics/129.求根到叶子节点数字之和.png and /dev/null differ
diff --git a/pics/131.分割回文串.png b/pics/131.分割回文串.png
deleted file mode 100644
index 0b50823f..00000000
Binary files a/pics/131.分割回文串.png and /dev/null differ
diff --git a/pics/134.加油站.png b/pics/134.加油站.png
deleted file mode 100644
index 120f714d..00000000
Binary files a/pics/134.加油站.png and /dev/null differ
diff --git a/pics/135.分发糖果.png b/pics/135.分发糖果.png
deleted file mode 100644
index cacd2cbe..00000000
Binary files a/pics/135.分发糖果.png and /dev/null differ
diff --git a/pics/135.分发糖果1.png b/pics/135.分发糖果1.png
deleted file mode 100644
index 27209df9..00000000
Binary files a/pics/135.分发糖果1.png and /dev/null differ
diff --git a/pics/1356.根据数字二进制下1的数目排序.png b/pics/1356.根据数字二进制下1的数目排序.png
deleted file mode 100644
index 0fca9fed..00000000
Binary files a/pics/1356.根据数字二进制下1的数目排序.png and /dev/null differ
diff --git a/pics/1365.有多少小于当前数字的数字.png b/pics/1365.有多少小于当前数字的数字.png
deleted file mode 100644
index 9d67d07b..00000000
Binary files a/pics/1365.有多少小于当前数字的数字.png and /dev/null differ
diff --git a/pics/142环形链表1.png b/pics/142环形链表1.png
deleted file mode 100644
index 7814be45..00000000
Binary files a/pics/142环形链表1.png and /dev/null differ
diff --git a/pics/142环形链表2.png b/pics/142环形链表2.png
deleted file mode 100644
index 86294647..00000000
Binary files a/pics/142环形链表2.png and /dev/null differ
diff --git a/pics/142环形链表3.png b/pics/142环形链表3.png
deleted file mode 100644
index 9bd96f6b..00000000
Binary files a/pics/142环形链表3.png and /dev/null differ
diff --git a/pics/142环形链表4.png b/pics/142环形链表4.png
deleted file mode 100644
index e37690d8..00000000
Binary files a/pics/142环形链表4.png and /dev/null differ
diff --git a/pics/142环形链表5.png b/pics/142环形链表5.png
deleted file mode 100644
index b99a79f1..00000000
Binary files a/pics/142环形链表5.png and /dev/null differ
diff --git a/pics/143.重排链表.png b/pics/143.重排链表.png
deleted file mode 100644
index 2fc94408..00000000
Binary files a/pics/143.重排链表.png and /dev/null differ
diff --git a/pics/147.对链表进行插入排序.png b/pics/147.对链表进行插入排序.png
deleted file mode 100644
index 807f66f4..00000000
Binary files a/pics/147.对链表进行插入排序.png and /dev/null differ
diff --git a/pics/151_翻转字符串里的单词.png b/pics/151_翻转字符串里的单词.png
deleted file mode 100644
index dc844950..00000000
Binary files a/pics/151_翻转字符串里的单词.png and /dev/null differ
diff --git a/pics/17. 电话号码的字母组合.jpeg b/pics/17. 电话号码的字母组合.jpeg
deleted file mode 100644
index f80ec767..00000000
Binary files a/pics/17. 电话号码的字母组合.jpeg and /dev/null differ
diff --git a/pics/17. 电话号码的字母组合.png b/pics/17. 电话号码的字母组合.png
deleted file mode 100644
index b11f7114..00000000
Binary files a/pics/17. 电话号码的字母组合.png and /dev/null differ
diff --git a/pics/19.删除链表的倒数第N个节点.png b/pics/19.删除链表的倒数第N个节点.png
deleted file mode 100644
index 6f6aa9c5..00000000
Binary files a/pics/19.删除链表的倒数第N个节点.png and /dev/null differ
diff --git a/pics/19.删除链表的倒数第N个节点1.png b/pics/19.删除链表的倒数第N个节点1.png
deleted file mode 100644
index cca947b4..00000000
Binary files a/pics/19.删除链表的倒数第N个节点1.png and /dev/null differ
diff --git a/pics/19.删除链表的倒数第N个节点2.png b/pics/19.删除链表的倒数第N个节点2.png
deleted file mode 100644
index 0d8144cd..00000000
Binary files a/pics/19.删除链表的倒数第N个节点2.png and /dev/null differ
diff --git a/pics/19.删除链表的倒数第N个节点3.png b/pics/19.删除链表的倒数第N个节点3.png
deleted file mode 100644
index d15d05e7..00000000
Binary files a/pics/19.删除链表的倒数第N个节点3.png and /dev/null differ
diff --git a/pics/199.二叉树的右视图.png b/pics/199.二叉树的右视图.png
deleted file mode 100644
index e5244117..00000000
Binary files a/pics/199.二叉树的右视图.png and /dev/null differ
diff --git a/pics/203_链表删除元素1.png b/pics/203_链表删除元素1.png
deleted file mode 100644
index b32aa50e..00000000
Binary files a/pics/203_链表删除元素1.png and /dev/null differ
diff --git a/pics/203_链表删除元素2.png b/pics/203_链表删除元素2.png
deleted file mode 100644
index 5519a69d..00000000
Binary files a/pics/203_链表删除元素2.png and /dev/null differ
diff --git a/pics/203_链表删除元素3.png b/pics/203_链表删除元素3.png
deleted file mode 100644
index cd50ce13..00000000
Binary files a/pics/203_链表删除元素3.png and /dev/null differ
diff --git a/pics/203_链表删除元素4.png b/pics/203_链表删除元素4.png
deleted file mode 100644
index 02aaf115..00000000
Binary files a/pics/203_链表删除元素4.png and /dev/null differ
diff --git a/pics/203_链表删除元素5.png b/pics/203_链表删除元素5.png
deleted file mode 100644
index e24ad3d3..00000000
Binary files a/pics/203_链表删除元素5.png and /dev/null differ
diff --git a/pics/203_链表删除元素6.png b/pics/203_链表删除元素6.png
deleted file mode 100644
index 13f9b6d6..00000000
Binary files a/pics/203_链表删除元素6.png and /dev/null differ
diff --git a/pics/206_反转链表.png b/pics/206_反转链表.png
deleted file mode 100644
index f26ad08f..00000000
Binary files a/pics/206_反转链表.png and /dev/null differ
diff --git a/pics/216.组合总和III.png b/pics/216.组合总和III.png
deleted file mode 100644
index 1b71e67c..00000000
Binary files a/pics/216.组合总和III.png and /dev/null differ
diff --git a/pics/216.组合总和III1.png b/pics/216.组合总和III1.png
deleted file mode 100644
index e495191d..00000000
Binary files a/pics/216.组合总和III1.png and /dev/null differ
diff --git a/pics/222.完全二叉树的节点个数.png b/pics/222.完全二叉树的节点个数.png
deleted file mode 100644
index 2eb1eb94..00000000
Binary files a/pics/222.完全二叉树的节点个数.png and /dev/null differ
diff --git a/pics/222.完全二叉树的节点个数1.png b/pics/222.完全二叉树的节点个数1.png
deleted file mode 100644
index 49e1772c..00000000
Binary files a/pics/222.完全二叉树的节点个数1.png and /dev/null differ
diff --git a/pics/226.翻转二叉树.png b/pics/226.翻转二叉树.png
deleted file mode 100644
index 1e3aec91..00000000
Binary files a/pics/226.翻转二叉树.png and /dev/null differ
diff --git a/pics/226.翻转二叉树1.png b/pics/226.翻转二叉树1.png
deleted file mode 100644
index 9435a12d..00000000
Binary files a/pics/226.翻转二叉树1.png and /dev/null differ
diff --git a/pics/234.回文链表.png b/pics/234.回文链表.png
deleted file mode 100644
index 285c73bb..00000000
Binary files a/pics/234.回文链表.png and /dev/null differ
diff --git a/pics/235.二叉搜索树的最近公共祖先.png b/pics/235.二叉搜索树的最近公共祖先.png
deleted file mode 100644
index 277b8165..00000000
Binary files a/pics/235.二叉搜索树的最近公共祖先.png and /dev/null differ
diff --git a/pics/236.二叉树的最近公共祖先.png b/pics/236.二叉树的最近公共祖先.png
deleted file mode 100644
index 023dd955..00000000
Binary files a/pics/236.二叉树的最近公共祖先.png and /dev/null differ
diff --git a/pics/236.二叉树的最近公共祖先1.png b/pics/236.二叉树的最近公共祖先1.png
deleted file mode 100644
index 4542ad5c..00000000
Binary files a/pics/236.二叉树的最近公共祖先1.png and /dev/null differ
diff --git a/pics/236.二叉树的最近公共祖先2.png b/pics/236.二叉树的最近公共祖先2.png
deleted file mode 100644
index 1681fbcc..00000000
Binary files a/pics/236.二叉树的最近公共祖先2.png and /dev/null differ
diff --git a/pics/239.滑动窗口最大值.png b/pics/239.滑动窗口最大值.png
deleted file mode 100644
index 49734e5d..00000000
Binary files a/pics/239.滑动窗口最大值.png and /dev/null differ
diff --git a/pics/24.两两交换链表中的节点.png b/pics/24.两两交换链表中的节点.png
deleted file mode 100644
index 3051c5a1..00000000
Binary files a/pics/24.两两交换链表中的节点.png and /dev/null differ
diff --git a/pics/24.两两交换链表中的节点1.png b/pics/24.两两交换链表中的节点1.png
deleted file mode 100644
index e79642c3..00000000
Binary files a/pics/24.两两交换链表中的节点1.png and /dev/null differ
diff --git a/pics/24.两两交换链表中的节点2.png b/pics/24.两两交换链表中的节点2.png
deleted file mode 100644
index e6dba912..00000000
Binary files a/pics/24.两两交换链表中的节点2.png and /dev/null differ
diff --git a/pics/24.两两交换链表中的节点3.png b/pics/24.两两交换链表中的节点3.png
deleted file mode 100644
index 7d706cd2..00000000
Binary files a/pics/24.两两交换链表中的节点3.png and /dev/null differ
diff --git a/pics/257.二叉树的所有路径.png b/pics/257.二叉树的所有路径.png
deleted file mode 100644
index 0a0683e0..00000000
Binary files a/pics/257.二叉树的所有路径.png and /dev/null differ
diff --git a/pics/257.二叉树的所有路径1.png b/pics/257.二叉树的所有路径1.png
deleted file mode 100644
index 97452f51..00000000
Binary files a/pics/257.二叉树的所有路径1.png and /dev/null differ
diff --git a/pics/26_封面.png b/pics/26_封面.png
deleted file mode 100644
index b8a2f1a1..00000000
Binary files a/pics/26_封面.png and /dev/null differ
diff --git a/pics/27_封面.png b/pics/27_封面.png
deleted file mode 100644
index a4a2b009..00000000
Binary files a/pics/27_封面.png and /dev/null differ
diff --git a/pics/31.下一个排列.png b/pics/31.下一个排列.png
deleted file mode 100644
index f88ef392..00000000
Binary files a/pics/31.下一个排列.png and /dev/null differ
diff --git a/pics/332.重新安排行程.png b/pics/332.重新安排行程.png
deleted file mode 100644
index 56f3ab02..00000000
Binary files a/pics/332.重新安排行程.png and /dev/null differ
diff --git a/pics/332.重新安排行程1.png b/pics/332.重新安排行程1.png
deleted file mode 100644
index 69d51882..00000000
Binary files a/pics/332.重新安排行程1.png and /dev/null differ
diff --git a/pics/347.前K个高频元素.png b/pics/347.前K个高频元素.png
deleted file mode 100644
index 54b1417d..00000000
Binary files a/pics/347.前K个高频元素.png and /dev/null differ
diff --git a/pics/35_封面.png b/pics/35_封面.png
deleted file mode 100644
index dc2971b5..00000000
Binary files a/pics/35_封面.png and /dev/null differ
diff --git a/pics/35_搜索插入位置.png b/pics/35_搜索插入位置.png
deleted file mode 100644
index 5fa65490..00000000
Binary files a/pics/35_搜索插入位置.png and /dev/null differ
diff --git a/pics/35_搜索插入位置2.png b/pics/35_搜索插入位置2.png
deleted file mode 100644
index 71fca151..00000000
Binary files a/pics/35_搜索插入位置2.png and /dev/null differ
diff --git a/pics/35_搜索插入位置3.png b/pics/35_搜索插入位置3.png
deleted file mode 100644
index ec1ce1eb..00000000
Binary files a/pics/35_搜索插入位置3.png and /dev/null differ
diff --git a/pics/35_搜索插入位置4.png b/pics/35_搜索插入位置4.png
deleted file mode 100644
index efb88e22..00000000
Binary files a/pics/35_搜索插入位置4.png and /dev/null differ
diff --git a/pics/35_搜索插入位置5.png b/pics/35_搜索插入位置5.png
deleted file mode 100644
index ec021c9d..00000000
Binary files a/pics/35_搜索插入位置5.png and /dev/null differ
diff --git a/pics/37.解数独.png b/pics/37.解数独.png
deleted file mode 100644
index 46b0a15a..00000000
Binary files a/pics/37.解数独.png and /dev/null differ
diff --git a/pics/376.摆动序列.png b/pics/376.摆动序列.png
deleted file mode 100644
index c6a06dfe..00000000
Binary files a/pics/376.摆动序列.png and /dev/null differ
diff --git a/pics/376.摆动序列1.png b/pics/376.摆动序列1.png
deleted file mode 100644
index 212e7722..00000000
Binary files a/pics/376.摆动序列1.png and /dev/null differ
diff --git a/pics/39.组合总和.png b/pics/39.组合总和.png
deleted file mode 100644
index 960cba69..00000000
Binary files a/pics/39.组合总和.png and /dev/null differ
diff --git a/pics/39.组合总和1.png b/pics/39.组合总和1.png
deleted file mode 100644
index d3557efc..00000000
Binary files a/pics/39.组合总和1.png and /dev/null differ
diff --git a/pics/40.组合总和II.png b/pics/40.组合总和II.png
deleted file mode 100644
index 104e5f4e..00000000
Binary files a/pics/40.组合总和II.png and /dev/null differ
diff --git a/pics/40.组合总和II1.png b/pics/40.组合总和II1.png
deleted file mode 100644
index 75fda6b3..00000000
Binary files a/pics/40.组合总和II1.png and /dev/null differ
diff --git a/pics/404.左叶子之和.png b/pics/404.左叶子之和.png
deleted file mode 100644
index 81b6d8c2..00000000
Binary files a/pics/404.左叶子之和.png and /dev/null differ
diff --git a/pics/404.左叶子之和1.png b/pics/404.左叶子之和1.png
deleted file mode 100644
index 8c050e04..00000000
Binary files a/pics/404.左叶子之和1.png and /dev/null differ
diff --git a/pics/406.根据身高重建队列.png b/pics/406.根据身高重建队列.png
deleted file mode 100644
index 9ea8f0b2..00000000
Binary files a/pics/406.根据身高重建队列.png and /dev/null differ
diff --git a/pics/416.分割等和子集.png b/pics/416.分割等和子集.png
deleted file mode 100644
index 8d1b9ef6..00000000
Binary files a/pics/416.分割等和子集.png and /dev/null differ
diff --git a/pics/416.分割等和子集1.png b/pics/416.分割等和子集1.png
deleted file mode 100644
index 6be70dc6..00000000
Binary files a/pics/416.分割等和子集1.png and /dev/null differ
diff --git a/pics/42.接雨水1.png b/pics/42.接雨水1.png
deleted file mode 100644
index 26624f4b..00000000
Binary files a/pics/42.接雨水1.png and /dev/null differ
diff --git a/pics/42.接雨水2.png b/pics/42.接雨水2.png
deleted file mode 100644
index 94eda97e..00000000
Binary files a/pics/42.接雨水2.png and /dev/null differ
diff --git a/pics/42.接雨水3.png b/pics/42.接雨水3.png
deleted file mode 100644
index 1e4b528a..00000000
Binary files a/pics/42.接雨水3.png and /dev/null differ
diff --git a/pics/42.接雨水4.png b/pics/42.接雨水4.png
deleted file mode 100644
index a18539ce..00000000
Binary files a/pics/42.接雨水4.png and /dev/null differ
diff --git a/pics/42.接雨水5.png b/pics/42.接雨水5.png
deleted file mode 100644
index 066792b0..00000000
Binary files a/pics/42.接雨水5.png and /dev/null differ
diff --git a/pics/429. N叉树的层序遍历.png b/pics/429. N叉树的层序遍历.png
deleted file mode 100644
index d28c543c..00000000
Binary files a/pics/429. N叉树的层序遍历.png and /dev/null differ
diff --git a/pics/435.无重叠区间.png b/pics/435.无重叠区间.png
deleted file mode 100644
index a45913c1..00000000
Binary files a/pics/435.无重叠区间.png and /dev/null differ
diff --git a/pics/45.跳跃游戏II.png b/pics/45.跳跃游戏II.png
deleted file mode 100644
index 77130cb2..00000000
Binary files a/pics/45.跳跃游戏II.png and /dev/null differ
diff --git a/pics/45.跳跃游戏II1.png b/pics/45.跳跃游戏II1.png
deleted file mode 100644
index 7850187f..00000000
Binary files a/pics/45.跳跃游戏II1.png and /dev/null differ
diff --git a/pics/45.跳跃游戏II2.png b/pics/45.跳跃游戏II2.png
deleted file mode 100644
index aa45f60a..00000000
Binary files a/pics/45.跳跃游戏II2.png and /dev/null differ
diff --git a/pics/452.用最少数量的箭引爆气球.png b/pics/452.用最少数量的箭引爆气球.png
deleted file mode 100644
index 64080914..00000000
Binary files a/pics/452.用最少数量的箭引爆气球.png and /dev/null differ
diff --git a/pics/455.分发饼干.png b/pics/455.分发饼干.png
deleted file mode 100644
index 17e15e09..00000000
Binary files a/pics/455.分发饼干.png and /dev/null differ
diff --git a/pics/459.重复的子字符串_1.png b/pics/459.重复的子字符串_1.png
deleted file mode 100644
index 2feddd6b..00000000
Binary files a/pics/459.重复的子字符串_1.png and /dev/null differ
diff --git a/pics/46.全排列.png b/pics/46.全排列.png
deleted file mode 100644
index d8c484ec..00000000
Binary files a/pics/46.全排列.png and /dev/null differ
diff --git a/pics/463.岛屿的周长.png b/pics/463.岛屿的周长.png
deleted file mode 100644
index 5b0fa013..00000000
Binary files a/pics/463.岛屿的周长.png and /dev/null differ
diff --git a/pics/463.岛屿的周长1.png b/pics/463.岛屿的周长1.png
deleted file mode 100644
index bb14dbd6..00000000
Binary files a/pics/463.岛屿的周长1.png and /dev/null differ
diff --git a/pics/47.全排列II1.png b/pics/47.全排列II1.png
deleted file mode 100644
index 8160dd86..00000000
Binary files a/pics/47.全排列II1.png and /dev/null differ
diff --git a/pics/47.全排列II2.png b/pics/47.全排列II2.png
deleted file mode 100644
index d2e096b1..00000000
Binary files a/pics/47.全排列II2.png and /dev/null differ
diff --git a/pics/47.全排列II3.png b/pics/47.全排列II3.png
deleted file mode 100644
index b6f5a862..00000000
Binary files a/pics/47.全排列II3.png and /dev/null differ
diff --git a/pics/486.预测赢家.png b/pics/486.预测赢家.png
deleted file mode 100644
index 03ddfaa8..00000000
Binary files a/pics/486.预测赢家.png and /dev/null differ
diff --git a/pics/486.预测赢家1.png b/pics/486.预测赢家1.png
deleted file mode 100644
index dbddef26..00000000
Binary files a/pics/486.预测赢家1.png and /dev/null differ
diff --git a/pics/486.预测赢家2.png b/pics/486.预测赢家2.png
deleted file mode 100644
index 7d26de9a..00000000
Binary files a/pics/486.预测赢家2.png and /dev/null differ
diff --git a/pics/486.预测赢家3.png b/pics/486.预测赢家3.png
deleted file mode 100644
index 506778c4..00000000
Binary files a/pics/486.预测赢家3.png and /dev/null differ
diff --git a/pics/486.预测赢家4.png b/pics/486.预测赢家4.png
deleted file mode 100644
index 8a679b74..00000000
Binary files a/pics/486.预测赢家4.png and /dev/null differ
diff --git a/pics/491. 递增子序列1.png b/pics/491. 递增子序列1.png
deleted file mode 100644
index 19da907e..00000000
Binary files a/pics/491. 递增子序列1.png and /dev/null differ
diff --git a/pics/491. 递增子序列2.png b/pics/491. 递增子序列2.png
deleted file mode 100644
index 24988010..00000000
Binary files a/pics/491. 递增子序列2.png and /dev/null differ
diff --git a/pics/491. 递增子序列3.png b/pics/491. 递增子序列3.png
deleted file mode 100644
index df293dd5..00000000
Binary files a/pics/491. 递增子序列3.png and /dev/null differ
diff --git a/pics/491. 递增子序列4.png b/pics/491. 递增子序列4.png
deleted file mode 100644
index 395fc515..00000000
Binary files a/pics/491. 递增子序列4.png and /dev/null differ
diff --git a/pics/501.二叉搜索树中的众数.png b/pics/501.二叉搜索树中的众数.png
deleted file mode 100644
index a7197b08..00000000
Binary files a/pics/501.二叉搜索树中的众数.png and /dev/null differ
diff --git a/pics/501.二叉搜索树中的众数1.png b/pics/501.二叉搜索树中的众数1.png
deleted file mode 100644
index 200bc2fa..00000000
Binary files a/pics/501.二叉搜索树中的众数1.png and /dev/null differ
diff --git a/pics/51.N皇后.png b/pics/51.N皇后.png
deleted file mode 100644
index 730b7cb8..00000000
Binary files a/pics/51.N皇后.png and /dev/null differ
diff --git a/pics/51.N皇后1.png b/pics/51.N皇后1.png
deleted file mode 100644
index e6c9c72d..00000000
Binary files a/pics/51.N皇后1.png and /dev/null differ
diff --git a/pics/513.找树左下角的值.png b/pics/513.找树左下角的值.png
deleted file mode 100644
index 67bda96f..00000000
Binary files a/pics/513.找树左下角的值.png and /dev/null differ
diff --git a/pics/513.找树左下角的值1.png b/pics/513.找树左下角的值1.png
deleted file mode 100644
index a5421ad6..00000000
Binary files a/pics/513.找树左下角的值1.png and /dev/null differ
diff --git a/pics/515.在每个树行中找最大值.png b/pics/515.在每个树行中找最大值.png
deleted file mode 100644
index e8f2f431..00000000
Binary files a/pics/515.在每个树行中找最大值.png and /dev/null differ
diff --git a/pics/530.二叉搜索树的最小绝对差.png b/pics/530.二叉搜索树的最小绝对差.png
deleted file mode 100644
index 04ca9a65..00000000
Binary files a/pics/530.二叉搜索树的最小绝对差.png and /dev/null differ
diff --git a/pics/538.把二叉搜索树转换为累加树.png b/pics/538.把二叉搜索树转换为累加树.png
deleted file mode 100644
index 27077f7b..00000000
Binary files a/pics/538.把二叉搜索树转换为累加树.png and /dev/null differ
diff --git a/pics/541_反转字符串II.png b/pics/541_反转字符串II.png
deleted file mode 100644
index 45f0ce6c..00000000
Binary files a/pics/541_反转字符串II.png and /dev/null differ
diff --git a/pics/55.跳跃游戏.png b/pics/55.跳跃游戏.png
deleted file mode 100644
index 012bb635..00000000
Binary files a/pics/55.跳跃游戏.png and /dev/null differ
diff --git a/pics/559.N叉树的最大深度.png b/pics/559.N叉树的最大深度.png
deleted file mode 100644
index d28c543c..00000000
Binary files a/pics/559.N叉树的最大深度.png and /dev/null differ
diff --git a/pics/56.合并区间.png b/pics/56.合并区间.png
deleted file mode 100644
index ff905c72..00000000
Binary files a/pics/56.合并区间.png and /dev/null differ
diff --git a/pics/57.插入区间.png b/pics/57.插入区间.png
deleted file mode 100644
index 67290167..00000000
Binary files a/pics/57.插入区间.png and /dev/null differ
diff --git a/pics/57.插入区间1.png b/pics/57.插入区间1.png
deleted file mode 100644
index 69835dee..00000000
Binary files a/pics/57.插入区间1.png and /dev/null differ
diff --git a/pics/59_封面.png b/pics/59_封面.png
deleted file mode 100644
index 795e8f04..00000000
Binary files a/pics/59_封面.png and /dev/null differ
diff --git a/pics/617.合并二叉树.png b/pics/617.合并二叉树.png
deleted file mode 100644
index 182f959c..00000000
Binary files a/pics/617.合并二叉树.png and /dev/null differ
diff --git a/pics/62.不同路径.png b/pics/62.不同路径.png
deleted file mode 100644
index d82c6ed0..00000000
Binary files a/pics/62.不同路径.png and /dev/null differ
diff --git a/pics/62.不同路径1.png b/pics/62.不同路径1.png
deleted file mode 100644
index 0d84838d..00000000
Binary files a/pics/62.不同路径1.png and /dev/null differ
diff --git a/pics/62.不同路径2.png b/pics/62.不同路径2.png
deleted file mode 100644
index 91984901..00000000
Binary files a/pics/62.不同路径2.png and /dev/null differ
diff --git a/pics/637.二叉树的层平均值.png b/pics/637.二叉树的层平均值.png
deleted file mode 100644
index 57018dab..00000000
Binary files a/pics/637.二叉树的层平均值.png and /dev/null differ
diff --git a/pics/654.最大二叉树.png b/pics/654.最大二叉树.png
deleted file mode 100644
index e8fc63aa..00000000
Binary files a/pics/654.最大二叉树.png and /dev/null differ
diff --git a/pics/657.机器人能否返回原点.png b/pics/657.机器人能否返回原点.png
deleted file mode 100644
index 6ea5b69b..00000000
Binary files a/pics/657.机器人能否返回原点.png and /dev/null differ
diff --git a/pics/669.修剪二叉搜索树.png b/pics/669.修剪二叉搜索树.png
deleted file mode 100644
index 89fe4104..00000000
Binary files a/pics/669.修剪二叉搜索树.png and /dev/null differ
diff --git a/pics/669.修剪二叉搜索树1.png b/pics/669.修剪二叉搜索树1.png
deleted file mode 100644
index 1b46e8d0..00000000
Binary files a/pics/669.修剪二叉搜索树1.png and /dev/null differ
diff --git a/pics/685.冗余连接II1.png b/pics/685.冗余连接II1.png
deleted file mode 100644
index ab833087..00000000
Binary files a/pics/685.冗余连接II1.png and /dev/null differ
diff --git a/pics/685.冗余连接II2.png b/pics/685.冗余连接II2.png
deleted file mode 100644
index 6dbb2ac7..00000000
Binary files a/pics/685.冗余连接II2.png and /dev/null differ
diff --git a/pics/700.二叉搜索树中的搜索.png b/pics/700.二叉搜索树中的搜索.png
deleted file mode 100644
index 1fd15466..00000000
Binary files a/pics/700.二叉搜索树中的搜索.png and /dev/null differ
diff --git a/pics/700.二叉搜索树中的搜索1.png b/pics/700.二叉搜索树中的搜索1.png
deleted file mode 100644
index 037627b1..00000000
Binary files a/pics/700.二叉搜索树中的搜索1.png and /dev/null differ
diff --git a/pics/763.划分字母区间.png b/pics/763.划分字母区间.png
deleted file mode 100644
index 0c1da0f3..00000000
Binary files a/pics/763.划分字母区间.png and /dev/null differ
diff --git a/pics/77.组合.png b/pics/77.组合.png
deleted file mode 100644
index 17cde493..00000000
Binary files a/pics/77.组合.png and /dev/null differ
diff --git a/pics/77.组合1.png b/pics/77.组合1.png
deleted file mode 100644
index a6a4a272..00000000
Binary files a/pics/77.组合1.png and /dev/null differ
diff --git a/pics/77.组合2.png b/pics/77.组合2.png
deleted file mode 100644
index 94e305b6..00000000
Binary files a/pics/77.组合2.png and /dev/null differ
diff --git a/pics/77.组合3.png b/pics/77.组合3.png
deleted file mode 100644
index 4ba73549..00000000
Binary files a/pics/77.组合3.png and /dev/null differ
diff --git a/pics/77.组合4.png b/pics/77.组合4.png
deleted file mode 100644
index b2519ccd..00000000
Binary files a/pics/77.组合4.png and /dev/null differ
diff --git a/pics/78.子集.png b/pics/78.子集.png
deleted file mode 100644
index 1700030f..00000000
Binary files a/pics/78.子集.png and /dev/null differ
diff --git a/pics/841.钥匙和房间.png b/pics/841.钥匙和房间.png
deleted file mode 100644
index 3bfdeea4..00000000
Binary files a/pics/841.钥匙和房间.png and /dev/null differ
diff --git a/pics/90.子集II.png b/pics/90.子集II.png
deleted file mode 100644
index 972010f6..00000000
Binary files a/pics/90.子集II.png and /dev/null differ
diff --git a/pics/90.子集II1.png b/pics/90.子集II1.png
deleted file mode 100644
index 92a18238..00000000
Binary files a/pics/90.子集II1.png and /dev/null differ
diff --git a/pics/90.子集II2.png b/pics/90.子集II2.png
deleted file mode 100644
index e42a56f7..00000000
Binary files a/pics/90.子集II2.png and /dev/null differ
diff --git a/pics/925.长按键入.png b/pics/925.长按键入.png
deleted file mode 100644
index c6bee552..00000000
Binary files a/pics/925.长按键入.png and /dev/null differ
diff --git a/pics/93.复原IP地址.png b/pics/93.复原IP地址.png
deleted file mode 100644
index 63d11f2e..00000000
Binary files a/pics/93.复原IP地址.png and /dev/null differ
diff --git a/pics/941.有效的山脉数组.png b/pics/941.有效的山脉数组.png
deleted file mode 100644
index e261242c..00000000
Binary files a/pics/941.有效的山脉数组.png and /dev/null differ
diff --git a/pics/968.监控二叉树1.png b/pics/968.监控二叉树1.png
deleted file mode 100644
index 7e6a75df..00000000
Binary files a/pics/968.监控二叉树1.png and /dev/null differ
diff --git a/pics/968.监控二叉树2.png b/pics/968.监控二叉树2.png
deleted file mode 100644
index 664a656e..00000000
Binary files a/pics/968.监控二叉树2.png and /dev/null differ
diff --git a/pics/968.监控二叉树3.png b/pics/968.监控二叉树3.png
deleted file mode 100644
index 0a3a9ea6..00000000
Binary files a/pics/968.监控二叉树3.png and /dev/null differ
diff --git a/pics/977.有序数组的平方.png b/pics/977.有序数组的平方.png
deleted file mode 100644
index cb9ccf6c..00000000
Binary files a/pics/977.有序数组的平方.png and /dev/null differ
diff --git a/pics/98.验证二叉搜索树.png b/pics/98.验证二叉搜索树.png
deleted file mode 100644
index 8a164140..00000000
Binary files a/pics/98.验证二叉搜索树.png and /dev/null differ
diff --git a/pics/leetcode_209.png b/pics/leetcode_209.png
deleted file mode 100644
index 2278be41..00000000
Binary files a/pics/leetcode_209.png and /dev/null differ
diff --git a/pics/公众号.png b/pics/公众号.png
new file mode 100644
index 00000000..eeec00ad
Binary files /dev/null and b/pics/公众号.png differ
diff --git a/pics/公众号二维码.jpg b/pics/公众号二维码.jpg
new file mode 100644
index 00000000..a91b2494
Binary files /dev/null and b/pics/公众号二维码.jpg differ
diff --git a/pics/剑指Offer05.替换空格.png b/pics/剑指Offer05.替换空格.png
deleted file mode 100644
index e4a2dfbc..00000000
Binary files a/pics/剑指Offer05.替换空格.png and /dev/null differ
diff --git a/pics/剑指Offer58-II.左旋转字符串.png b/pics/剑指Offer58-II.左旋转字符串.png
deleted file mode 100644
index 9b41754e..00000000
Binary files a/pics/剑指Offer58-II.左旋转字符串.png and /dev/null differ
diff --git a/pics/动态规划-背包问题1.png b/pics/动态规划-背包问题1.png
deleted file mode 100644
index dd51c5a9..00000000
Binary files a/pics/动态规划-背包问题1.png and /dev/null differ
diff --git a/pics/动态规划-背包问题2.png b/pics/动态规划-背包问题2.png
deleted file mode 100644
index 6ea1863c..00000000
Binary files a/pics/动态规划-背包问题2.png and /dev/null differ
diff --git a/pics/动态规划-背包问题3.png b/pics/动态规划-背包问题3.png
deleted file mode 100644
index 568c7bd9..00000000
Binary files a/pics/动态规划-背包问题3.png and /dev/null differ
diff --git a/pics/动态规划-背包问题4.png b/pics/动态规划-背包问题4.png
deleted file mode 100644
index 97632993..00000000
Binary files a/pics/动态规划-背包问题4.png and /dev/null differ
diff --git a/pics/回溯算法理论基础.png b/pics/回溯算法理论基础.png
deleted file mode 100644
index f8f2eb2f..00000000
Binary files a/pics/回溯算法理论基础.png and /dev/null differ
diff --git a/pics/微信搜一搜.png b/pics/微信搜一搜.png
new file mode 100644
index 00000000..62bee447
Binary files /dev/null and b/pics/微信搜一搜.png differ
diff --git a/pics/我要打十个.gif b/pics/我要打十个.gif
deleted file mode 100644
index c64eb70a..00000000
Binary files a/pics/我要打十个.gif and /dev/null differ
diff --git a/pics/知识星球.png b/pics/知识星球.png
new file mode 100644
index 00000000..43a5c6b3
Binary files /dev/null and b/pics/知识星球.png differ
diff --git a/pics/算法大纲.png b/pics/算法大纲.png
new file mode 100644
index 00000000..602f1d03
Binary files /dev/null and b/pics/算法大纲.png differ
diff --git a/pics/螺旋矩阵.png b/pics/螺旋矩阵.png
deleted file mode 100644
index 5148db46..00000000
Binary files a/pics/螺旋矩阵.png and /dev/null differ
diff --git a/pics/面试题02.07.链表相交_1.png b/pics/面试题02.07.链表相交_1.png
deleted file mode 100644
index 678311d1..00000000
Binary files a/pics/面试题02.07.链表相交_1.png and /dev/null differ
diff --git a/pics/面试题02.07.链表相交_2.png b/pics/面试题02.07.链表相交_2.png
deleted file mode 100644
index 97881bec..00000000
Binary files a/pics/面试题02.07.链表相交_2.png and /dev/null differ
diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md
index b7c9831b..6c9d99dd 100644
--- a/problems/0001.两数之和.md
+++ b/problems/0001.两数之和.md
@@ -1,9 +1,15 @@
-# 题目地址
-https://leetcode-cn.com/problems/two-sum/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 只用数组和set还是不够的!
-# 第1题. 两数之和
+## 1. 两数之和
+
+https://leetcode-cn.com/problems/two-sum/
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
@@ -18,7 +24,7 @@ https://leetcode-cn.com/problems/two-sum/
所以返回 [0, 1]
-# 思路
+## 思路
很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。
@@ -51,11 +57,11 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底
解题思路动画如下:
-
+
-# C++代码
+C++代码:
-```
+```C++
class Solution {
public:
vector twoSum(vector& nums, int target) {
@@ -72,66 +78,55 @@ public:
};
```
-## 一般解法
-代码:
-```C++
+
+## 其他语言版本
+
+
+Java:
+```java
+public int[] twoSum(int[] nums, int target) {
+ int[] res = new int[2];
+ if(nums == null || nums.length == 0){
+ return res;
+ }
+ Map map = new HashMap<>();
+ for(int i = 0; i < nums.length; i++){
+ int temp = target - nums[i];
+ if(map.containsKey(temp)){
+ res[1] = i;
+ res[0] = map.get(temp);
+ }
+ map.put(nums[i], i);
+ }
+ return res;
+}
```
-## 优化解法
+Python:
-```C++
-class Solution {
-public:
- vector twoSum(vector& nums, int target) {
- for (int i = 0; i < nums.size(); i ++) {
- for (int j = i + 1; j < nums.size(); j++) {
- if (nums[i] + nums[j] == target) {
- return {i, j};
- }
+
+Go:
+
+```go
+func twoSum(nums []int, target int) []int {
+ for k1, _ := range nums {
+ for k2 := k1 + 1; k2 < len(nums); k2++ {
+ if target == nums[k1] + nums[k2] {
+ return []int{k1, k2}
}
}
- return {};
}
-};
-
+ return []int{}
+}
```
-```
-class Solution {
-public:
- vector twoSum(vector& nums, int target) {
- std::map map;
- for(int i = 0; i < nums.size(); i++) {
- auto iter = map.find(target - nums[i]);
- if(iter != map.end()) {
- return {iter->second,i};
- }
- map.insert({nums, i});
- }
- return {};
- }
-};
-```
-```
-class Solution {
-public:
- vector twoSum(vector& nums, int target) {
- std::unordered_map map;
- for(int i = 0; i < nums.size(); i++) {
- auto iter = map.find(target - nums[i]);
- if(iter != map.end()) {
- return {iter->second, i};
- break;
- }
- map.emplace(nums[i], i);
- }
- return {};
- }
-};
-```
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
\ No newline at end of file
diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md
index c437cadb..55e22887 100644
--- a/problems/0015.三数之和.md
+++ b/problems/0015.三数之和.md
@@ -1,10 +1,19 @@
-# 题目地址
-https://leetcode-cn.com/problems/3sum/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
> 用哈希表解决了[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),那么三数之和呢?
# 第15题. 三数之和
+https://leetcode-cn.com/problems/3sum/
+
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
**注意:** 答案中不可以包含重复的三元组。
@@ -20,7 +29,7 @@ https://leetcode-cn.com/problems/3sum/
]
-# 思路
+# 思路
**注意[0, 0, 0, 0] 这组数据**
@@ -36,14 +45,14 @@ https://leetcode-cn.com/problems/3sum/
大家可以尝试使用哈希法写一写,就知道其困难的程度了。
-## 哈希法C++代码
-```
+哈希法C++代码:
+```C++
class Solution {
public:
vector> threeSum(vector& nums) {
vector> result;
sort(nums.begin(), nums.end());
- // 找出a + b + c = 0
+ // 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
@@ -84,22 +93,22 @@ public:
动画效果如下:
-
+
-拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下表0的地方开始,同时定一个下表left 定义在i+1的位置上,定义下表right 在数组结尾的位置上。
+拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下表0的地方开始,同时定一个下表left 定义在i+1的位置上,定义下表right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i] b = nums[left] c = nums[right]。
-接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下表就应该向左移动,这样才能让三数之和小一些。
+接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下表就应该向左移动,这样才能让三数之和小一些。
-如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
+如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)。
## 双指针法C++代码
-```
+```C++
class Solution {
public:
vector> threeSum(vector& nums) {
@@ -152,9 +161,9 @@ public:
};
```
-# 思考题
+# 思考题
-既然三数之和可以使用双指针法,我们之前讲过的[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),可不可以使用双指针法呢?
+既然三数之和可以使用双指针法,我们之前讲过的[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),可不可以使用双指针法呢?
如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!**
@@ -162,5 +171,62 @@ public:
如果[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)要求返回的是数值的话,就可以使用双指针法了。
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+
+## 其他语言版本
+
+
+Java:
+```Java
+class Solution {
+ public List> threeSum(int[] nums) {
+ List> result = new ArrayList<>();
+ Arrays.sort(nums);
+
+ for (int i = 0; i < nums.length; i++) {
+ if (nums[i] > 0) {
+ return result;
+ }
+
+ if (i > 0 && nums[i] == nums[i - 1]) {
+ continue;
+ }
+
+ int left = i + 1;
+ int right = nums.length - 1;
+ while (right > left) {
+ int sum = nums[i] + nums[left] + nums[right];
+ if (sum > 0) {
+ right--;
+ } else if (sum < 0) {
+ left++;
+ } else {
+ result.add(Arrays.asList(nums[i], nums[left], nums[right]));
+
+ while (right > left && nums[right] == nums[right - 1]) right--;
+ while (right > left && nums[left] == nums[left + 1]) left++;
+
+ right--;
+ left++;
+ }
+ }
+ }
+ return result;
+ }
+}
+```
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md
index e6d28496..6f51a181 100644
--- a/problems/0017.电话号码的字母组合.md
+++ b/problems/0017.电话号码的字母组合.md
@@ -1,5 +1,11 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 多个集合求组合问题。
# 17.电话号码的字母组合
@@ -9,29 +15,29 @@
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
-
+
-示例:
-输入:"23"
-输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
+示例:
+输入:"23"
+输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
-# 思路
+# 思路
从示例上来说,输入"23",最直接的想法就是两层for循环遍历了吧,正好把组合的情况都输出了。
-如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环.......
+如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环.......
大家应该感觉出和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。
理解本题后,要解决如下三个问题:
-1. 数字和字母如何映射
-2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
-3. 输入1 * #按键等等异常情况
+1. 数字和字母如何映射
+2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
+3. 输入1 * #按键等等异常情况
-## 数字和字母如何映射
+## 数字和字母如何映射
可以使用map或者定义一个二位数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下:
@@ -63,7 +69,7 @@ const string letterMap[10] = {
回溯三部曲:
-* 确定回溯函数参数
+* 确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
@@ -78,10 +84,10 @@ const string letterMap[10] = {
```
vector result;
string s;
-void backtracking(const string& digits, int index)
+void backtracking(const string& digits, int index)
```
-* 确定终止条件
+* 确定终止条件
例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
@@ -119,14 +125,15 @@ for (int i = 0; i < letters.size(); i++) {
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!**
-## 输入1 * #按键等等异常情况
+注意:输入1 * #按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
**但是要知道会有这些异常,如果是现场面试中,一定要考虑到!**
-# C++代码
+## C++代码
+
关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的回溯法模板,不难写出如下C++代码:
@@ -217,7 +224,7 @@ public:
};
```
-我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)这篇文章中也深度分析了,回溯隐藏在了哪里。
+我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)这篇文章中也深度分析了,回溯隐藏在了哪里。
所以大家可以按照版本一来写就可以了。
@@ -227,9 +234,24 @@ public:
其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。
-**就酱,如果学到了,就帮Carl转发一波吧,让更多小伙伴知道这里!**
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+## 其他语言版本
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md
index 34430d1c..ff441bf7 100644
--- a/problems/0018.四数之和.md
+++ b/problems/0018.四数之和.md
@@ -1,11 +1,18 @@
-# 题目地址
-https://leetcode-cn.com/problems/4sum/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
> 一样的道理,能解决四数之和
-
> 那么五数之和、六数之和、N数之和呢?
-# 第18题. 四数之和
+# 第18题. 四数之和
+
+https://leetcode-cn.com/problems/4sum/
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
@@ -13,26 +20,26 @@ https://leetcode-cn.com/problems/4sum/
答案中不可以包含重复的四元组。
-示例:
-给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
-满足要求的四元组集合为:
+示例:
+给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
+满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
-# 思路
+# 思路
-四数之和,和[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)是一个思路,都是使用双指针法, 基本解法就是在[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) 的基础上再套一层for循环。
+四数之和,和[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)是一个思路,都是使用双指针法, 基本解法就是在[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) 的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(大家亲自写代码就能感受出来)
[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下表作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
-四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下表作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
+四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下表作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
-那么一样的道理,五数之和、六数之和等等都采用这种解法。
+那么一样的道理,五数之和、六数之和等等都采用这种解法。
对于[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
@@ -57,15 +64,16 @@ https://leetcode-cn.com/problems/4sum/
双指针法在数组和链表中还有很多应用,后面还会介绍到。
-# C++代码
-```
+C++代码
+
+```C++
class Solution {
public:
vector> fourSum(vector& nums, int target) {
vector> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
- // 这种剪枝是错误的,这道题目target 是任意值
+ // 这种剪枝是错误的,这道题目target 是任意值
// if (nums[k] > target) {
// return result;
// }
@@ -104,5 +112,68 @@ public:
};
```
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+
+
+
+## 其他语言版本
+
+
+Java:
+```Java
+class Solution {
+ public List> fourSum(int[] nums, int target) {
+ List> result = new ArrayList<>();
+ Arrays.sort(nums);
+
+ for (int i = 0; i < nums.length; i++) {
+
+ if (i > 0 && nums[i - 1] == nums[i]) {
+ continue;
+ }
+
+ for (int j = i + 1; j < nums.length; j++) {
+
+ if (j > i + 1 && nums[j - 1] == nums[j]) {
+ continue;
+ }
+
+ int left = j + 1;
+ int right = nums.length - 1;
+ while (right > left) {
+ int sum = nums[i] + nums[j] + nums[left] + nums[right];
+ if (sum > target) {
+ right--;
+ } else if (sum < target) {
+ left++;
+ } else {
+ result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
+
+ while (right > left && nums[right] == nums[right - 1]) right--;
+ while (right > left && nums[left] == nums[left + 1]) left++;
+
+ left++;
+ right--;
+ }
+ }
+ }
+ }
+ return result;
+ }
+}
+```
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md
index b332c9de..3b6dde1e 100644
--- a/problems/0019.删除链表的倒数第N个节点.md
+++ b/problems/0019.删除链表的倒数第N个节点.md
@@ -1,6 +1,39 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-## 思路
+
+
+## 19.删除链表的倒数第N个节点
+
+题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
+
+给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
+
+进阶:你能尝试使用一趟扫描实现吗?
+
+示例 1:
+
+
+
+输入:head = [1,2,3,4,5], n = 2
+输出:[1,2,3,5]
+示例 2:
+
+输入:head = [1], n = 1
+输出:[]
+示例 3:
+
+输入:head = [1,2], n = 1
+输出:[1]
+
+
+## 思路
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
@@ -8,25 +41,24 @@
分为如下几步:
-* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)
-
+* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)
* 定义fast指针和slow指针,初始值为虚拟头结点,如图:
-
+
* fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
-
+
-* fast和slow同时移动,之道fast指向末尾,如题:
-
+* fast和slow同时移动,之道fast指向末尾,如题:
+
* 删除slow指向的下一个节点,如图:
-
+
此时不难写出如下C++代码:
-```
+```C++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
@@ -47,3 +79,43 @@ public:
}
};
```
+
+
+## 其他语言版本
+
+java:
+
+```java
+class Solution {
+ public ListNode removeNthFromEnd(ListNode head, int n) {
+ ListNode dummy = new ListNode(-1);
+ dummy.next = head;
+
+ ListNode slow = dummy;
+ ListNode fast = dummy;
+ while (n-- > 0) {
+ fast = fast.next;
+ }
+ // 记住 待删除节点slow 的上一节点
+ ListNode prev = null;
+ while (fast != null) {
+ prev = slow;
+ slow = slow.next;
+ fast = fast.next;
+ }
+ // 上一节点的next指针绕过 待删除节点slow 直接指向slow的下一节点
+ prev.next = slow.next;
+ // 释放 待删除节点slow 的next指针, 这句删掉也能AC
+ slow.next = null;
+
+ return dummy.next;
+ }
+}
+```
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md
index 293c53dd..e8784397 100644
--- a/problems/0020.有效的括号.md
+++ b/problems/0020.有效的括号.md
@@ -1,11 +1,18 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-## 题目地址
-https://leetcode-cn.com/problems/valid-parentheses/
> 数据结构与算法应用往往隐藏在我们看不到的地方
-# 20. 有效的括号
+# 20. 有效的括号
+
+https://leetcode-cn.com/problems/valid-parentheses/
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
@@ -14,27 +21,27 @@ https://leetcode-cn.com/problems/valid-parentheses/
* 左括号必须以正确的顺序闭合。
* 注意空字符串可被认为是有效字符串。
-示例 1:
-输入: "()"
+示例 1:
+输入: "()"
输出: true
-示例 2:
-输入: "()[]{}"
-输出: true
+示例 2:
+输入: "()[]{}"
+输出: true
-示例 3:
-输入: "(]"
-输出: false
+示例 3:
+输入: "(]"
+输出: false
-示例 4:
-输入: "([)]"
-输出: false
+示例 4:
+输入: "([)]"
+输出: false
-示例 5:
-输入: "{[]}"
-输出: true
+示例 5:
+输入: "{[]}"
+输出: true
-# 思路
+# 思路
## 题外话
@@ -52,9 +59,9 @@ cd a/b/c/../../
这个命令最后进入a目录,系统是如何知道进入了a目录呢 ,这就是栈的应用(其实可以出一道相应的面试题了)
-所以栈在计算机领域中应用是非常广泛的。
+所以栈在计算机领域中应用是非常广泛的。
-有的同学经常会想学的这些数据结构有什么用,也开发不了什么软件,大多数同学说的软件应该都是可视化的软件例如APP、网站之类的,那都是非常上层的应用了,底层很多功能的实现都是基础的数据结构和算法。
+有的同学经常会想学的这些数据结构有什么用,也开发不了什么软件,大多数同学说的软件应该都是可视化的软件例如APP、网站之类的,那都是非常上层的应用了,底层很多功能的实现都是基础的数据结构和算法。
**所以数据结构与算法的应用往往隐藏在我们看不到的地方!**
@@ -66,9 +73,9 @@ cd a/b/c/../../
首先要弄清楚,字符串里的括号不匹配有几种情况。
-**一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越远。**
+**一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越乱。**
-建议要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
+建议要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
先来分析一下 这里有三种不匹配的情况,
@@ -83,7 +90,7 @@ cd a/b/c/../../
动画如下:
-
+
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
@@ -98,12 +105,10 @@ cd a/b/c/../../
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
-实现代码如下:
-
-## C++代码
+实现C++代码如下:
-```
+```C++
class Solution {
public:
bool isValid(string s) {
@@ -124,5 +129,27 @@ public:
```
技巧性的东西没有固定的学习方法,还是要多看多练,自己总灵活运用了。
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0021.合并两个有序链表.md b/problems/0021.合并两个有序链表.md
deleted file mode 100644
index 0272272a..00000000
--- a/problems/0021.合并两个有序链表.md
+++ /dev/null
@@ -1,60 +0,0 @@
-> 更多精彩文章持续更新,微信搜索「代码随想录」第一时间围观,本文[https://github.com/youngyangyang04/TechCPP](https://github.com/youngyangyang04/TechCPP) 已经收录,里面有更多干货等着你,欢迎Star!
-
-## 题目地址
-https://leetcode-cn.com/problems/merge-two-sorted-lists/
-
-## 思路
-
-链表的基本操作,一下代码中有详细注释
-
-
-## 代码
-
-```
-/**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * ListNode *next;
- * ListNode() : val(0), next(nullptr) {}
- * ListNode(int x) : val(x), next(nullptr) {}
- * ListNode(int x, ListNode *next) : val(x), next(next) {}
- * };
- */
-// 内存消耗比较大,尝试一下 使用题目中的链表
-class Solution {
-public:
-// 题目中我们为什么定义了一个 新的head
- ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
- ListNode* head = new ListNode();
- ListNode* index = head;
- while(l1 != nullptr && l2 != nullptr) { // 注意null 和nullptr的区别
- if (l1->val < l2->val) {
- index->next = l1;
- l1 = l1->next;
- } else {
- index->next = l2;
- l2 = l2->next;
- }
- index = index->next;
- }
- if (l1 != nullptr) {
- index->next = l1;
- l1 = l1->next;
- }
- if (l2 != nullptr) {
- index->next = l2;
- l2 = l2->next;
- }
- ListNode* tmp = head; // 注意清理内存,不清理也没事
- head = head->next;
- delete tmp;
- return head;
- // return head->next
-
- }
-};
-```
-
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
-
diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md
deleted file mode 100644
index 92f9b294..00000000
--- a/problems/0024.两两交换链表中的节点.md
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-## 题目地址
-
-## 思路
-
-这道题目正常模拟就可以了。
-
-建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
-
-对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)。
-
-接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序**
-
-初始时,cur指向虚拟头结点,然后进行如下三步:
-
-
-
-操作之后,链表如下:
-
-
-
-
-看这个可能就更直观一些了:
-
-
-
-
-对应的C++代码实现如下:
-
-```
-class Solution {
-public:
- ListNode* swapPairs(ListNode* head) {
- ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
- dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
- ListNode* cur = dummyHead;
- while(cur->next != nullptr && cur->next->next != nullptr) {
- ListNode* tmp = cur->next; // 记录临时节点
- ListNode* tmp1 = cur->next->next->next; // 记录临时节点
-
- cur->next = cur->next->next; // 步骤一
- cur->next->next = tmp; // 步骤二
- cur->next->next->next = tmp1; // 步骤三
-
- cur = cur->next->next; // cur移动两位,准备下一轮交换
- }
- return dummyHead->next;
- }
-};
-```
-时间复杂度:O(n)
-空间复杂度:O(1)
-
-## 拓展
-
-**这里还是说一下,大家不必太在意leetcode上执行用时,打败多少多少用户,这个就是一个玩具,非常不准确。**
-
-做题的时候自己能分析出来时间复杂度就可以了,至于leetcode上执行用时,大概看一下就行。
-
-上面的代码我第一次提交执行用时8ms,打败6.5%的用户,差点吓到我了。
-
-心想应该没有更好的方法了吧,也就O(n)的时间复杂度,重复提交几次,这样了:
-
-
-
-所以,不必过于在意leetcode上这个统计。
-
-
-
diff --git a/problems/0026.删除排序数组中的重复项.md b/problems/0026.删除排序数组中的重复项.md
deleted file mode 100644
index 114f2da2..00000000
--- a/problems/0026.删除排序数组中的重复项.md
+++ /dev/null
@@ -1,49 +0,0 @@
-## 题目地址
-https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
-
-# 思路
-
-此题使用双指针法,O(n)的时间复杂度,拼速度的话,可以剪剪枝。
-
-注意题目中:不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
-
-双指针法,动画如下:
-
-
-
-其实**双指针法在在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法,可以将时间复杂度O(n^2)的解法优化为 O(n)的解法,例如:**
-
-* [0015.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md)
-* [0018.四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md)
-* [0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md)
-* [0206.翻转链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0206.翻转链表.md)
-* [0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md)
-* [剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md)
-
-**还有链表找环,也用到双指针:**
-
-* [0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md)
-
-大家都可以去做一做,感受一下双指针法的内在逻辑!
-
-
-# C++ 代码
-
-
-```
-class Solution {
-public:
- int removeDuplicates(vector& nums) {
- if (nums.empty()) return 0; // 别忘记空数组的判断
- int slowIndex = 0;
- for (int fastIndex = 0; fastIndex < (nums.size() - 1); fastIndex++){
- if(nums[fastIndex] != nums[fastIndex + 1]) { // 发现和后一个不相同
- nums[++slowIndex] = nums[fastIndex + 1]; //slowIndex = 0 的数据一定是不重复的,所以直接 ++slowIndex
- }
- }
- return slowIndex + 1; //别忘了slowIndex是从0开始的,所以返回slowIndex + 1
- }
-};
-```
-> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
-
diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md
index 637bd5ad..5cfdb68d 100644
--- a/problems/0027.移除元素.md
+++ b/problems/0027.移除元素.md
@@ -1,18 +1,13 @@
-
-
-
-
-
+
+
-
-
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 移除元素想要高效的话,不是很简单!
-# 编号:27. 移除元素
+## 27. 移除元素
题目地址:https://leetcode-cn.com/problems/remove-element/
@@ -22,38 +17,38 @@
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
-示例 1:
-给定 nums = [3,2,2,3], val = 3,
-函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
-你不需要考虑数组中超出新长度后面的元素。
+示例 1:
+给定 nums = [3,2,2,3], val = 3,
+函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
+你不需要考虑数组中超出新长度后面的元素。
-示例 2:
-给定 nums = [0,1,2,2,3,0,4,2], val = 2,
-函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
+示例 2:
+给定 nums = [0,1,2,2,3,0,4,2], val = 2,
+函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
**你不需要考虑数组中超出新长度后面的元素。**
-# 思路
+## 思路
有的同学可能说了,多余的元素,删掉不就得了。
-**要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。**
+**要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。**
-数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg)。
+数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。
-# 暴力解法
+### 暴力解法
-这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
+这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
删除过程如下:
-
+
很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。
-# 暴力解法C++代码
+代码如下:
-```
+```C++
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
@@ -75,53 +70,84 @@ public:
};
```
-# 双指针法
+* 时间复杂度:$O(n^2)$
+* 空间复杂度:$O(1)$
+
+### 双指针法
双指针法(快慢指针法): **通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
删除过程如下:
-
+
-**双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。**
+**双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。**
-我们来回顾一下,之前已经讲过有四道题目使用了双指针法。
+后序都会一一介绍到,本题代码如下:
-双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下:
-
-* [15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
-* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
-
-双指针来记录前后指针实现链表反转:
-
-* [206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
-
-使用双指针来确定有环:
-
-* [142题.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
-
-双指针法在数组和链表中还有很多应用,后面还会介绍到。
-
-# 双指针法C++代码:
-```
+```C++
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector& nums, int val) {
- int slowIndex = 0;
- for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
- if (val != nums[fastIndex]) {
- nums[slowIndex++] = nums[fastIndex];
+ int slowIndex = 0;
+ for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
+ if (val != nums[fastIndex]) {
+ nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
```
+注意这些实现方法并没有改变元素的相对位置!
-**循序渐进学算法,认准「代码随想录」,Carl手把手带你过关斩将!**
+* 时间复杂度:$O(n)$
+* 空间复杂度:$O(1)$
-
-
-
+旧文链接:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
+
+## 相关题目推荐
+
+* 26.删除排序数组中的重复项
+* 283.移动零
+* 844.比较含退格的字符串
+* 977.有序数组的平方
+
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+JavaScript:
+```
+//时间复杂度O(n)
+//空间复杂度O(1)
+var removeElement = (nums, val) => {
+ let k = 0;
+ for(let i = 0;i < nums.length;i++){
+ if(nums[i] != val){
+ nums[k++] = nums[i]
+ }
+ }
+ return k;
+};
+```
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0028.实现strStr().md b/problems/0028.实现strStr.md
similarity index 69%
rename from problems/0028.实现strStr().md
rename to problems/0028.实现strStr.md
index f6e689d7..f1de00f8 100644
--- a/problems/0028.实现strStr().md
+++ b/problems/0028.实现strStr.md
@@ -1,29 +1,36 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
+> 在一个串中查找是否出现过另一个串,这是KMP的看家本领。
+
+# 28. 实现 strStr()
-## 题目地址
https://leetcode-cn.com/problems/implement-strstr/
-> 在一个串中查找是否出现过另一个串,这是KMP的看家本领。
-
-# 题目:28. 实现 strStr()
-
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
-示例 1:
-输入: haystack = "hello", needle = "ll"
-输出: 2
+示例 1:
+输入: haystack = "hello", needle = "ll"
+输出: 2
-示例 2:
-输入: haystack = "aaaaa", needle = "bba"
-输出: -1
+示例 2:
+输入: haystack = "aaaaa", needle = "bba"
+输出: -1
-说明:
-当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
-对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
+说明:
+当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
+对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
-# 思路
+# 思路
本题是KMP 经典题目。
@@ -108,7 +115,7 @@ next数组就是一个前缀表(prefix table)。
如动画所示:
-
+
动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说道。
@@ -120,22 +127,45 @@ next数组就是一个前缀表(prefix table)。
首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,在重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
-那么什么是前缀表:**记录下表i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
+那么什么是前缀表:**记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
+
+# 最长公共前后缀?
+
+文章中字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
+
+后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
+
+**正确理解什么是前缀什么是后缀很重要。**
+
+那么网上清一色都说 “kmp 最长公共前后缀” 又是什么回事呢?
+
+
+我查了一遍 算法导论 和 算法4里KMP的章节,都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 准确一些。
+
+**因为前缀表要求的就是相同前后缀的长度。**
+
+而最长公共前后缀里面的“公共”,更像是说前缀和后缀公共的长度。这其实并不是前缀表所需要的。
+
+所以字符串a的最长相等前后缀为0。
+字符串aa的最长相等前后缀为1。
+字符串aaa的最长相等前后缀为2。
+等等.....。
+
# 为什么一定要用前缀表
这就是前缀表那为啥就能告诉我们 上次匹配的位置,并跳过去呢?
-回顾一下,刚刚匹配的过程在下表5的地方遇到不匹配,模式串是指向f,如图:
-
+回顾一下,刚刚匹配的过程在下标5的地方遇到不匹配,模式串是指向f,如图:
+
-然后就找到了下表2,指向b,继续匹配:如图:
-
+然后就找到了下标2,指向b,继续匹配:如图:
+
以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!
-**下表5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配就可以了。**
+**下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配就可以了。**
所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。
@@ -147,14 +177,14 @@ next数组就是一个前缀表(prefix table)。
如图:
-
+
长度为前1个字符的子串`a`,最长相同前后缀的长度为0。(注意字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**;**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。)
-
+
长度为前2个字符的子串`aa`,最长相同前后缀的长度为1。
-
+
长度为前3个字符的子串`aab`,最长相同前后缀的长度为0。
以此类推:
@@ -163,13 +193,13 @@ next数组就是一个前缀表(prefix table)。
长度为前6个字符的子串`aabaaf`,最长相同前后缀的长度为0。
那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
-
+
-可以看出模式串与前缀表对应位置的数字表示的就是:**下表i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
+可以看出模式串与前缀表对应位置的数字表示的就是:**下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:
-
+
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
@@ -177,7 +207,7 @@ next数组就是一个前缀表(prefix table)。
所以要看前一位的 前缀表的数值。
-前一个字符的前缀表的数值是2, 所有把下表移动到下表2的位置继续比配。 可以再反复看一下上面的动画。
+前一个字符的前缀表的数值是2, 所有把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
最后就在文本串中找到了和模式串匹配的子串了。
@@ -203,11 +233,10 @@ next数组就可以是前缀表,但是很多实现都是把前缀表统一减
匹配过程动画如下:
-
+
# 时间复杂度分析
-
其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
暴力的解法显而易见是O(n * m),所以**KMP在字符串匹配中极大的提高的搜索的效率。**
@@ -234,7 +263,7 @@ void getNext(int* next, const string& s)
1. 初始化:
-定义两个指针i和j,j指向前缀终止位置(严格来说是终止位置减一的位置),i指向后缀终止位置(与j同理)。
+定义两个指针i和j,j指向前缀起始位置,i指向后缀起始位置。
然后还要对next数组进行初始化赋值,如下:
@@ -255,15 +284,15 @@ next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是
因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。
-所以遍历模式串s的循环下表i 要从 1开始,代码如下:
+所以遍历模式串s的循环下标i 要从 1开始,代码如下:
```
for(int i = 1; i < s.size(); i++) {
```
-如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回溯。
+如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
-怎么回溯呢?
+怎么回退呢?
next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。
@@ -273,7 +302,7 @@ next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度
```
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
- j = next[j]; // 向前回溯
+ j = next[j]; // 向前回退
}
```
@@ -292,13 +321,13 @@ next[i] = j;
最后整体构建next数组的函数代码如下:
-```
+```C++
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
- j = next[j]; // 向前回溯
+ j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
@@ -311,8 +340,7 @@ void getNext(int* next, const string& s){
代码构造next数组的逻辑流程动画如下:
-
-
+
得到了next数组之后,就要用这个来做匹配了。
@@ -320,7 +348,7 @@ void getNext(int* next, const string& s){
在文本串s里 找是否出现过模式串t。
-定义两个下表j 指向模式串起始位置,i指向文本串起始位置。
+定义两个下标j 指向模式串起始位置,i指向文本串起始位置。
那么j初始值依然为-1,为什么呢? **依然因为next数组里记录的起始位置为-1。**
@@ -330,7 +358,7 @@ i就从0开始,遍历文本串,代码如下:
for (int i = 0; i < s.size(); i++)
```
-接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 经行比较。
+接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。
如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。
@@ -364,7 +392,7 @@ if (j == (t.size() - 1) ) {
那么使用next数组,用模式串匹配文本串的整体代码如下:
-```
+```C++
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
@@ -383,7 +411,7 @@ for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
# 前缀表统一减一 C++代码实现
-```
+```C++
class Solution {
public:
void getNext(int* next, const string& s) {
@@ -391,7 +419,7 @@ public:
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
- j = next[j]; // 向前回溯
+ j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
@@ -410,11 +438,11 @@ public:
while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
- if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
+ if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
j++; // i的增加在for循环里
}
if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
- return (i - needle.size() + 1);
+ return (i - needle.size() + 1);
}
}
return -1;
@@ -433,13 +461,13 @@ public:
我给出的getNext的实现为:(前缀表统一减一)
-```
+```C++
void getNext(int* next, const string& s) {
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
- j = next[j]; // 向前回溯
+ j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
@@ -455,12 +483,12 @@ void getNext(int* next, const string& s) {
那么前缀表不减一来构建next数组,代码如下:
-```
+```C++
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
- while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下表的操作
+ while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
@@ -478,20 +506,20 @@ void getNext(int* next, const string& s) {
实现代码如下:
-```
+```C++
class Solution {
public:
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
- for(int i = 1; i < s.size(); i++) {
- while (j > 0 && s[i] != s[j]) {
- j = next[j - 1];
+ for(int i = 1; i < s.size(); i++) {
+ while (j > 0 && s[i] != s[j]) {
+ j = next[j - 1];
}
- if (s[i] == s[j]) {
+ if (s[i] == s[j]) {
j++;
}
- next[i] = j;
+ next[i] = j;
}
}
int strStr(string haystack, string needle) {
@@ -501,14 +529,14 @@ public:
int next[needle.size()];
getNext(next, needle);
int j = 0;
- for (int i = 0; i < haystack.size(); i++) {
- while(j > 0 && haystack[i] != needle[j]) {
- j = next[j - 1];
+ for (int i = 0; i < haystack.size(); i++) {
+ while(j > 0 && haystack[i] != needle[j]) {
+ j = next[j - 1];
}
- if (haystack[i] == needle[j]) {
+ if (haystack[i] == needle[j]) {
j++;
}
- if (j == needle.size() ) {
+ if (j == needle.size() ) {
return (i - needle.size() + 1);
}
}
@@ -532,7 +560,128 @@ public:
可以说把KMP的每一个细微的细节都扣了出来,毫无遮掩的展示给大家了!
+## 其他语言版本
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+Java:
+```java
+// 方法一
+class Solution {
+ public void getNext(int[] next, String s){
+ int j = -1;
+ next[0] = j;
+ for (int i = 1; i=0 && s.charAt(i) != s.charAt(j+1)){
+ j=next[j];
+ }
+
+ if(s.charAt(i)==s.charAt(j+1)){
+ j++;
+ }
+ next[i] = j;
+ }
+ }
+ public int strStr(String haystack, String needle) {
+ if(needle.length()==0){
+ return 0;
+ }
+
+ int[] next = new int[needle.length()];
+ getNext(next, needle);
+ int j = -1;
+ for(int i = 0; i=0 && haystack.charAt(i) != needle.charAt(j+1)){
+ j = next[j];
+ }
+ if(haystack.charAt(i)==needle.charAt(j+1)){
+ j++;
+ }
+ if(j==needle.length()-1){
+ return (i-needle.length()+1);
+ }
+ }
+
+ return -1;
+ }
+}
+```
+
+Python:
+
+```python
+// 方法一
+class Solution:
+ def strStr(self, haystack: str, needle: str) -> int:
+ a=len(needle)
+ b=len(haystack)
+ if a==0:
+ return 0
+ next=self.getnext(a,needle)
+ p=-1
+ for j in range(b):
+ while p>=0 and needle[p+1]!=haystack[j]:
+ p=next[p]
+ if needle[p+1]==haystack[j]:
+ p+=1
+ if p==a-1:
+ return j-a+1
+ return -1
+
+ def getnext(self,a,needle):
+ next=['' for i in range(a)]
+ k=-1
+ next[0]=k
+ for i in range(1,len(needle)):
+ while (k>-1 and needle[k+1]!=needle[i]):
+ k=next[k]
+ if needle[k+1]==needle[i]:
+ k+=1
+ next[i]=k
+ return next
+```
+
+```python
+// 方法二
+class Solution:
+ def strStr(self, haystack: str, needle: str) -> int:
+ a=len(needle)
+ b=len(haystack)
+ if a==0:
+ return 0
+ i=j=0
+ next=self.getnext(a,needle)
+ while(i
diff --git a/problems/0031.下一个排列.md b/problems/0031.下一个排列.md
deleted file mode 100644
index 68665d6b..00000000
--- a/problems/0031.下一个排列.md
+++ /dev/null
@@ -1,58 +0,0 @@
-
-## 思路
-
-一些同学可能手动写排列的顺序,都没有写对,那么写程序的话思路一定是有问题的了,我这里以1234为例子,把全排列都列出来。可以参考一下规律所在:
-
-```
-1 2 3 4
-1 2 4 3
-1 3 2 4
-1 3 4 2
-1 4 2 3
-1 4 3 2
-2 1 3 4
-2 1 4 3
-2 3 1 4
-2 3 4 1
-2 4 1 3
-2 4 3 1
-3 1 2 4
-3 1 4 2
-3 2 1 4
-3 2 4 1
-3 4 1 2
-3 4 2 1
-4 1 2 3
-4 1 3 2
-4 2 1 3
-4 2 3 1
-4 3 1 2
-4 3 2 1
-```
-
-如图:
-
-以求1243为例,流程如图:
-
-
-
-对应的C++代码如下:
-
-```
-class Solution {
-public:
- void nextPermutation(vector& nums) {
- for (int i = nums.size() - 1; i >= 0; i--) {
- for (int j = nums.size() - 1; j > i; j--) {
- if (nums[j] > nums[i]) {
- swap(nums[j], nums[i]);
- sort(nums.begin() + i + 1, nums.end());
- return;
- }
- }
- }
- // 到这里了说明整个数组都是倒叙了,反转一下便可
- reverse(nums.begin(), nums.end());
- }
-};
-```
diff --git a/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md b/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md
deleted file mode 100644
index a62261e9..00000000
--- a/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md
+++ /dev/null
@@ -1,134 +0,0 @@
-> **如果对二分查找比较模糊,建议看这篇:[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q),这里详细介绍了二分的两种写法,以及循环不变量的重要性,顺便还可以把「leetcode:35.搜索插入位置」题目刷了**。
-
-这道题目如果基础不是很好,不建议大家看简短的代码,简短的代码隐藏了太多逻辑,结果就是稀里糊涂把题AC了,但是没有想清楚具体细节!
-
-下面我来把所有情况都讨论一下。
-
-寻找target在数组里的左右边界,有如下三种情况:
-
-* 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
-* 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
-* 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
-
-这三种情况都考虑到,说明就想的很清楚了。
-
-接下来,在去寻找左边界,和右边界了。
-
-采用二分法来取寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
-
-**刚刚接触二分搜索的同学不建议上来就像如果用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界**
-
-## 寻找右边界
-
-先来寻找右边界,至于二分查找,如果看过[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚**循环不变量**,很容易区分两种写法。
-
-那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭又闭的区间(如果这里有点看不懂了,强烈建议把[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)这篇文章先看了,在把「leetcode:35.搜索插入位置」做了之后在做这道题目就好很多了)
-
-确定好:计算出来的右边界是不包好target的右边界,左边界同理。
-
-可以写出如下代码
-
-```
-// 二分查找,寻找target的右边界(不包括target)
-// 如果rightBorder为没有被赋值(即target在数组范围的左边,例如数组[3,3],target为2),为了处理情况一
-int getRightBorder(vector& nums, int target) {
- int left = 0;
- int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
- int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
- while (left <= right) { // 当left==right,区间[left, right]依然有效
- int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
- if (nums[middle] > target) {
- right = middle - 1; // target 在左区间,所以[left, middle - 1]
- } else { // 当nums[middle] == target的时候,更新left,这样才能得到target的右边界
- left = middle + 1;
- rightBorder = left;
- }
- }
- return rightBorder;
-}
-```
-
-## 寻找左边界
-
-```
-// 二分查找,寻找target的左边界leftBorder(不包括target)
-// 如果leftBorder没有被赋值(即target在数组范围的右边,例如数组[3,3],target为4),为了处理情况一
-int getLeftBorder(vector& nums, int target) {
- int left = 0;
- int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
- int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
- while (left <= right) {
- int middle = left + ((right - left) / 2);
- if (nums[middle] >= target) { // 寻找左边界,就要在nums[middle] == target的时候更新right
- right = middle - 1;
- leftBorder = right;
- } else {
- left = middle + 1;
- }
- }
- return leftBorder;
-}
-```
-
-## 处理三种情况
-
-左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了
-
-```
-class Solution {
-public:
- vector searchRange(vector& nums, int target) {
- int leftBorder = getLeftBorder(nums, target);
- int rightBorder = getRightBorder(nums, target);
- // 情况一
- if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
- // 情况三
- if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
- // 情况二
- return {-1, -1};
- }
-private:
- int getRightBorder(vector& nums, int target) {
- int left = 0;
- int right = nums.size() - 1;
- int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
- while (left <= right) {
- int middle = left + ((right - left) / 2);
- if (nums[middle] > target) {
- right = middle - 1;
- } else { // 寻找右边界,nums[middle] == target的时候更新left
- left = middle + 1;
- rightBorder = left;
- }
- }
- return rightBorder;
- }
- int getLeftBorder(vector& nums, int target) {
- int left = 0;
- int right = nums.size() - 1;
- int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
- while (left <= right) {
- int middle = left + ((right - left) / 2);
- if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
- right = middle - 1;
- leftBorder = right;
- } else {
- left = middle + 1;
- }
- }
- return leftBorder;
- }
-};
-```
-
-这份代码在简洁性很有大的优化空间,例如把寻找左右区间函数合并一起。
-
-但拆开更清晰一些,而且把三种情况以及对应的处理逻辑完整的展现出来了。
-
-# 总结
-
-初学者建议大家一块一块的去分拆这道题目,正如本题解描述,想清楚三种情况之后,先专注于寻找右区间,然后专注于寻找左区间,左右根据左右区间做最后判断。
-
-不要上来就想如果一起寻找左右区间,搞着搞着就会顾此失彼,绕进去拔不出来了。
-
-
diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md
index 5c6a1545..5bd3b553 100644
--- a/problems/0035.搜索插入位置.md
+++ b/problems/0035.搜索插入位置.md
@@ -1,20 +1,15 @@
-
-
-
-
-
-
+
+
-
-
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 二分查找法是数组里的常用方法,彻底掌握它是十分必要的。
-# 编号35:搜索插入位置
+
+# 35.搜索插入位置
题目地址:https://leetcode-cn.com/problems/search-insert-position/
@@ -22,23 +17,23 @@
你可以假设数组中无重复元素。
-示例 1:
-输入: [1,3,5,6], 5
-输出: 2
+示例 1:
+输入: [1,3,5,6], 5
+输出: 2
-示例 2:
-输入: [1,3,5,6], 2
-输出: 1
+示例 2:
+输入: [1,3,5,6], 2
+输出: 1
-示例 3:
-输入: [1,3,5,6], 7
+示例 3:
+输入: [1,3,5,6], 7
输出: 4
-示例 4:
-输入: [1,3,5,6], 0
-输出: 0
+示例 4:
+输入: [1,3,5,6], 0
+输出: 0
-# 思路
+# 思路
这道题目不难,但是为什么通过率相对来说并不高呢,我理解是大家对边界处理的判断有所失误导致的。
@@ -68,20 +63,20 @@ public:
for (int i = 0; i < nums.size(); i++) {
// 分别处理如下三种情况
// 目标值在数组所有元素之前
- // 目标值等于数组中某一个元素
- // 目标值插入数组中的位置
+ // 目标值等于数组中某一个元素
+ // 目标值插入数组中的位置
if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
return i;
}
}
- // 目标值在数组所有元素之后的情况
+ // 目标值在数组所有元素之后的情况
return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度
}
};
```
-时间复杂度:O(n)
-时间复杂度:O(1)
+时间复杂度:O(n)
+时间复杂度:O(1)
效率如下:
@@ -89,7 +84,7 @@ public:
## 二分法
-既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
+既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。

@@ -107,9 +102,9 @@ public:
相信很多同学对二分查找法中边界条件处理不好。
-例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢?
+例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢?
-这里弄不清楚主要是因为**对区间的定义没有想清楚,这就是不变量**。
+这里弄不清楚主要是因为**对区间的定义没有想清楚,这就是不变量**。
要在二分查找的过程中,保持不变量,这也就是**循环不变量** (感兴趣的同学可以查一查)。
@@ -127,7 +122,7 @@ public:
int searchInsert(vector& nums, int target) {
int n = nums.size();
int left = 0;
- int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
+ int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
@@ -147,7 +142,7 @@ public:
}
};
```
-时间复杂度:O(logn)
+时间复杂度:O(logn)
时间复杂度:O(1)
效率如下:
@@ -190,10 +185,10 @@ public:
};
```
-时间复杂度:O(logn)
+时间复杂度:O(logn)
时间复杂度:O(1)
-# 总结
+# 总结
希望通过这道题目,大家会发现平时写二分法,为什么总写不好,就是因为对区间定义不清楚。
@@ -203,6 +198,27 @@ public:
**循序渐进学算法,认准「代码随想录」,Carl手把手带你过关斩将!**
-
-
-
+
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md
index 4e3a74e2..75e2d3cd 100644
--- a/problems/0037.解数独.md
+++ b/problems/0037.解数独.md
@@ -1,35 +1,42 @@
-> 解数独,理解二维递归是关键
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg)
-# 37. 解数独
+## 37. 解数独
题目地址:https://leetcode-cn.com/problems/sudoku-solver/
编写一个程序,通过填充空格来解决数独问题。
-一个数独的解法需遵循如下规则:
-数字 1-9 在每一行只能出现一次。
-数字 1-9 在每一列只能出现一次。
-数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
-空白格用 '.' 表示。
+一个数独的解法需遵循如下规则:
+数字 1-9 在每一行只能出现一次。
+数字 1-9 在每一列只能出现一次。
+数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
+空白格用 '.' 表示。

-一个数独。
+一个数独。
-
+
-答案被标成红色。
+答案被标成红色。
-提示:
+提示:
* 给定的数独序列只包含数字 1-9 和字符 '.' 。
* 你可以假设给定的数独只有唯一解。
* 给定数独永远是 9x9 形式的。
-# 思路
+## 思路
-棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是**二维递归**。
+棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是**二维递归**。
怎么做二维递归呢?
@@ -46,9 +53,9 @@

-## 回溯三部曲
+## 回溯三部曲
-* 递归函数以及参数
+* 递归函数以及参数
**递归函数的返回值需要是bool类型,为什么呢?**
@@ -60,15 +67,15 @@
bool backtracking(vector>& board)
```
-* 递归终止条件
+* 递归终止条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
-**不用终止条件会不会死循环?**
+**不用终止条件会不会死循环?**
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
-**那么有没有永远填不满的情况呢?**
+**那么有没有永远填不满的情况呢?**
这个问题我在递归单层搜索逻辑里在来讲!
@@ -89,7 +96,7 @@ bool backtracking(vector>& board) {
for (int j = 0; j < board[0].size(); j++) { // 遍历列
if (board[i][j] != '.') continue;
for (char k = '1'; k <= '9'; k++) { // (i, j) 这个位置放k是否合适
- if (isValid(i, j, k, board)) {
+ if (isValid(i, j, k, board)) {
board[i][j] = k; // 放置k
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
board[i][j] = '.'; // 回溯,撤销k
@@ -104,16 +111,16 @@ bool backtracking(vector>& board) {
**注意这里return false的地方,这里放return false 是有讲究的**。
-因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
+因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
-那么会直接返回, **这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!**
+那么会直接返回, **这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!**
-## 判断棋盘是否合法
+## 判断棋盘是否合法
判断棋盘是否合法有如下三个维度:
-* 同行是否重复
-* 同列是否重复
+* 同行是否重复
+* 同列是否重复
* 9宫格里是否重复
代码如下:
@@ -145,7 +152,7 @@ bool isValid(int row, int col, char val, vector>& board) {
最后整体代码如下:
-# C++代码
+## C++代码
```C++
class Solution {
@@ -155,7 +162,7 @@ bool backtracking(vector>& board) {
for (int j = 0; j < board[0].size(); j++) { // 遍历列
if (board[i][j] != '.') continue;
for (char k = '1'; k <= '9'; k++) { // (i, j) 这个位置放k是否合适
- if (isValid(i, j, k, board)) {
+ if (isValid(i, j, k, board)) {
board[i][j] = k; // 放置k
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
board[i][j] = '.'; // 回溯,撤销k
@@ -195,7 +202,7 @@ public:
};
```
-# 总结
+## 总结
解数独可以说是非常难的题目了,如果还一直停留在单层递归的逻辑中,这道题目可以让大家瞬间崩溃。
@@ -207,9 +214,23 @@ public:
**恭喜一路上坚持打卡的录友们,回溯算法已经接近尾声了,接下来就是要一波总结了**。
-如果一直跟住「代码随想录」的节奏,你会发现自己进步飞快,从思维方式到刷题习惯,都会有质的飞跃,「代码随想录」绝对值得推荐给身边的同学朋友们!
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+## 其他语言版本
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md
index 509ad28f..f983a994 100644
--- a/problems/0039.组合总和.md
+++ b/problems/0039.组合总和.md
@@ -1,7 +1,13 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 看懂很容易,彻底掌握需要下功夫
-# 第39题. 组合总和
+## 39. 组合总和
题目链接:https://leetcode-cn.com/problems/combination-sum/
@@ -9,29 +15,32 @@
candidates 中的数字可以无限制重复被选取。
-说明:
+说明:
-* 所有数字(包括 target)都是正整数。
+* 所有数字(包括 target)都是正整数。
* 解集不能包含重复的组合。
-示例 1:
-输入:candidates = [2,3,6,7], target = 7,
-所求解集为:
-[
- [7],
- [2,2,3]
-]
+示例 1:
+输入:candidates = [2,3,6,7], target = 7,
+所求解集为:
+[
+ [7],
+ [2,2,3]
+]
-示例 2:
-输入:candidates = [2,3,5], target = 8,
-所求解集为:
-[
- [2,2,2,2],
- [2,3,3],
- [3,5]
-]
+示例 2:
+输入:candidates = [2,3,5], target = 8,
+所求解集为:
+[
+ [2,2,2,2],
+ [2,3,3],
+ [3,5]
+]
+
+## 思路
+
+[B站视频讲解-组合总和](https://www.bilibili.com/video/BV1KT4y1M7HJ)
-# 思路
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。
@@ -39,15 +48,14 @@ candidates 中的数字可以无限制重复被选取。
本题搜索的过程抽象成树形结构如下:
-
-
+
注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
而在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) 中都可以知道要递归K层,因为要取k个元素的组合。
## 回溯三部曲
-* 递归函数参数
+* 递归函数参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)
@@ -65,23 +73,23 @@ candidates 中的数字可以无限制重复被选取。
代码如下:
-```
+```C++
vector> result;
vector path;
-void backtracking(vector& candidates, int target, int sum, int startIndex)
+void backtracking(vector& candidates, int target, int sum, int startIndex)
```
* 递归终止条件
在如下树形结构中:
-
+
从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
sum等于target的时候,需要收集结果,代码如下:
-```
+```C++
if (sum > target) {
return;
}
@@ -91,7 +99,7 @@ if (sum == target) {
}
```
-* 单层搜索的逻辑
+* 单层搜索的逻辑
单层for循环依然是从startIndex开始,搜索candidates集合。
@@ -99,7 +107,7 @@ if (sum == target) {
如何重复选取呢,看代码,注释部分:
-```
+```C++
for (int i = startIndex; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
@@ -111,7 +119,7 @@ for (int i = startIndex; i < candidates.size(); i++) {
按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的模板,不难写出如下C++完整代码:
-```
+```C++
// 版本一
class Solution {
private:
@@ -144,11 +152,11 @@ public:
};
```
-## 剪枝优化
+## 剪枝优化
在这个树形结构中:
-
+
以及上面的版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。
@@ -160,18 +168,18 @@ public:
如图:
-
+
for循环剪枝代码如下:
```
-for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
+for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
```
整体代码如下:(注意注释的部分)
-```
+```C++
class Solution {
private:
vector> result;
@@ -203,12 +211,12 @@ public:
};
```
-# 总结
+## 总结
本题和我们之前讲过的[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)有两点不同:
-* 组合没有数量要求
-* 元素可无限重复选取
+* 组合没有数量要求
+* 元素可无限重复选取
针对这两个问题,我都做了详细的分析。
@@ -220,9 +228,26 @@ public:
可以看出我写的文章都会大量引用之前的文章,就是要不断作对比,分析其差异,然后给出代码解决的方法,这样才能彻底理解题目的本质与难点。
-**就酱,如果感觉很给力,就帮Carl宣传一波吧,哈哈**。
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md
index 40179710..ffcbe212 100644
--- a/problems/0040.组合总和II.md
+++ b/problems/0040.组合总和II.md
@@ -1,6 +1,15 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
> 这篇可以说是全网把组合问题如何去重,讲的最清晰的了!
-# 40.组合总和II
+## 40.组合总和II
题目链接:https://leetcode-cn.com/problems/combination-sum-ii/
@@ -8,42 +17,42 @@
candidates 中的每个数字在每个组合中只能使用一次。
-说明:
-所有数字(包括目标数)都是正整数。
-解集不能包含重复的组合。
+说明:
+所有数字(包括目标数)都是正整数。
+解集不能包含重复的组合。
-示例 1:
-输入: candidates = [10,1,2,7,6,1,5], target = 8,
-所求解集为:
-[
- [1, 7],
- [1, 2, 5],
- [2, 6],
- [1, 1, 6]
-]
-
-示例 2:
-输入: candidates = [2,5,2,1,2], target = 5,
-所求解集为:
-[
- [1,2,2],
- [5]
-]
+示例 1:
+输入: candidates = [10,1,2,7,6,1,5], target = 8,
+所求解集为:
+[
+ [1, 7],
+ [1, 2, 5],
+ [2, 6],
+ [1, 1, 6]
+]
-# 思路
+示例 2:
+输入: candidates = [2,5,2,1,2], target = 5,
+所求解集为:
+[
+ [1,2,2],
+ [5]
+]
+
+## 思路
这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别:
1. 本题candidates 中的每个数字在每个组合中只能使用一次。
-2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是无重复元素的数组candidates
+2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是无重复元素的数组candidates
-最后本题和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求一样,解集不能包含重复的组合。
+最后本题和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求一样,解集不能包含重复的组合。
**本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合**。
一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
-所以要在搜索的过程中就去掉重复组合。
+所以要在搜索的过程中就去掉重复组合。
很多同学在去重的问题上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
@@ -68,17 +77,17 @@ candidates 中的每个数字在每个组合中只能使用一次。
可以看到图中,每个节点相对于 [39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组,这个used数组下面会重点介绍。
-## 回溯三部曲
+## 回溯三部曲
-* **递归函数参数**
+* **递归函数参数**
-与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
+与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
代码如下:
-```
+```C++
vector> result; // 存放组合集合
vector path; // 符合条件的组合
void backtracking(vector& candidates, int target, int sum, int startIndex, vector& used) {
@@ -90,8 +99,8 @@ void backtracking(vector& candidates, int target, int sum, int startIndex,
代码如下:
-```
-if (sum > target) { // 这个条件其实可以省略
+```C++
+if (sum > target) { // 这个条件其实可以省略
return;
}
if (sum == target) {
@@ -118,14 +127,14 @@ if (sum == target) {
我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:
-* used[i - 1] == true,说明同一树支candidates[i - 1]使用过
+* used[i - 1] == true,说明同一树支candidates[i - 1]使用过
* used[i - 1] == false,说明同一树层candidates[i - 1]使用过
**这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!**
那么单层搜索的逻辑代码如下:
-```
+```C++
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树支candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
@@ -149,7 +158,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
回溯三部曲分析完了,整体C++代码如下:
-```
+```C++
class Solution {
private:
vector> result;
@@ -190,7 +199,47 @@ public:
```
-# 总结
+## 补充
+
+这里直接用startIndex来去重也是可以的, 就不用used数组了。
+
+```C++
+class Solution {
+private:
+ vector> result;
+ vector path;
+ void backtracking(vector& candidates, int target, int sum, int startIndex) {
+ if (sum == target) {
+ result.push_back(path);
+ return;
+ }
+ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
+ // 要对同一树层使用过的元素进行跳过
+ if (i > startIndex && candidates[i] == candidates[i - 1]) {
+ continue;
+ }
+ sum += candidates[i];
+ path.push_back(candidates[i]);
+ backtracking(candidates, target, sum, i + 1); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
+ sum -= candidates[i];
+ path.pop_back();
+ }
+ }
+
+public:
+ vector> combinationSum2(vector& candidates, int target) {
+ path.clear();
+ result.clear();
+ // 首先把给candidates排序,让其相同的元素都挨在一起。
+ sort(candidates.begin(), candidates.end());
+ backtracking(candidates, target, 0, 0);
+ return result;
+ }
+};
+
+```
+
+## 总结
本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)难度提升了不少。
@@ -198,9 +247,26 @@ public:
所以Carl有必要把去重的这块彻彻底底的给大家讲清楚,**就连“树层去重”和“树枝去重”都是我自创的词汇,希望对大家理解有帮助!**
-**就酱,如果感觉「代码随想录」诚意满满,就帮Carl宣传一波吧,感谢啦!**
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md
deleted file mode 100644
index 05cf9e08..00000000
--- a/problems/0042.接雨水.md
+++ /dev/null
@@ -1,340 +0,0 @@
-# 题目链接
-
-# 思路
-
-接雨水问题在面试中还是常见题目的,有必要好好讲一讲。
-
-本文深度讲解如下三种方法:
-* 双指针法
-* 动态规划
-* 单调栈
-
-## 双指针解法
-
-这道题目暴力解法并不简单,我们来看一下思路。
-
-首先要明确,要按照行来计算,还是按照列来计算。如图所示:
-
-
-
-
-
-一些同学在实现暴力解法的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
-
-我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。
-
-首先,**如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。**
-
-可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
-
-这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:
-
-
-
-列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
-
-列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。
-
-列4 柱子的高度为1(以下用height表示)
-
-那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。
-
-列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。
-
-此时求出了列4的雨水体积。
-
-一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
-
-首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
-```
-for (int i = 0; i < height.size(); i++) {
- // 第一个柱子和最后一个柱子不接雨水
- if (i == 0 || i == height.size() - 1) continue;
-}
-```
-
-在for循环中求左右两边最高柱子,代码如下:
-
-```
-int rHeight = height[i]; // 记录右边柱子的最高高度
-int lHeight = height[i]; // 记录左边柱子的最高高度
-for (int r = i + 1; r < height.size(); r++) {
- if (height[r] > rHeight) rHeight = height[r];
-}
-for (int l = i - 1; l >= 0; l--) {
- if (height[l] > lHeight) lHeight = height[l];
-}
-```
-
-最后,计算该列的雨水高度,代码如下:
-
-```
-int h = min(lHeight, rHeight) - height[i];
-if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中
-```
-
-整体代码如下:
-
-```
-class Solution {
-public:
- int trap(vector& height) {
- int sum = 0;
- for (int i = 0; i < height.size(); i++) {
- // 第一个柱子和最后一个柱子不接雨水
- if (i == 0 || i == height.size() - 1) continue;
-
- int rHeight = height[i]; // 记录右边柱子的最高高度
- int lHeight = height[i]; // 记录左边柱子的最高高度
- for (int r = i + 1; r < height.size(); r++) {
- if (height[r] > rHeight) rHeight = height[r];
- }
- for (int l = i - 1; l >= 0; l--) {
- if (height[l] > lHeight) lHeight = height[l];
- }
- int h = min(lHeight, rHeight) - height[i];
- if (h > 0) sum += h;
- }
- return sum;
- }
-};
-```
-
-因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2)。
-空间复杂度为O(1)。
-
-## 动态规划解法
-
-在上面的双指针解法,我们可以看到,只要知道左边柱子的最高高度 和 记录右边柱子的最高高度,就可以计算当前位置的雨水面积,这也是也列来计算的。
-
-即,当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度
-
-为了的到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一波。
-
-这其实是有重复计算的。
-
-我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)。
-
-避免的重复计算,者就用到了动态规划。
-
-当前位置,左边的最高高度,是前一个位置的最高高度和本高度的最大值。
-
-即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
-
-从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
-
-这样就找到递推公式。
-
-是不是地推公式还挺简单的,其实动态规划就是这样,只要想到了递推公式,其实就比较简单了。
-
-代码如下:
-
-```
-class Solution {
-public:
- int trap(vector& height) {
- if (height.size() <= 2) return 0;
- vector maxLeft(height.size(), 0);
- vector maxRight(height.size(), 0);
- int size = maxRight.size();
-
- // 记录每个柱子左边柱子最大高度
- maxLeft[0] = height[0];
- for (int i = 1; i < size; i++) {
- maxLeft[i] = max(height[i], maxLeft[i - 1]);
- }
- // 记录每个柱子右边柱子最大高度
- maxRight[size - 1] = height[size - 1];
- for (int i = size - 2; i >= 0; i--) {
- maxRight[i] = max(height[i], maxRight[i + 1]);
- }
- // 求和
- int sum = 0;
- for (int i = 0; i < size; i++) {
- int count = min(maxLeft[i], maxRight[i]) - height[i];
- if (count > 0) sum += count;
- }
- return sum;
- }
-};
-```
-
-## 单调栈解法
-
-这个解法可以说是最不好理解的了,所以下面我花了大量的篇幅来介绍这种方法。
-
-单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)一样,需要我们自己维持顺序,没有现成的容器可以用。
-
-
-### 准备工作
-
-那么本题使用单调栈有如下几个问题:
-
-1. 首先单调栈是按照行方向来计算雨水,如图:
-
-
-
-知道这一点,后面的就可以理解了。
-
-2. 使用单调栈内元素的顺序
-
-从大到小还是从小打到呢?
-
-要从栈底到栈头(元素从栈头弹出)是从大到小的顺序。
-
-因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
-
-如图:
-
-
-
-
-3. 遇到相同高度的柱子怎么办。
-
-遇到相同的元素,更新栈内下表,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
-
-例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
-
-因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
-
-如图所示:
-
-
-
-
-
-4. 栈里要保存什么数值
-
-是用单调栈,其实是通过 长 * 宽 来计算雨水面积的。
-
-长就是通过柱子的高度来计算,宽是通过柱子之间的下表来计算,
-
-那么栈里有没有必要存一个pair类型的元素,保存柱子的高度和下表呢。
-
-其实不用,栈里就存放int类型的元素就行了,表示下表,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下表对应的高度了。
-
-所以栈的定义如下:
-
-```
-stack st; // 存着下标,计算的时候用下标对应的柱子高度
-```
-
-明确了如上几点,我们再来看处理逻辑。
-
-### 单调栈处理逻辑
-
-先将下表0的柱子加入到栈中,`st.push(0);`。
-
-然后开始从下表1开始遍历所有的柱子,`for (int i = 1; i < height.size(); i++)`。
-
-如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从大到小的顺序(从栈底到栈头)。
-
-代码如下:
-
-```
-if (height[i] < height[st.top()]) st.push(i);
-```
-
-如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
-
-代码如下:
-
-```
-if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
- st.pop();
- st.push(i);
-}
-```
-
-如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
-
-
-
-取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下表记为mid,对应的高度为height[mid](就是图中的高度1)。
-
-栈顶元素st.top(),就是凹槽的左边位置,下表为st.top(),对应的高度为height[st.top()](就是图中的高度2)。
-
-当前遍历的元素i,就是凹槽右边的位置,下表为i,对应的高度为height[i](就是图中的高度3)。
-
-那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
-
-雨水的宽度是 凹槽右边的下表 - 凹槽左边的下表 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;`
-
-当前凹槽雨水的体积就是:`h * w`。
-
-求当前凹槽雨水的体积代码如下:
-
-```
-while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续跟新栈顶元素
- int mid = st.top();
- st.pop();
- if (!st.empty()) {
- int h = min(height[st.top()], height[i]) - height[mid];
- int w = i - st.top() - 1; // 注意减一,只求中间宽度
- sum += h * w;
- }
-}
-```
-
-关键部分讲完了,整体代码如下:
-
-```
-class Solution {
-public:
- int trap(vector& height) {
- if (height.size() <= 2) return 0; // 可以不加
- stack st; // 存着下标,计算的时候用下标对应的柱子高度
- st.push(0);
- int sum = 0;
- for (int i = 1; i < height.size(); i++) {
- if (height[i] < height[st.top()]) { // 情况一
- st.push(i);
- } if (height[i] == height[st.top()]) { // 情况二
- st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
- st.push(i);
- } else { // 情况三
- while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
- int mid = st.top();
- st.pop();
- if (!st.empty()) {
- int h = min(height[st.top()], height[i]) - height[mid];
- int w = i - st.top() - 1; // 注意减一,只求中间宽度
- sum += h * w;
- }
- }
- st.push(i);
- }
- }
- return sum;
- }
-};
-```
-
-以上代码冗余了一些,但是思路是清晰的,下面我将代码精简一下,如下:
-
-```
-class Solution {
-public:
- int trap(vector& height) {
- stack st;
- st.push(0);
- int sum = 0;
- for (int i = 1; i < height.size(); i++) {
- while (!st.empty() && height[i] > height[st.top()]) {
- int mid = st.top();
- st.pop();
- if (!st.empty()) {
- int h = min(height[st.top()], height[i]) - height[mid];
- int w = i - st.top() - 1;
- sum += h * w;
- }
- }
- st.push(i);
- }
- return sum;
- }
-};
-```
-
-精简之后的代码,大家就看不出去三种情况的处理了,貌似好像只处理的情况三,其实是把情况一和情况二融合了。 这样的代码不太利于理解。
-
diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md
index 2474b6d1..2def83a9 100644
--- a/problems/0045.跳跃游戏II.md
+++ b/problems/0045.跳跃游戏II.md
@@ -1,31 +1,40 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
> 相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不少,做好心里准备!
-# 45.跳跃游戏II
+## 45.跳跃游戏II
题目地址:https://leetcode-cn.com/problems/jump-game-ii/
-给定一个非负整数数组,你最初位于数组的第一个位置。
+给定一个非负整数数组,你最初位于数组的第一个位置。
-数组中的每个元素代表你在该位置可以跳跃的最大长度。
-
-你的目标是使用最少的跳跃次数到达数组的最后一个位置。
+数组中的每个元素代表你在该位置可以跳跃的最大长度。
-示例:
-输入: [2,3,1,1,4]
-输出: 2
-解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
+你的目标是使用最少的跳跃次数到达数组的最后一个位置。
-说明:
-假设你总是可以到达数组的最后一个位置。
+示例:
+输入: [2,3,1,1,4]
+输出: 2
+解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
+
+说明:
+假设你总是可以到达数组的最后一个位置。
-# 思路
+## 思路
本题相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。
但思路是相似的,还是要看最大覆盖范围。
-本题要计算最小步数,那么就要想清楚什么时候步数才一定要加一呢?
+本题要计算最小步数,那么就要想清楚什么时候步数才一定要加一呢?
贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数。
@@ -50,7 +59,7 @@
这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
* 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
-* 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
+* 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
C++代码如下:(详细注释)
@@ -76,7 +85,7 @@ public:
return ans;
}
};
-```
+```
## 方法二
@@ -103,8 +112,8 @@ class Solution {
public:
int jump(vector& nums) {
int curDistance = 0; // 当前覆盖的最远距离下标
- int ans = 0; // 记录走的最大步数
- int nextDistance = 0; // 下一步覆盖的最远距离下标
+ int ans = 0; // 记录走的最大步数
+ int nextDistance = 0; // 下一步覆盖的最远距离下标
for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1,这是关键所在
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖的最远距离下标
if (i == curDistance) { // 遇到当前覆盖的最远距离下标
@@ -117,11 +126,11 @@ public:
};
```
-可以看出版本二的代码相对于版本一简化了不少!
+可以看出版本二的代码相对于版本一简化了不少!
其精髓在于控制移动下标i只移动到nums.size() - 2的位置,所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑别的了。
-# 总结
+## 总结
相信大家可以发现,这道题目相当于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。
@@ -129,10 +138,23 @@ public:
理解本题的关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
-就酱,如果感觉「代码随想录」很不错,就分享给身边的朋友同学吧!
+
+## 其他语言版本
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
+Java:
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md
index 7e1b87a2..5f7b1ac0 100644
--- a/problems/0046.全排列.md
+++ b/problems/0046.全排列.md
@@ -1,25 +1,31 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 开始排列问题
-# 46.全排列
+## 46.全排列
题目链接:https://leetcode-cn.com/problems/permutations/
-给定一个 没有重复 数字的序列,返回其所有可能的全排列。
-
-示例:
-输入: [1,2,3]
-输出:
-[
- [1,2,3],
- [1,3,2],
- [2,1,3],
- [2,3,1],
- [3,1,2],
- [3,2,1]
-]
+给定一个 没有重复 数字的序列,返回其所有可能的全排列。
-## 思路
+示例:
+输入: [1,2,3]
+输出:
+[
+ [1,2,3],
+ [1,3,2],
+ [2,1,3],
+ [2,3,1],
+ [3,1,2],
+ [3,2,1]
+]
+
+## 思路
此时我们已经学习了[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。
@@ -33,9 +39,9 @@

-## 回溯三部曲
+## 回溯三部曲
-* 递归函数参数
+* 递归函数参数
**首先排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方**。
@@ -53,13 +59,13 @@ vector path;
void backtracking (vector& nums, vector& used)
```
-* 递归终止条件
+* 递归终止条件

可以看出叶子节点,就是收割结果的地方。
-那么什么时候,算是到达叶子节点呢?
+那么什么时候,算是到达叶子节点呢?
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
@@ -73,7 +79,7 @@ if (path.size() == nums.size()) {
}
```
-* 单层搜索的逻辑
+* 单层搜索的逻辑
这里和[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。
@@ -96,9 +102,8 @@ for (int i = 0; i < nums.size(); i++) {
整体C++代码如下:
-## C++代码
-```
+```C++
class Solution {
public:
vector> result;
@@ -128,18 +133,32 @@ public:
};
```
-# 总结
+## 总结
大家此时可以感受出排列问题的不同:
-* 每层都是从0开始搜索而不是startIndex
+* 每层都是从0开始搜索而不是startIndex
* 需要used数组记录path里都放了哪些元素了
排列问题是回溯算法解决的经典题目,大家可以好好体会体会。
-就酱,如果感觉「代码随想录」诚意满满,就帮Carl宣传一波吧!
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+## 其他语言版本
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md
index dbe8a2e6..94bb4df1 100644
--- a/problems/0047.全排列II.md
+++ b/problems/0047.全排列II.md
@@ -1,28 +1,34 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+# 排列问题(二)
-> 排列也要去重了
-
-# 47.全排列 II
+## 47.全排列 II
题目链接:https://leetcode-cn.com/problems/permutations-ii/
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
-示例 1:
-输入:nums = [1,1,2]
-输出:
-[[1,1,2],
- [1,2,1],
- [2,1,1]]
+示例 1:
+输入:nums = [1,1,2]
+输出:
+[[1,1,2],
+ [1,2,1],
+ [2,1,1]]
-示例 2:
-输入:nums = [1,2,3]
-输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
+示例 2:
+输入:nums = [1,2,3]
+输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
* 1 <= nums.length <= 8
* -10 <= nums[i] <= 10
-## 思路
+## 思路
这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。
@@ -91,14 +97,14 @@ public:
大家发现,去重最为关键的代码为:
```
-if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
+if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
```
**如果改成 `used[i - 1] == true`, 也是正确的!**,去重代码如下:
```
-if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
+if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
```
@@ -121,17 +127,17 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
-# 总结
+## 总结
这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写:
```
-if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
+if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
```
和这么写:
```
-if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
+if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
```
@@ -142,9 +148,83 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
是不是豁然开朗了!!
-就酱,很多录友表示和「代码随想录」相见恨晚,那么大家帮忙多多宣传,让更多的同学知道这里,感谢啦!
+## 其他语言版本
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+java:
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+```java
+class Solution {
+ //存放结果
+ List> result = new ArrayList<>();
+ //暂存结果
+ List path = new ArrayList<>();
+ public List> permuteUnique(int[] nums) {
+ boolean[] used = new boolean[nums.length];
+ Arrays.fill(used, false);
+ Arrays.sort(nums);
+ backTrack(nums, used);
+ return result;
+ }
+
+ private void backTrack(int[] nums, boolean[] used) {
+ if (path.size() == nums.length) {
+ result.add(new ArrayList<>(path));
+ return;
+ }
+ for (int i = 0; i < nums.length; i++) {
+ // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
+ // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
+ // 如果同⼀树层nums[i - 1]使⽤过则直接跳过
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
+ continue;
+ }
+ //如果同⼀树⽀nums[i]没使⽤过开始处理
+ if (used[i] == false) {
+ used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
+ path.add(nums[i]);
+ backTrack(nums, used);
+ path.remove(path.size() - 1);//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
+ used[i] = false;//回溯
+ }
+ }
+ }
+}
+```
+
+python:
+
+```python
+class Solution:
+ def permuteUnique(self, nums: List[int]) -> List[List[int]]:
+ # res用来存放结果
+ if not nums: return []
+ res = []
+ used = [0] * len(nums)
+ def backtracking(nums, used, path):
+ # 终止条件
+ if len(path) == len(nums):
+ res.append(path.copy())
+ return
+ for i in range(len(nums)):
+ if not used[i]:
+ if i>0 and nums[i] == nums[i-1] and not used[i-1]:
+ continue
+ used[i] = 1
+ path.append(nums[i])
+ backtracking(nums, used, path)
+ path.pop()
+ used[i] = 0
+ # 记得给nums排序
+ backtracking(sorted(nums),used,[])
+ return res
+```
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md
index 7475325e..ac0235c2 100644
--- a/problems/0051.N皇后.md
+++ b/problems/0051.N皇后.md
@@ -1,13 +1,17 @@
-> 开始棋盘问题,如果对回溯法还不了解的同学可以看这个视频
-
-如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg)
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 第51题. N皇后
+## 第51题. N皇后
-题目链接: https://leetcode-cn.com/problems/n-queens/
+题目链接: https://leetcode-cn.com/problems/n-queens/
-n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
+n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。

@@ -17,45 +21,45 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
-输入: 4
-输出: [
- [".Q..", // 解法 1
- "...Q",
- "Q...",
- "..Q."],
-
- ["..Q.", // 解法 2
- "Q...",
- "...Q",
- ".Q.."]
-]
-解释: 4 皇后问题存在两个不同的解法。
-
-提示:
+输入: 4
+输出: [
+ [".Q..", // 解法 1
+ "...Q",
+ "Q...",
+ "..Q."],
+
+ ["..Q.", // 解法 2
+ "Q...",
+ "...Q",
+ ".Q.."]
+]
+解释: 4 皇后问题存在两个不同的解法。
+
+提示:
> 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )
-# 思路
+## 思路
都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二位矩阵还会有点不知所措。
首先来看一下皇后们的约束条件:
1. 不能同行
-2. 不能同列
+2. 不能同列
3. 不能同斜线
确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。
下面我用一个3 * 3 的棋牌,将搜索过程抽象为一颗树,如图:
-
+
从图中,可以看出,二维矩阵中矩阵的高就是这颗树的高度,矩阵的宽就是树形结构中每一个节点的宽度。
-那么我们用皇后们的约束条件,来回溯搜索这颗树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。
+那么我们用皇后们的约束条件,来回溯搜索这颗树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。
-## 回溯三部曲
+## 回溯三部曲
按照我总结的如下回溯模板,我们来依次分析:
@@ -73,11 +77,11 @@ void backtracking(参数) {
}
```
-* 递归函数参数
+* 递归函数参数
我依然是定义全局变量二维数组result来记录最终结果。
-参数n是棋牌的大小,然后用row来记录当前遍历到棋盘的第几层了。
+参数n是棋牌的大小,然后用row来记录当前遍历到棋盘的第几层了。
代码如下:
@@ -86,10 +90,11 @@ vector> result;
void backtracking(int n, int row, vector& chessboard) {
```
-* 递归终止条件
+* 递归终止条件
在如下树形结构中:
-
+
+
可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。
@@ -102,9 +107,9 @@ if (row == n) {
}
```
-* 单层搜索的逻辑
+* 单层搜索的逻辑
-递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
+递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
@@ -125,10 +130,10 @@ for (int col = 0; col < n; col++) {
按照如下标准去重:
1. 不能同行
-2. 不能同列
+2. 不能同列
3. 不能同斜线 (45度和135度角)
-代码如下:
+代码如下:
```
bool isValid(int row, int col, vector& chessboard, int n) {
@@ -155,15 +160,15 @@ bool isValid(int row, int col, vector& chessboard, int n) {
}
```
-在这份代码中,细心的同学可以发现为什么没有在同行进行检查呢?
+在这份代码中,细心的同学可以发现为什么没有在同行进行检查呢?
因为在单层搜索的过程中,每一层递归,只会选for循环(也就是同一行)里的一个元素,所以不用去重了。
-那么按照这个模板不难写出如下代码:
+那么按照这个模板不难写出如下C++代码:
## C++代码
-```
+```C++
class Solution {
private:
vector> result;
@@ -216,7 +221,7 @@ public:
可以看出,除了验证棋盘合法性的代码,省下来部分就是按照回溯法模板来的。
-# 总结
+## 总结
本题是我们解决棋盘问题的第一道题目。
@@ -226,9 +231,144 @@ public:
大家可以在仔细体会体会!
-就酱,如果感觉「代码随想录」干货满满,就分享给身边的朋友同学吧,他们可能也需要!
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+## 其他语言补充
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+Python:
+
+```python
+class Solution:
+ def solveNQueens(self, n: int) -> List[List[str]]:
+ if not n: return []
+ board = [['.'] * n for _ in range(n)]
+ res = []
+ def isVaild(board,row, col):
+ #判断同一列是否冲突
+ for i in range(len(board)):
+ if board[i][col] == 'Q':
+ return False
+ # 判断左上角是否冲突
+ i = row -1
+ j = col -1
+ while i>=0 and j>=0:
+ if board[i][j] == 'Q':
+ return False
+ i -= 1
+ j -= 1
+ # 判断右上角是否冲突
+ i = row - 1
+ j = col + 1
+ while i>=0 and j < len(board):
+ if board[i][j] == 'Q':
+ return False
+ i -= 1
+ j += 1
+ return True
+
+ def backtracking(board, row, n):
+ # 如果走到最后一行,说明已经找到一个解
+ if row == n:
+ temp_res = []
+ for temp in board:
+ temp_str = "".join(temp)
+ temp_res.append(temp_str)
+ res.append(temp_res)
+ for col in range(n):
+ if not isVaild(board, row, col):
+ continue
+ board[row][col] = 'Q'
+ backtracking(board, row+1, n)
+ board[row][col] = '.'
+ backtracking(board, 0, n)
+ return res
+```
+
+Java:
+
+```java
+class Solution {
+ List> res = new ArrayList<>();
+
+ public List> solveNQueens(int n) {
+ char[][] chessboard = new char[n][n];
+ for (char[] c : chessboard) {
+ Arrays.fill(c, '.');
+ }
+ backTrack(n, 0, chessboard);
+ return res;
+ }
+
+
+ public void backTrack(int n, int row, char[][] chessboard) {
+ if (row == n) {
+ res.add(Array2List(chessboard));
+ return;
+ }
+
+ for (int col = 0;col < n; ++col) {
+ if (isValid (row, col, n, chessboard)) {
+ chessboard[row][col] = 'Q';
+ backTrack(n, row+1, chessboard);
+ chessboard[row][col] = '.';
+ }
+ }
+
+ }
+
+
+ public List Array2List(char[][] chessboard) {
+ List list = new ArrayList<>();
+
+ for (char[] c : chessboard) {
+ list.add(String.copyValueOf(c));
+ }
+ return list;
+ }
+
+
+ public boolean isValid(int row, int col, int n, char[][] chessboard) {
+ // 检查列
+ for (int i=0; i=0 && j>=0; i--, j--) {
+ if (chessboard[i][j] == 'Q') {
+ return false;
+ }
+ }
+
+ // 检查135度对角线
+ for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
+ if (chessboard[i][j] == 'Q') {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+```
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0052.N皇后II.md b/problems/0052.N皇后II.md
deleted file mode 100644
index 4cf103fb..00000000
--- a/problems/0052.N皇后II.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# 题目链接
-https://leetcode-cn.com/problems/n-queens-ii/
-
-# 第51题. N皇后
-
-n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
-
-上图为 8 皇后问题的一种解法。
-
-
-给定一个整数 n,返回 n 皇后不同的解决方案的数量。
-
-示例:
-
-输入: 4
-输出: 2
-解释: 4 皇后问题存在如下两个不同的解法。
-[
- [".Q..", // 解法 1
- "...Q",
- "Q...",
- "..Q."],
-
- ["..Q.", // 解法 2
- "Q...",
- "...Q",
- ".Q.."]
-]
-# 思路
-
-这道题目和 51.N皇后 基本没有区别
-
-# C++代码
-
-```
-class Solution {
-private:
-int count = 0;
-void backtracking(int n, int row, vector& chessboard) {
- if (row == n) {
- count++;
- return;
- }
- for (int col = 0; col < n; col++) {
- if (isValid(row, col, chessboard, n)) {
- chessboard[row][col] = 'Q'; // 放置皇后
- backtracking(n, row + 1, chessboard);
- chessboard[row][col] = '.'; // 回溯
- }
- }
-}
-bool isValid(int row, int col, vector& chessboard, int n) {
- int count = 0;
- // 检查列
- for (int i = 0; i < row; i++) { // 这是一个剪枝
- if (chessboard[i][col] == 'Q') {
- return false;
- }
- }
- // 检查 45度角是否有皇后
- for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
- if (chessboard[i][j] == 'Q') {
- return false;
- }
- }
- // 检查 135度角是否有皇后
- for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
- if (chessboard[i][j] == 'Q') {
- return false;
- }
- }
- return true;
-}
-
-public:
- int totalNQueens(int n) {
- std::vector chessboard(n, std::string(n, '.'));
- backtracking(n, 0, chessboard);
- return count;
-
- }
-};
-```
-
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
diff --git a/problems/0053.最大子序和.md b/problems/0053.最大子序和.md
index ab83d861..b8a9d748 100644
--- a/problems/0053.最大子序和.md
+++ b/problems/0053.最大子序和.md
@@ -1,27 +1,31 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 从本题开始,贪心题目都比较难了!
-通知:一些录友表示经常看不到每天的文章,现在公众号已经不按照发送时间推荐了,而是根据一些规则乱序推送,所以可能关注了「代码随想录」也一直看不到文章,建议把「代码随想录」设置星标哈,设置星标之后,每天就按发文时间推送了,我每天都是定时8:35发送的,嗷嗷准时!
-# 53. 最大子序和
+## 53. 最大子序和
题目地址:https://leetcode-cn.com/problems/maximum-subarray/
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
-示例:
-输入: [-2,1,-3,4,-1,2,1,-5,4]
-输出: 6
-解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
+示例:
+输入: [-2,1,-3,4,-1,2,1,-5,4]
+输出: 6
+解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
-# 思路
-## 暴力解法
+## 暴力解法
-暴力解法的思路,第一层for 就是设置起始位置,第二层for循环遍历数组寻找最大值
+暴力解法的思路,第一层for 就是设置起始位置,第二层for循环遍历数组寻找最大值
-时间复杂度:O(n^2)
+时间复杂度:O(n^2)
空间复杂度:O(1)
-```
+```C++
class Solution {
public:
int maxSubArray(vector& nums) {
@@ -41,7 +45,7 @@ public:
以上暴力的解法C++勉强可以过,其他语言就不确定了。
-## 贪心解法
+## 贪心解法
**贪心贪的是哪里呢?**
@@ -49,17 +53,17 @@ public:
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
-全局最优:选取最大“连续和”
+全局最优:选取最大“连续和”
**局部最优的情况下,并记录最大的“连续和”,可以推出全局最优**。
从代码角度上来讲:遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。
-**这相当于是暴力解法中的不断调整最大子序和区间的起始位置**。
+**这相当于是暴力解法中的不断调整最大子序和区间的起始位置**。
-**那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?**
+**那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?**
区间的终止位置,其实就是如果count取到最大值了,及时记录下来了。例如如下代码:
@@ -67,17 +71,17 @@ public:
if (count > result) result = count;
```
-**这样相当于是用result记录最大子序和区间和(变相的算是调整了终止位置)**。
+**这样相当于是用result记录最大子序和区间和(变相的算是调整了终止位置)**。
如动画所示:
-
+
红色的起始位置就是贪心每次取count为正数的时候,开始一个区间的统计。
那么不难写出如下C++代码(关键地方已经注释)
-```
+```C++
class Solution {
public:
int maxSubArray(vector& nums) {
@@ -94,18 +98,18 @@ public:
}
};
```
-时间复杂度:O(n)
-空间复杂度:O(1)
+时间复杂度:O(n)
+空间复杂度:O(1)
当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。
-## 动态规划
+## 动态规划
当然本题还可以用动态规划来做,当前[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)主要讲解贪心系列,后续到动态规划系列的时候会详细讲解本题的dp方法。
那么先给出我的dp代码如下,有时间的录友可以提前做一做:
-```
+```C++
class Solution {
public:
int maxSubArray(vector& nums) {
@@ -122,20 +126,31 @@ public:
};
```
-时间复杂度:O(n)
-空间复杂度:O(n)
+时间复杂度:O(n)
+空间复杂度:O(n)
-# 总结
+## 总结
本题的贪心思路其实并不好想,这也进一步验证了,别看贪心理论很直白,有时候看似是常识,但贪心的题目一点都不简单!
后续将介绍的贪心题目都挺难的,哈哈,所以贪心很有意思,别小看贪心!
-就酱,如果感觉「代码随想录」干货满满,就帮忙转发一波吧,让更多的小伙伴知道这里!
+## 其他语言版本
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+Java:
+
+
+Python:
+
+
+Go:
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0053.最大子序和(动态规划).md b/problems/0053.最大子序和(动态规划).md
new file mode 100644
index 00000000..957d8b6c
--- /dev/null
+++ b/problems/0053.最大子序和(动态规划).md
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 53. 最大子序和
+
+题目地址:https://leetcode-cn.com/problems/maximum-subarray/
+
+给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
+
+示例:
+输入: [-2,1,-3,4,-1,2,1,-5,4]
+输出: 6
+解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
+
+## 思路
+
+这道题之前我们在讲解贪心专题的时候用贪心算法解决过一次,[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)。
+
+这次我们用动态规划的思路再来分析一次。
+
+动规五部曲如下:
+
+1. 确定dp数组(dp table)以及下标的含义
+
+**dp[i]:包括下标i之前的最大连续子序列和为dp[i]**。
+
+2. 确定递推公式
+
+dp[i]只有两个方向可以推出来:
+
+* dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
+* nums[i],即:从头开始计算当前连续子序列和
+
+一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
+
+3. dp数组如何初始化
+
+从递推公式可以看出来dp[i]是依赖于dp[i - 1]的状态,dp[0]就是递推公式的基础。
+
+dp[0]应该是多少呢?
+
+更具dp[i]的定义,很明显dp[0]因为为nums[0]即dp[0] = nums[0]。
+
+4. 确定遍历顺序
+
+递推公式中dp[i]依赖于dp[i - 1]的状态,需要从前向后遍历。
+
+5. 举例推导dp数组
+
+以示例一为例,输入:nums = [-2,1,-3,4,-1,2,1,-5,4],对应的dp状态如下:
+
+
+**注意最后的结果可不是dp[nums.size() - 1]!** ,而是dp[6]。
+
+在回顾一下dp[i]的定义:包括下标i之前的最大连续子序列和为dp[i]。
+
+那么我们要找最大的连续子序列,就应该找每一个i为终点的连续最大子序列。
+
+所以在递推公式的时候,可以直接选出最大的dp[i]。
+
+以上动规五部曲分析完毕,完整代码如下:
+
+```C++
+class Solution {
+public:
+ int maxSubArray(vector& nums) {
+ if (nums.size() == 0) return 0;
+ vector dp(nums.size());
+ dp[0] = nums[0];
+ int result = dp[0];
+ for (int i = 1; i < nums.size(); i++) {
+ dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
+ if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
+ }
+ return result;
+ }
+};
+```
+* 时间复杂度:O(n)
+* 空间复杂度:O(n)
+
+
+## 总结
+
+这道题目用贪心也很巧妙,但有一点绕,需要仔细想一想,如果想回顾一下贪心就看这里吧:[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)
+
+动规的解法还是很直接的。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md
index 2f0ab898..0cad1fa7 100644
--- a/problems/0055.跳跃游戏.md
+++ b/problems/0055.跳跃游戏.md
@@ -1,7 +1,13 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 通知
-# 55. 跳跃游戏
+## 55. 跳跃游戏
题目链接:https://leetcode-cn.com/problems/jump-game/
@@ -11,20 +17,20 @@
判断你是否能够到达最后一个位置。
-示例 1:
-输入: [2,3,1,1,4]
-输出: true
-解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
+示例 1:
+输入: [2,3,1,1,4]
+输出: true
+解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
-示例 2:
-输入: [3,2,1,0,4]
-输出: false
-解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
+示例 2:
+输入: [3,2,1,0,4]
+输出: false
+解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
-## 思路
+## 思路
-刚看到本题一开始可能想:当前位置元素如果是3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?
+刚看到本题一开始可能想:当前位置元素如果是3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?
其实跳几步无所谓,关键在于可跳的覆盖范围!
@@ -32,9 +38,9 @@
这个范围内,别管是怎么跳的,反正一定可以跳过来。
-**那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!**
+**那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!**
-每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
+每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
**贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点**。
@@ -66,7 +72,7 @@ public:
}
};
```
-# 总结
+## 总结
这道题目关键点在于:不用拘泥于每次究竟跳跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
@@ -76,4 +82,22 @@ public:
**是真的就是没什么联系,因为贪心无套路!**没有个整体的贪心框架解决一些列问题,只能是接触各种类型的题目锻炼自己的贪心思维!
-就酱,「代码随想录」值得推荐给身边的朋友同学们!
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md
index 2d891c36..f939325f 100644
--- a/problems/0056.合并区间.md
+++ b/problems/0056.合并区间.md
@@ -1,33 +1,68 @@
-## 题目链接
-https://leetcode-cn.com/problems/merge-intervals/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-## 思路
-这道题目看起来就是一道模拟类的题,但其实是一道贪心题目!
-按照左区间排序之后,每次合并都取最大的右区间,这样就可以合并更多的区间了。
+## 56. 合并区间
-那有同学问了,这不是废话么? 当然要取最大的右区间啊。
+题目链接:https://leetcode-cn.com/problems/merge-intervals/
-**是的,一想就是这么个道理,但它就是贪心的思想,局部最优推导出整体最优**。
+给出一个区间的集合,请合并所有重叠的区间。
-这也就是为什么很多同学刷题的时候都没有发现自己用了贪心。
+示例 1:
+输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
+输出: [[1,6],[8,10],[15,18]]
+解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
-合并思路:如果 `intervals[i][0] < intervals[i - 1][1]` 即intervals[i]起始位置 < intervals[i - 1]终止位置,则一定有重复,需要合并。
+示例 2:
+输入: intervals = [[1,4],[4,5]]
+输出: [[1,5]]
+解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
+注意:输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。
-如图所示:
+提示:
-
+* intervals[i][0] <= intervals[i][1]
+
+## 思路
+
+大家应该都感觉到了,此题一定要排序,那么按照左边界排序,还是右边界排序呢?
+
+都可以!
+
+那么我按照左边界排序,排序之后局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了,整体最优:合并所有重叠的区间。
+
+局部最优可以推出全局最优,找不出反例,试试贪心。
+
+那有同学问了,本来不就应该合并最大右边界么,这和贪心有啥关系?
+
+有时候贪心就是常识!哈哈
+
+按照左边界从小到大排序之后,如果 `intervals[i][0] < intervals[i - 1][1]` 即intervals[i]左边界 < intervals[i - 1]右边界,则一定有重复,因为intervals[i]的左边界一定是大于等于intervals[i - 1]的左边界。
+
+即:intervals[i]的左边界在intervals[i - 1]左边界和右边界的范围内,那么一定有重复!
+
+这么说有点抽象,看图:(**注意图中区间都是按照左边界排序之后了**)
+
+
+
+知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢?
+
+其实就是用合并区间后左边界和右边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把原区间加入到result数组。
C++代码如下:
-```
+```C++
class Solution {
public:
- // 按照区间左边界排序
+ // 按照区间左边界从小到大排序
static bool cmp (const vector& a, const vector& b) {
return a[0] < b[0];
}
-
vector> merge(vector>& intervals) {
vector> result;
if (intervals.size() == 0) return result;
@@ -36,13 +71,14 @@ public:
int length = intervals.size();
for (int i = 1; i < length; i++) {
- int start = intervals[i - 1][0];
- int end = intervals[i - 1][1];
+ int start = intervals[i - 1][0]; // 初始为i-1区间的左边界
+ int end = intervals[i - 1][1]; // 初始i-1区间的右边界
while (i < length && intervals[i][0] <= end) { // 合并区间
- end = max(end, intervals[i][1]);
- if (i == length - 1) flag = true; // 最后一个区间也合并了
- i++;
+ end = max(end, intervals[i][1]); // 不断更新右区间
+ if (i == length - 1) flag = true; // 最后一个区间也合并了
+ i++; // 继续合并下一个区间
}
+ // start和end是表示intervals[i - 1]的左边界右边界,所以最优intervals[i]区间是否合并了要标记一下
result.push_back({start, end});
}
// 如果最后一个区间没有合并,将其加入result
@@ -53,3 +89,66 @@ public:
}
};
```
+
+当然以上代码有冗余一些,可以优化一下,如下:(思路是一样的)
+
+```C++
+class Solution {
+public:
+ vector> merge(vector>& intervals) {
+ vector> result;
+ if (intervals.size() == 0) return result;
+ // 排序的参数使用了lamda表达式
+ sort(intervals.begin(), intervals.end(), [](const vector& a, const vector& b){return a[0] < b[0];});
+
+ result.push_back(intervals[0]);
+ for (int i = 1; i < intervals.size(); i++) {
+ if (result.back()[1] >= intervals[i][0]) { // 合并区间
+ result.back()[1] = max(result.back()[1], intervals[i][1]);
+ } else {
+ result.push_back(intervals[i]);
+ }
+ }
+ return result;
+ }
+};
+```
+
+* 时间复杂度:O(nlogn) ,有一个快排
+* 空间复杂度:O(1),我没有算result数组(返回值所需容器占的空间)
+
+
+## 总结
+
+对于贪心算法,很多同学都是:**如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了**。
+
+跟着「代码随想录」刷题的录友应该感受过,贪心难起来,真的难。
+
+那应该怎么办呢?
+
+正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。
+
+「代码随想录」会把贪心常见的经典题目覆盖到,大家只要认真学习打卡就可以了。
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0057.插入区间.md b/problems/0057.插入区间.md
deleted file mode 100644
index 79dba1c4..00000000
--- a/problems/0057.插入区间.md
+++ /dev/null
@@ -1,90 +0,0 @@
-
-# 链接
-https://leetcode-cn.com/problems/insert-interval/
-
-# 思路
-这道题目合并的情况有很多种,想想都让人头疼。
-
-我把这道题目化为三步:
-
-## 步骤一:找到需要合并的区间
-
-找到插入区间需要插入或者合并的位置。
-
-代码如下:
-
-```
-int index = 0; // intervals的索引
-while (index < intervals.size() && intervals[index][1] < newInterval[0]) {
- result.push_back(intervals[index++]);
-}
-```
-
-此时intervals[index]就需要合并的区间了
-
-## 步骤二:合并区间
-
-合并区间还有两种情况
-
-1. intervals[index]需要合并,如图:
-
-
-
-对于这种情况,只要是intervals[index]起始位置 <= newInterval终止位置,就要一直合并下去。
-
-代码如下:
-
-```
-while (index < intervals.size() && intervals[index][0] <= newInterval[1]) { // 注意防止越界
- newInterval[0] = min(intervals[index][0], newInterval[0]);
- newInterval[1] = max(intervals[index][1], newInterval[1]);
- index++;
-}
-```
-合并之后,将newInterval放入result就可以了
-
-2. intervals[index]不用合并,插入区间直接插入就行,如图:
-
-
-
-对于这种情况,就直接把newInterval放入result就可以了
-
-## 步骤三:处理合并区间之后的区间
-
-合并之后,就应该把合并之后的区间,以此加入result中。
-
-代码如下:
-
-```
-while (index < intervals.size()) {
- result.push_back(intervals[index++]);
-}
-```
-
-# 整体C++代码
-
-```
-class Solution {
-public:
- vector> insert(vector>& intervals, vector& newInterval) {
- vector> result;
- int index = 0; // intervals的索引
- // 步骤一:找到需要合并的区间
- while (index < intervals.size() && intervals[index][1] < newInterval[0]) {
- result.push_back(intervals[index++]);
- }
- // 步骤二:合并区间
- while (index < intervals.size() && intervals[index][0] <= newInterval[1]) {
- newInterval[0] = min(intervals[index][0], newInterval[0]);
- newInterval[1] = max(intervals[index][1], newInterval[1]);
- index++;
- }
- result.push_back(newInterval);
- // 步骤三:处理合并区间之后的区间
- while (index < intervals.size()) {
- result.push_back(intervals[index++]);
- }
- return result;
- }
-};
-```
diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md
index 111be4c4..5e2d48d8 100644
--- a/problems/0059.螺旋矩阵II.md
+++ b/problems/0059.螺旋矩阵II.md
@@ -1,22 +1,16 @@
-
-
-
-
-
-
+
+
-
-
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 一进循环深似海,从此offer是路人
-# 题目59.螺旋矩阵II
+## 59.螺旋矩阵II
-题目地址:https://leetcode-cn.com/problems/spiral-matrix-ii/
+题目地址:https://leetcode-cn.com/problems/spiral-matrix-ii/
给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
@@ -29,7 +23,7 @@
[ 7, 6, 5 ]
]
-# 思路
+## 思路
这道题目可以说在面试中出现频率较高的题目,**本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。**
@@ -39,13 +33,13 @@
结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里哪里有问题,改了那里这里又跑不起来了。
-大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。
+大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/4X-8VRgnYRGd5LYGZ33m4w)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
-* 填充上行从左到右
+* 填充上行从左到右
* 填充右列从上到下
* 填充下行从右到左
* 填充左列从下到上
@@ -64,13 +58,13 @@
这也是坚持了每条边左闭右开的原则。
-一些同学做这道题目之所以一直写不好,代码越写越乱。
+一些同学做这道题目之所以一直写不好,代码越写越乱。
就是因为在画每一条边的时候,一会左开又闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
-# C++代码
+整体C++代码如下:
```C++
class Solution {
@@ -122,8 +116,118 @@ public:
};
```
-**循序渐进学算法,认准「代码随想录」,Carl手把手带你过关斩将!**
+## 类似题目
-
-
-
+* 54.螺旋矩阵
+* 剑指Offer 29.顺时针打印矩阵
+
+
+
+
+## 其他语言版本
+
+Java:
+
+```Java
+class Solution {
+ public int[][] generateMatrix(int n) {
+ int[][] res = new int[n][n];
+
+ // 循环次数
+ int loop = n / 2;
+
+ // 定义每次循环起始位置
+ int startX = 0;
+ int startY = 0;
+
+ // 定义偏移量
+ int offset = 1;
+
+ // 定义填充数字
+ int count = 1;
+
+ // 定义中间位置
+ int mid = n / 2;
+
+
+ while (loop > 0) {
+ int i = startX;
+ int j = startY;
+
+ // 模拟上侧从左到右
+ for (; j startY; j--) {
+ res[i][j] = count++;
+ }
+
+ // 模拟左侧从下到上
+ for (; i > startX; i--) {
+ res[i][j] = count++;
+ }
+
+ loop--;
+
+ startX += 1;
+ startY += 1;
+
+ offset += 2;
+ }
+
+
+ if (n % 2 == 1) {
+ res[mid][mid] = count;
+ }
+
+ return res;
+ }
+}
+```
+
+python:
+
+```python
+class Solution:
+ def generateMatrix(self, n: int) -> List[List[int]]:
+ left, right, up, down = 0, n-1, 0, n-1
+ matrix = [ [0]*n for _ in range(n)]
+ num = 1
+ while left<=right and up<=down:
+ # 填充左到右
+ for i in range(left, right+1):
+ matrix[up][i] = num
+ num += 1
+ up += 1
+ # 填充上到下
+ for i in range(up, down+1):
+ matrix[i][right] = num
+ num += 1
+ right -= 1
+ # 填充右到左
+ for i in range(right, left-1, -1):
+ matrix[down][i] = num
+ num += 1
+ down -= 1
+ # 填充下到上
+ for i in range(down, up-1, -1):
+ matrix[i][left] = num
+ num += 1
+ left += 1
+ return matrix
+```
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md
index 7eee77b3..e3a6da8c 100644
--- a/problems/0062.不同路径.md
+++ b/problems/0062.不同路径.md
@@ -1,7 +1,53 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 思路
+## 62.不同路径
-## 深搜
+题目链接:https://leetcode-cn.com/problems/unique-paths/
+
+一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
+
+机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
+
+问总共有多少条不同的路径?
+
+示例 1:
+
+
+
+输入:m = 3, n = 7
+输出:28
+
+示例 2:
+输入:m = 2, n = 3
+输出:3
+解释:
+从左上角开始,总共有 3 条路径可以到达右下角。
+1. 向右 -> 向右 -> 向下
+2. 向右 -> 向下 -> 向右
+3. 向下 -> 向右 -> 向右
+
+
+示例 3:
+输入:m = 7, n = 3
+输出:28
+
+示例 4:
+输入:m = 3, n = 3
+输出:6
+
+提示:
+* 1 <= m, n <= 100
+* 题目数据保证答案小于等于 2 * 10^9
+
+## 思路
+
+### 深搜
这道题目,刚一看最直观的想法就是用图论里的深搜,来枚举出来有多少种路径。
@@ -28,27 +74,36 @@ public:
};
```
-大家如果提交了代码就会发现超时了!
+**大家如果提交了代码就会发现超时了!**
来分析一下时间复杂度,这个深搜的算法,其实就是要遍历整个二叉树。
这颗树的深度其实就是m+n-1(深度按从1开始计算)。
-那二叉树的节点个数就是 2^(m + n - 1) - 1。可以理解深搜的算法就是遍历了整个满二叉树(其实没有把搜索节点都遍历到,只是近似而已)
+那二叉树的节点个数就是 2^(m + n - 1) - 1。可以理解深搜的算法就是遍历了整个满二叉树(其实没有遍历整个满二叉树,只是近似而已)
所以上面深搜代码的时间复杂度为O(2^(m + n - 1) - 1),可以看出,这是指数级别的时间复杂度,是非常大的。
-## 动态规划
+### 动态规划
机器人从(0 , 0) 位置触发,到(m - 1, n - 1)终点。
-按照动规三部曲来分析:
+按照动规五部曲来分析:
-* dp数组表述啥
+1. 确定dp数组(dp table)以及下标的含义
-这里设计一个dp二维数组,dp[i][j] 表示从(0 ,0)出发,到(i, j) 有几条不同的路径。
+dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
-* dp数组的初始化
+
+2. 确定递推公式
+
+想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。
+
+此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。
+
+那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。
+
+3. dp数组的初始化
如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。
@@ -59,19 +114,19 @@ for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
```
-* 递推公式
+4. 确定遍历顺序
-想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。
+这里要看一下递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。
-此时在回顾一下 dp[i-1][j] 表示啥,是从(0, 0)的位置到(i-1, j)有几条路径,dp[i][j - 1]同理。
+这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。
-那么很自然,dp[i][j] = dp[i-1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。
+5. 举例推导dp数组
如图所示:

-C++代码如下:
+以上动规五部曲分析完毕,C++代码如下:
```C++
class Solution {
@@ -89,7 +144,7 @@ public:
}
};
```
-* 时间复杂度:O(m * n)
+* 时间复杂度:O(m * n)
* 空间复杂度:O(m * n)
其实用一个一维数组(也可以理解是滚动数组)就可以了,但是不利于理解,可以优化点空间,建议先理解了二维,在理解一维,C++代码如下:
@@ -109,12 +164,12 @@ public:
}
};
```
-* 时间复杂度:O(m * n)
+* 时间复杂度:O(m * n)
* 空间复杂度:O(n)
-# 数论方法
+### 数论方法
-在这个图中,可以看出一共 m,n的话,无论怎么走,走到终点都需要 m + n - 2 步。
+在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 m + n - 2 步。

@@ -130,7 +185,9 @@ public:
**求组合的时候,要防止两个int相乘溢出!** 所以不能把算式的分子都算出来,分母都算出来再做除法。
-```
+例如如下代码是不行的。
+
+```C++
class Solution {
public:
int uniquePaths(int m, int n) {
@@ -145,9 +202,9 @@ public:
```
-需要在计算分子的时候,不算除以分母,代码如下:
+需要在计算分子的时候,不断除以分母,代码如下:
-```
+```C++
class Solution {
public:
int uniquePaths(int m, int n) {
@@ -167,10 +224,37 @@ public:
};
```
-计算组合问题的代码还是有难度的,特别是处理溢出的情况!
+时间复杂度:O(m)
+空间复杂度:O(1)
-最后这个代码还有点复杂了,还是可以优化,我就不继续优化了,有空在整理一下,哈哈,就酱!
+**计算组合问题的代码还是有难度的,特别是处理溢出的情况!**
+
+## 总结
+
+本文分别给出了深搜,动规,数论三种方法。
+
+深搜当然是超时了,顺便分析了一下使用深搜的时间复杂度,就可以看出为什么超时了。
+
+然后在给出动规的方法,依然是使用动规五部曲,这次我们就要考虑如何正确的初始化了,初始化和遍历顺序其实也很重要!
+
+就酱,循序渐进学算法,认准「代码随想录」!
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md
new file mode 100644
index 00000000..311f712e
--- /dev/null
+++ b/problems/0063.不同路径II.md
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 63. 不同路径 II
+
+题目链接:https://leetcode-cn.com/problems/unique-paths-ii/
+
+一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
+
+机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
+
+现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
+
+
+
+网格中的障碍物和空位置分别用 1 和 0 来表示。
+
+示例 1:
+
+
+
+输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
+输出:2
+解释:
+3x3 网格的正中间有一个障碍物。
+从左上角到右下角一共有 2 条不同的路径:
+1. 向右 -> 向右 -> 向下 -> 向下
+2. 向下 -> 向下 -> 向右 -> 向右
+
+示例 2:
+
+
+
+输入:obstacleGrid = [[0,1],[0,0]]
+输出:1
+
+提示:
+
+* m == obstacleGrid.length
+* n == obstacleGrid[i].length
+* 1 <= m, n <= 100
+* obstacleGrid[i][j] 为 0 或 1
+
+
+## 思路
+
+这道题相对于[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A) 就是有了障碍。
+
+第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢?
+
+[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了。
+
+动规五部曲:
+
+1. 确定dp数组(dp table)以及下标的含义
+
+dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
+
+2. 确定递推公式
+
+递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。
+
+但这里需要注意一点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)。
+
+所以代码为:
+
+```
+if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
+ dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
+}
+```
+
+3. dp数组如何初始化
+
+在[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)不同路径中我们给出如下的初始化:
+
+```
+vector> dp(m, vector(n, 0)); // 初始值为0
+for (int i = 0; i < m; i++) dp[i][0] = 1;
+for (int j = 0; j < n; j++) dp[0][j] = 1;
+```
+
+因为从(0, 0)的位置到(i, 0)的路径只有一条,所以dp[i][0]一定为1,dp[0][j]也同理。
+
+但如果(i, 0) 这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]应该还是初始值0。
+
+如图:
+
+
+
+下标(0, j)的初始化情况同理。
+
+所以本题初始化代码为:
+
+```C++
+vector> dp(m, vector(n, 0));
+for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
+for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
+```
+
+**注意代码里for循环的终止条件,一旦遇到obstacleGrid[i][0] == 1的情况就停止dp[i][0]的赋值1的操作,dp[0][j]同理**
+
+4. 确定遍历顺序
+
+从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值。
+
+代码如下:
+
+```C++
+for (int i = 1; i < m; i++) {
+ for (int j = 1; j < n; j++) {
+ if (obstacleGrid[i][j] == 1) continue;
+ dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
+ }
+}
+```
+
+
+5. 举例推导dp数组
+
+拿示例1来举例如题:
+
+
+
+对应的dp table 如图:
+
+
+
+如果这个图看不同,建议在理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下!
+
+动规五部分分析完毕,对应C++代码如下:
+
+```C++
+class Solution {
+public:
+ int uniquePathsWithObstacles(vector>& obstacleGrid) {
+ int m = obstacleGrid.size();
+ int n = obstacleGrid[0].size();
+ vector> dp(m, vector(n, 0));
+ for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
+ for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
+ for (int i = 1; i < m; i++) {
+ for (int j = 1; j < n; j++) {
+ if (obstacleGrid[i][j] == 1) continue;
+ dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
+ }
+ }
+ return dp[m - 1][n - 1];
+ }
+};
+```
+* 时间复杂度O(n * m) n m 分别为obstacleGrid 长度和宽度
+* 空间复杂度O(n * m)
+
+至于能不能优化空间降为一维dp数组,我感觉不太行,因为要考虑障碍,如果把这些障碍压缩到一行,结果一定就不一样了。
+
+## 总结
+
+本题是[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)的障碍版,整体思路大体一致。
+
+但就算是做过62.不同路径,在做本题也会有感觉遇到障碍无从下手。
+
+其实只要考虑到,遇到障碍dp[i][j]保持0就可以了。
+
+也有一些小细节,例如:初始化的部分,很容易忽略了障碍之后应该都是0的情况。
+
+就酱,「代码随想录」值得推荐给身边学算法的同学朋友们,关注后都会发现相见恨晚!
+
+
+## 其他语言版本
+
+Java:
+
+```java
+class Solution {
+ public int uniquePathsWithObstacles(int[][] obstacleGrid) {
+ int n = obstacleGrid.length, m = obstacleGrid[0].length;
+ int[][] dp = new int[n][m];
+ dp[0][0] = 1 - obstacleGrid[0][0];
+ for (int i = 1; i < m; i++) {
+ if (obstacleGrid[0][i] == 0 && dp[0][i - 1] == 1) {
+ dp[0][i] = 1;
+ }
+ }
+ for (int i = 1; i < n; i++) {
+ if (obstacleGrid[i][0] == 0 && dp[i - 1][0] == 1) {
+ dp[i][0] = 1;
+ }
+ }
+ for (int i = 1; i < n; i++) {
+ for (int j = 1; j < m; j++) {
+ if (obstacleGrid[i][j] == 1) continue;
+ dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
+ }
+ }
+ return dp[n - 1][m - 1];
+ }
+}
+```
+
+
+Python:
+
+```python
+class Solution:
+ def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
+ # 构造一个DP table
+ row = len(obstacleGrid)
+ col = len(obstacleGrid[0])
+ dp = [[0 for _ in range(col)] for _ in range(row)]
+
+ dp[0][0] = 1 if obstacleGrid[0][0] != 1 else 0
+ if dp[0][0] == 0: return 0 # 如果第一个格子就是障碍,return 0
+ # 第一行
+ for i in range(1, col):
+ if obstacleGrid[0][i] != 1:
+ dp[0][i] = dp[0][i-1]
+
+ # 第一列
+ for i in range(1, row):
+ if obstacleGrid[i][0] != 1:
+ dp[i][0] = dp[i-1][0]
+ print(dp)
+
+ for i in range(1, row):
+ for j in range(1, col):
+ if obstacleGrid[i][j] != 1:
+ dp[i][j] = dp[i-1][j] + dp[i][j-1]
+ return dp[-1][-1]
+```
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md
index 37a536ed..6ae6adc7 100644
--- a/problems/0070.爬楼梯.md
+++ b/problems/0070.爬楼梯.md
@@ -1,15 +1,175 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-dp里求排列,1 2 步 和 2 1 步都是上三个台阶,但不一样!
+## 70. 爬楼梯
-这是求排列
+假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
+
+每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
+
+注意:给定 n 是一个正整数。
+
+示例 1:
+输入: 2
+输出: 2
+解释: 有两种方法可以爬到楼顶。
+1. 1 阶 + 1 阶
+2. 2 阶
+
+示例 2:
+输入: 3
+输出: 3
+解释: 有三种方法可以爬到楼顶。
+1. 1 阶 + 1 阶 + 1 阶
+2. 1 阶 + 2 阶
+3. 2 阶 + 1 阶
+
+
+## 思路
+
+本题大家如果没有接触过的话,会感觉比较难,多举几个例子,就可以发现其规律。
+
+爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
+
+那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。
+
+所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来,那么就可以想到动态规划了。
+
+我们来分析一下,动规五部曲:
+
+定义一个一维数组来记录不同楼层的状态
+
+1. 确定dp数组以及下标的含义
+
+dp[i]: 爬到第i层楼梯,有dp[i]种方法
+
+2. 确定递推公式
+
+如果可以推出dp[i]呢?
+
+从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。
+
+首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
+
+还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
+
+那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!
+
+所以dp[i] = dp[i - 1] + dp[i - 2] 。
+
+在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
+
+这体现出确定dp数组以及下标的含义的重要性!
+
+3. dp数组如何初始化
+
+在回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]中方法。
+
+那么i为0,dp[i]应该是多少呢,这个可以有很多解释,但都基本是直接奔着答案去解释的。
+
+例如强行安慰自己爬到第0层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1,相当于直接站在楼顶。
+
+但总有点牵强的成分。
+
+那还这么理解呢:我就认为跑到第0层,方法就是0啊,一步只能走一个台阶或者两个台阶,然而楼层是0,直接站楼顶上了,就是不用方法,dp[0]就应该是0.
+
+**其实这么争论下去没有意义,大部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中i从2开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1**。
+
+从dp数组定义的角度上来说,dp[0] = 0 也能说得通。
+
+需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。
+
+所以本题其实就不应该讨论dp[0]的初始化!
+
+我相信dp[1] = 1,dp[2] = 2,这个初始化大家应该都没有争议的。
+
+所以我的原则是:不考虑dp[0]如果初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。
+
+4. 确定遍历顺序
+
+从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
+
+5. 举例推导dp数组
+
+举例当n为5的时候,dp table(dp数组)应该是这样的
+
+
+
+如果代码出问题了,就把dp table 打印出来,看看究竟是不是和自己推导的一样。
+
+**此时大家应该发现了,这不就是斐波那契数列么!**
+
+唯一的区别是,没有讨论dp[0]应该是什么,因为dp[0]在本题没有意义!
+
+以上五部分析完之后,C++代码如下:
+
+```C++
+// 版本一
+class Solution {
+public:
+ int climbStairs(int n) {
+ if (n <= 1) return n; // 因为下面直接对dp[2]操作了,防止空指针
+ vector dp(n + 1);
+ dp[1] = 1;
+ dp[2] = 2;
+ for (int i = 3; i <= n; i++) { // 注意i是从3开始的
+ dp[i] = dp[i - 1] + dp[i - 2];
+ }
+ return dp[n];
+ }
+};
```
+* 时间复杂度:O(n)
+* 空间复杂度:O(n)
+
+当然依然也可以,优化一下空间复杂度,代码如下:
+
+```C++
+// 版本二
+class Solution {
+public:
+ int climbStairs(int n) {
+ if (n <= 1) return n;
+ int dp[3];
+ dp[1] = 1;
+ dp[2] = 2;
+ for (int i = 3; i <= n; i++) {
+ int sum = dp[1] + dp[2];
+ dp[1] = dp[2];
+ dp[2] = sum;
+ }
+ return dp[2];
+ }
+};
+```
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+后面将讲解的很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化,**但我个人认为面试中能写出版本一就够了哈,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化**。
+
+因为版本一才能体现出动规的思想精髓,递推的状态变化。
+
+## 拓展
+
+这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
+
+这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会拿从背包问题的角度上来再讲一遍。
+
+这里我先给出我的实现代码:
+
+```C++
class Solution {
public:
int climbStairs(int n) {
vector dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
- for (int j = 1; j <= 2; j++) {
+ for (int j = 1; j <= m; j++) { // 把m换成2,就可以AC爬楼梯这道题
if (i - j >= 0) dp[i] += dp[i - j];
}
}
@@ -17,3 +177,65 @@ public:
}
};
```
+
+代码中m表示最多可以爬m个台阶。
+
+**以上代码不能运行哈,我主要是为了体现只要把m换成2,粘过去,就可以AC爬楼梯这道题,不信你就粘一下试试,哈哈**。
+
+
+**此时我就发现一个绝佳的大厂面试题**,第一道题就是单纯的爬楼梯,然后看候选人的代码实现,如果把dp[0]的定义成1了,就可以发难了,为什么dp[0]一定要初始化为1,此时可能候选人就要强行给dp[0]应该是1找各种理由。那这就是一个考察点了,对dp[i]的定义理解的不深入。
+
+然后可以继续发难,如果一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。这道题目leetcode上并没有原题,绝对是考察候选人算法能力的绝佳好题。
+
+这一连套问下来,候选人算法能力如何,面试官心里就有数了。
+
+**其实大厂面试最喜欢问题的就是这种简单题,然后慢慢变化,在小细节上考察候选人**。
+
+
+
+## 总结
+
+这道题目和[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)题目基本是一样的,但是会发现本题相比[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)难多了,为什么呢?
+
+关键是 [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w) 题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。
+
+而本题,就需要逐个分析了,大家现在应该初步感受出[关于动态规划,你该了解这些!](https://leetcode-cn.com/circle/article/tNuNnM/)里给出的动规五部曲了。
+
+简单题是用来掌握方法论的,例如昨天斐波那契的题目够简单了吧,但昨天和今天可以使用一套方法分析出来的,这就是方法论!
+
+所以不要轻视简单题,那种凭感觉就刷过去了,其实和没掌握区别不大,只有掌握方法论并说清一二三,才能触类旁通,举一反三哈!
+
+就酱,循序渐进学算法,认准「代码随想录」!
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+```python
+class Solution:
+ def climbStairs(self, n: int) -> int:
+ # dp[i]表示爬到第i级楼梯的种数, (1, 2) (2, 1)是两种不同的类型
+ dp = [0] * (n + 1)
+ dp[0] = 1
+ for i in range(n+1):
+ for j in range(1, 3):
+ if i>=j:
+ dp[i] += dp[i-j]
+ return dp[-1]
+```
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0070.爬楼梯完全背包版本.md b/problems/0070.爬楼梯完全背包版本.md
new file mode 100644
index 00000000..beda45d5
--- /dev/null
+++ b/problems/0070.爬楼梯完全背包版本.md
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+# 动态规划:以前我没得选,现在我选择再爬一次!
+
+之前讲这道题目的时候,因为还没有讲背包问题,所以就只是讲了一下爬楼梯最直接的动规方法(斐波那契)。
+
+**这次终于讲到了背包问题,我选择带录友们再爬一次楼梯!**
+
+## 70. 爬楼梯
+
+链接:https://leetcode-cn.com/problems/climbing-stairs/
+
+假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
+
+每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
+
+注意:给定 n 是一个正整数。
+
+示例 1:
+输入: 2
+输出: 2
+解释: 有两种方法可以爬到楼顶。
+1. 1 阶 + 1 阶
+2. 2 阶
+
+示例 2:
+输入: 3
+输出: 3
+解释: 有三种方法可以爬到楼顶。
+1. 1 阶 + 1 阶 + 1 阶
+2. 1 阶 + 2 阶
+3. 2 阶 + 1 阶
+
+## 思路
+
+这道题目 我们在[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) 中已经讲过一次了,原题其实是一道简单动规的题目。
+
+既然这么简单为什么还要讲呢,其实本题稍加改动就是一道面试好题。
+
+**改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?**
+
+1阶,2阶,.... m阶就是物品,楼顶就是背包。
+
+每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。
+
+问跳到楼顶有几种方法其实就是问装满背包有几种方法。
+
+**此时大家应该发现这就是一个完全背包问题了!**
+
+和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了。
+
+动规五部曲分析如下:
+
+1. 确定dp数组以及下标的含义
+
+**dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法**。
+
+2. 确定递推公式
+
+在[动态规划:494.目标和](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) 、 [动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)、[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中我们都讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];
+
+本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]
+
+那么递推公式为:dp[i] += dp[i - j]
+
+3. dp数组如何初始化
+
+既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。
+
+下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果
+
+4. 确定遍历顺序
+
+这是背包里求排列问题,即:**1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!**
+
+所以需将target放在外循环,将nums放在内循环。
+
+每一步可以走多次,这是完全背包,内循环需要从前向后遍历。
+
+5. 举例来推导dp数组
+
+介于本题和[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)几乎是一样的,这里我就不再重复举例了。
+
+
+以上分析完毕,C++代码如下:
+```
+class Solution {
+public:
+ int climbStairs(int n) {
+ vector dp(n + 1, 0);
+ dp[0] = 1;
+ for (int i = 1; i <= n; i++) { // 遍历背包
+ for (int j = 1; j <= m; j++) { // 遍历物品
+ if (i - j >= 0) dp[i] += dp[i - j];
+ }
+ }
+ return dp[n];
+ }
+};
+```
+
+代码中m表示最多可以爬m个台阶,代码中把m改成2就是本题70.爬楼梯可以AC的代码了。
+
+## 总结
+
+**本题看起来是一道简单题目,稍稍进阶一下其实就是一个完全背包!**
+
+如果我来面试的话,我就会先给候选人出一个 本题原题,看其表现,如果顺利写出来,进而在要求每次可以爬[1 - m]个台阶应该怎么写。
+
+顺便再考察一下两个for循环的嵌套顺序,为什么target放外面,nums放里面。
+
+这就能考察对背包问题本质的掌握程度,候选人是不是刷题背公式,一眼就看出来了。
+
+这么一连套下来,如果候选人都能答出来,相信任何一位面试官都是非常满意的。
+
+**本题代码不长,题目也很普通,但稍稍一进阶就可以考察完全背包,而且题目进阶的内容在leetcode上并没有原题,一定程度上就可以排除掉刷题党了,简直是面试题目的绝佳选择!**
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md
new file mode 100644
index 00000000..8e6e0187
--- /dev/null
+++ b/problems/0072.编辑距离.md
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 72. 编辑距离
+
+给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
+
+你可以对一个单词进行如下三种操作:
+
+* 插入一个字符
+* 删除一个字符
+* 替换一个字符
+
+示例 1:
+输入:word1 = "horse", word2 = "ros"
+输出:3
+解释:
+horse -> rorse (将 'h' 替换为 'r')
+rorse -> rose (删除 'r')
+rose -> ros (删除 'e')
+
+示例 2:
+输入:word1 = "intention", word2 = "execution"
+输出:5
+解释:
+intention -> inention (删除 't')
+inention -> enention (将 'i' 替换为 'e')
+enention -> exention (将 'n' 替换为 'x')
+exention -> exection (将 'n' 替换为 'c')
+exection -> execution (插入 'u')
+
+
+提示:
+
+* 0 <= word1.length, word2.length <= 500
+* word1 和 word2 由小写英文字母组成
+
+
+## 思路
+
+编辑距离终于来了,这道题目如果大家没有了解动态规划的话,会感觉超级复杂。
+
+编辑距离是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙的算出最少编辑距离。
+
+接下来我依然使用动规五部曲,对本题做一个详细的分析:
+
+1. 确定dp数组(dp table)以及下标的含义
+
+**dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]**。
+
+这里在强调一下:为啥要表示下标i-1为结尾的字符串呢,为啥不表示下标i为结尾的字符串呢?
+
+用i来表示也可以! 但我统一以下标i-1为结尾的字符串,在下面的递归公式中会容易理解一点。
+
+2. 确定递推公式
+
+在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:
+
+* if (word1[i - 1] == word2[j - 1])
+ * 不操作
+* if (word1[i - 1] != word2[j - 1])
+ * 增
+ * 删
+ * 换
+
+也就是如上四种情况。
+
+if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
+
+此时可能有同学有点不明白,为啥要即dp[i][j] = dp[i - 1][j - 1]呢?
+
+那么就在回顾上面讲过的dp[i][j]的定义,word1[i - 1] 与 word2[j - 1]相等了,那么就不用编辑了,以下标i-2为结尾的字符串word1和以下标j-2为结尾的字符串word2的最近编辑距离dp[i - 1][j - 1] 就是 dp[i][j]了。
+
+在下面的讲解中,如果哪里看不懂,就回想一下dp[i][j]的定义,就明白了。
+
+**在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!**
+
+if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?
+
+操作一:word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 i-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。
+
+即 dp[i][j] = dp[i - 1][j] + 1;
+
+
+操作二:word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。
+
+即 dp[i][j] = dp[i][j - 1] + 1;
+
+这里有同学发现了,怎么都是添加元素,删除元素去哪了。
+
+**word2添加一个元素,相当于word1删除一个元素**,例如 word1 = "ad" ,word2 = "a",word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!
+
+操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。
+
+即 dp[i][j] = dp[i - 1][j - 1] + 1;
+
+综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
+
+递归公式代码如下:
+
+```C++
+if (word1[i - 1] == word2[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1];
+}
+else {
+ dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
+}
+```
+
+3. dp数组如何初始化
+
+在回顾一下dp[i][j]的定义。
+
+**dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]**。
+
+那么dp[i][0] 和 dp[0][j] 表示什么呢?
+
+dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。
+
+那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;
+
+同理dp[0][j] = j;
+
+所以C++代码如下:
+
+```C++
+for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
+for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
+```
+
+4. 确定遍历顺序
+
+从如下四个递推公式:
+
+* dp[i][j] = dp[i - 1][j - 1]
+* dp[i][j] = dp[i - 1][j - 1] + 1
+* dp[i][j] = dp[i][j - 1] + 1
+* dp[i][j] = dp[i - 1][j] + 1
+
+可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图:
+
+
+
+所以在dp矩阵中一定是从左到右从上到下去遍历。
+
+代码如下:
+
+```C++
+for (int i = 1; i <= word1.size(); i++) {
+ for (int j = 1; j <= word2.size(); j++) {
+ if (word1[i - 1] == word2[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1];
+ }
+ else {
+ dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
+ }
+ }
+}
+```
+
+5. 举例推导dp数组
+
+以示例1,输入:word1 = "horse", word2 = "ros"为例,dp矩阵状态图如下:
+
+
+
+以上动规五部分析完毕,C++代码如下:
+
+```C++
+class Solution {
+public:
+ int minDistance(string word1, string word2) {
+ vector> dp(word1.size() + 1, vector(word2.size() + 1, 0));
+ for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
+ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
+ for (int i = 1; i <= word1.size(); i++) {
+ for (int j = 1; j <= word2.size(); j++) {
+ if (word1[i - 1] == word2[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1];
+ }
+ else {
+ dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
+ }
+ }
+ }
+ return dp[word1.size()][word2.size()];
+ }
+};
+```
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0077.组合.md b/problems/0077.组合.md
index 0a7187d1..3dfb5216 100644
--- a/problems/0077.组合.md
+++ b/problems/0077.组合.md
@@ -1,5 +1,13 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
-> 回溯法的第一道题目,就不简单呀!
# 第77题. 组合
@@ -21,7 +29,8 @@
也可以直接看我的B站视频:[带你学透回溯算法-组合问题(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv#reply3733925949)
-# 思路
+## 思路
+
本题这是回溯法的经典题目。
@@ -164,7 +173,7 @@ for循环每次从startIndex开始遍历,然后用path保存取到的节点i
代码如下:
-```
+```C++
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
@@ -179,7 +188,7 @@ backtracking的下面部分就是回溯的操作了,撤销本次处理的结
关键地方都讲完了,组合问题C++完整代码如下:
-```
+```C++
class Solution {
private:
vector> result; // 存放符合条件结果的集合
@@ -235,14 +244,114 @@ void backtracking(参数) {
接着用回溯法三部曲,逐步分析了函数参数、终止条件和单层搜索的过程。
-**本题其实是可以剪枝优化的,大家可以思考一下,具体如何剪枝我会在下一篇详细讲解,敬请期待!**
+# 剪枝优化
-**就酱,如果对你有帮助,就帮Carl转发一下吧,让更多的同学发现这里!**
+我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。
+
+在遍历的过程中有如下代码:
+
+```
+for (int i = startIndex; i <= n; i++) {
+ path.push_back(i);
+ backtracking(n, k, i + 1);
+ path.pop_back();
+}
+```
+
+这个遍历的范围是可以剪枝优化的,怎么优化呢?
+
+来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。
+
+这么说有点抽象,如图所示:
+
+
+
+图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
+
+**所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置**。
+
+**如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了**。
+
+注意代码中i,就是for循环里选择的起始位置。
+```
+for (int i = startIndex; i <= n; i++) {
+```
+
+接下来看一下优化过程如下:
+
+1. 已经选择的元素个数:path.size();
+
+2. 还需要的元素个数为: k - path.size();
+
+3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
+
+为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
+
+举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
+
+从2开始搜索都是合理的,可以是组合[2, 3, 4]。
+
+这里大家想不懂的话,建议也举一个例子,就知道是不是要+1了。
+
+所以优化之后的for循环是:
+
+```
+for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
+```
+
+优化后整体代码如下:
+
+```
+class Solution {
+private:
+ vector> result;
+ vector path;
+ void backtracking(int n, int k, int startIndex) {
+ if (path.size() == k) {
+ result.push_back(path);
+ return;
+ }
+ for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
+ path.push_back(i); // 处理节点
+ backtracking(n, k, i + 1);
+ path.pop_back(); // 回溯,撤销处理的节点
+ }
+ }
+public:
+
+ vector> combine(int n, int k) {
+ backtracking(n, k, 1);
+ return result;
+ }
+};
+```
+
+# 剪枝总结
+
+本篇我们准对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。
+
+所以我依然是把整个回溯过程抽象为一颗树形结构,然后可以直观的看出,剪枝究竟是剪的哪里。
-**[本题剪枝操作文章链接](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)**
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md
index 6f12abd0..2af123d1 100644
--- a/problems/0077.组合优化.md
+++ b/problems/0077.组合优化.md
@@ -1,9 +1,19 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
-> 如果想在电脑上看文章的话,可以看这里:https://github.com/youngyangyang04/leetcode-master,已经按照顺序整理了「代码随想录」的所有文章,可以fork到自己仓库里,随时复习。**那么重点来了,来都来了,顺便给一个star吧,哈哈**
在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们通过回溯搜索法,解决了n个数中求k个数的组合问题。
+> 可以直接看我的B栈视频讲解:[带你学透回溯算法-组合问题的剪枝操作](https://www.bilibili.com/video/BV1wi4y157er)
+
文中的回溯法是可以剪枝优化的,本篇我们继续来看一下题目77. 组合。
链接:https://leetcode-cn.com/problems/combinations/
@@ -23,7 +33,7 @@ private:
return;
}
for (int i = startIndex; i <= n; i++) {
- path.push_back(i); // 处理节点
+ path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归
path.pop_back(); // 回溯,撤销处理的节点
}
@@ -38,17 +48,17 @@ public:
};
```
-## 剪枝优化
+# 剪枝优化
我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。
-在遍历的过程中有如下代码:
+在遍历的过程中有如下代码:
```
-for (int i = startIndex; i <= n; i++) {
- path.push_back(i);
- backtracking(n, k, i + 1);
- path.pop_back();
+for (int i = startIndex; i <= n; i++) {
+ path.push_back(i);
+ backtracking(n, k, i + 1);
+ path.pop_back();
}
```
@@ -58,9 +68,10 @@ for (int i = startIndex; i <= n; i++) {
这么说有点抽象,如图所示:
-
+
-图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
+
+图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
**所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置**。
@@ -68,7 +79,7 @@ for (int i = startIndex; i <= n; i++) {
注意代码中i,就是for循环里选择的起始位置。
```
-for (int i = startIndex; i <= n; i++) {
+for (int i = startIndex; i <= n; i++) {
```
接下来看一下优化过程如下:
@@ -81,7 +92,7 @@ for (int i = startIndex; i <= n; i++) {
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
-举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
+举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
@@ -98,7 +109,7 @@ for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜
```
class Solution {
private:
- vector> result;
+ vector> result;
vector path;
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
@@ -106,7 +117,7 @@ private:
return;
}
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
- path.push_back(i); // 处理节点
+ path.push_back(i); // 处理节点
backtracking(n, k, i + 1);
path.pop_back(); // 回溯,撤销处理的节点
}
@@ -120,10 +131,34 @@ public:
};
```
-# 总结
+# 总结
本篇我们准对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。
所以我依然是把整个回溯过程抽象为一颗树形结构,然后可以直观的看出,剪枝究竟是剪的哪里。
**就酱,学到了就帮Carl转发一下吧,让更多的同学知道这里!**
+
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0078.子集.md b/problems/0078.子集.md
index 8a2ff8a3..8c68843d 100644
--- a/problems/0078.子集.md
+++ b/problems/0078.子集.md
@@ -1,36 +1,43 @@
-> 认识本质之后,这就是一道模板题
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 第78题. 子集
-题目地址:https://leetcode-cn.com/problems/subsets/
+## 第78题. 子集
+
+题目地址:https://leetcode-cn.com/problems/subsets/
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
-输入: nums = [1,2,3]
-输出:
-[
- [3],
- [1],
- [2],
- [1,2,3],
- [1,3],
- [2,3],
- [1,2],
- []
-]
+输入: nums = [1,2,3]
+输出:
+[
+ [3],
+ [1],
+ [2],
+ [1,2,3],
+ [1,3],
+ [2,3],
+ [1,2],
+ []
+]
-# 思路
+## 思路
求子集问题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:分割问题!](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)又不一样了。
-如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,**那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!**
+如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,**那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!**
-其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
+其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
-**那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!**
+**那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!**
有同学问了,什么时候for可以从0开始呢?
@@ -42,9 +49,9 @@
从图中红线部分,可以看出**遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合**。
-## 回溯三部曲
+## 回溯三部曲
-* 递归函数参数
+* 递归函数参数
全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)
@@ -58,13 +65,13 @@ vector path;
void backtracking(vector& nums, int startIndex) {
```
-* 递归终止条件
+* 递归终止条件
从图中可以看出:

-剩余集合为空的时候,就是叶子节点。
+剩余集合为空的时候,就是叶子节点。
那么什么时候剩余集合为空呢?
@@ -76,7 +83,7 @@ if (startIndex >= nums.size()) {
}
```
-**其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了**。
+**其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了**。
* 单层搜索逻辑
@@ -113,7 +120,7 @@ void backtracking(参数) {
可以写出如下回溯算法C++代码:
-```
+```C++
class Solution {
private:
vector> result;
@@ -140,13 +147,13 @@ public:
```
-在注释中,可以发现可以不写终止条件,因为本来我们就要遍历整颗树。
+在注释中,可以发现可以不写终止条件,因为本来我们就要遍历整颗树。
-有的同学可能担心不写终止条件会不会无限递归?
+有的同学可能担心不写终止条件会不会无限递归?
并不会,因为每次递归的下一层就是从i+1开始的。
-# 总结
+## 总结
相信大家经过了
* 组合问题:
@@ -166,10 +173,22 @@ public:
**而组合问题、分割问题是收集树形结构中叶子节点的结果**。
-**就酱,如果感觉收获满满,就帮Carl宣传一波「代码随想录」吧!**
+## 其他语言版本
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+Java:
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0083.删除排序链表中的重复元素.md b/problems/0083.删除排序链表中的重复元素.md
deleted file mode 100644
index b725f7f8..00000000
--- a/problems/0083.删除排序链表中的重复元素.md
+++ /dev/null
@@ -1,40 +0,0 @@
-
-## 题目地址
-
-https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
-
-## 思路
-
-这道题目没有必要设置虚拟节点,因为不会删除头结点
-
-## 代码
-
-```
-/**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * ListNode *next;
- * ListNode(int x) : val(x), next(NULL) {}
- * };
- */
-class Solution {
-public:
- ListNode* deleteDuplicates(ListNode* head) {
- ListNode* p = head;
- while (p != NULL && p->next!= NULL) {
- if (p->val == p->next->val) {
- ListNode* tmp = p->next;
- p->next = p->next->next;
- delete tmp;
- } else {
- p = p->next;
- }
- }
- return head;
- }
-};
-```
-
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
-
diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md
deleted file mode 100644
index e5057e55..00000000
--- a/problems/0084.柱状图中最大的矩形.md
+++ /dev/null
@@ -1,104 +0,0 @@
-
-# 链接
-
-https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
-
-## 思路
-
-```
-class Solution {
-public:
- int largestRectangleArea(vector& heights) {
- int sum = 0;
- for (int i = 0; i < heights.size(); i++) {
- int left = i;
- int right = i;
- for (; left >= 0; left--) {
- if (heights[left] < heights[i]) break;
- }
- for (; right < heights.size(); right++) {
- if (heights[right] < heights[i]) break;
- }
- int w = right - left - 1;
- int h = heights[i];
- sum = max(sum, w * h);
- }
- return sum;
- }
-};
-```
-
-如上代码并不能通过leetcode,超时了,因为时间复杂度是O(n^2)。
-
-## 思考一下动态规划
-
-## 单调栈
-
-单调栈的思路还是不容易理解的,
-
-想清楚从大到小,还是从小到大,
-
-本题是从栈底到栈头 从小到大,和 接雨水正好反过来。
-
-
-```
-class Solution {
-public:
- int largestRectangleArea(vector& heights) {
- stack st;
- heights.insert(heights.begin(), 0); // 数组头部加入元素0
- heights.push_back(0); // 数组尾部加入元素0
- st.push(0);
- int result = 0;
- // 第一个元素已经入栈,从下表1开始
- for (int i = 1; i < heights.size(); i++) {
- // 注意heights[i] 是和heights[st.top()] 比较 ,st.top()是下表
- if (heights[i] > heights[st.top()]) {
- st.push(i);
- } else if (heights[i] == heights[st.top()]) {
- st.pop(); // 这个可以加,可以不加,效果一样,思路不同
- st.push(i);
- } else {
- while (heights[i] < heights[st.top()]) { // 注意是while
- int mid = st.top();
- st.pop();
- int left = st.top();
- int right = i;
- int w = right - left - 1;
- int h = heights[mid];
- result = max(result, w * h);
- }
- st.push(i);
- }
- }
- return result;
- }
-};
-
-```
-
-代码精简之后:
-
-```
-class Solution {
-public:
- int largestRectangleArea(vector& heights) {
- stack st;
- heights.insert(heights.begin(), 0); // 数组头部加入元素0
- heights.push_back(0); // 数组尾部加入元素0
- st.push(0);
- int result = 0;
- for (int i = 1; i < heights.size(); i++) {
- while (heights[i] < heights[st.top()]) {
- int mid = st.top();
- st.pop();
- int w = i - st.top() - 1;
- int h = heights[mid];
- result = max(result, w * h);
- }
- st.push(i);
- }
- return result;
- }
-};
-```
diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md
index c8e29763..cc5fd571 100644
--- a/problems/0090.子集II.md
+++ b/problems/0090.子集II.md
@@ -1,7 +1,13 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 子集问题加去重!
-# 第90题.子集II
+## 第90题.子集II
题目链接:https://leetcode-cn.com/problems/subsets-ii/
@@ -9,20 +15,20 @@
说明:解集不能包含重复的子集。
-示例:
-输入: [1,2,2]
-输出:
-[
- [2],
- [1],
- [1,2,2],
- [2,2],
- [1,2],
- []
-]
+示例:
+输入: [1,2,2]
+输出:
+[
+ [2],
+ [1],
+ [1,2,2],
+ [2,2],
+ [1,2],
+ []
+]
-# 思路
+## 思路
做本题之前一定要先做[78.子集](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)。
@@ -40,7 +46,7 @@
本题就是其实就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了,所以我就直接给出代码了:
-# C++代码
+## C++代码
```
class Solution {
@@ -110,7 +116,45 @@ public:
```
-# 总结
+## 补充
+
+本题也可以不适用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。
+
+如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。
+
+代码如下:
+
+```C++
+class Solution {
+private:
+ vector> result;
+ vector path;
+ void backtracking(vector& nums, int startIndex) {
+ result.push_back(path);
+ for (int i = startIndex; i < nums.size(); i++) {
+ // 而我们要对同一树层使用过的元素进行跳过
+ if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
+ continue;
+ }
+ path.push_back(nums[i]);
+ backtracking(nums, i + 1);
+ path.pop_back();
+ }
+ }
+
+public:
+ vector> subsetsWithDup(vector& nums) {
+ result.clear();
+ path.clear();
+ sort(nums.begin(), nums.end()); // 去重需要排序
+ backtracking(nums, 0);
+ return result;
+ }
+};
+
+```
+
+## 总结
其实这道题目的知识点,我们之前都讲过了,如果之前讲过的子集问题和去重问题都掌握的好,这道题目应该分分钟AC。
@@ -122,11 +166,24 @@ if (i > startIndex && nums[i] == nums[i - 1] ) {
}
```
-**就酱,如果感觉融会贯通了,就把「代码随想录」介绍给自己的同学朋友吧,也许他们也需要!**
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md
index e691852e..4cea7a3a 100644
--- a/problems/0093.复原IP地址.md
+++ b/problems/0093.复原IP地址.md
@@ -1,6 +1,14 @@
-> 一些录友表示跟不上现在的节奏,想从头开始打卡学习起来,可以在公众号左下方,「算法汇总」可以找到历史文章,都是按系列排好顺序的,挨个看就可以了,看文章下的留言你就会发现,有很多录友都在从头打卡,你并不孤单!
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 93.复原IP地址
+
+
+## 93.复原IP地址
题目地址:https://leetcode-cn.com/problems/restore-ip-addresses/
@@ -10,32 +18,32 @@
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。
-示例 1:
-输入:s = "25525511135"
-输出:["255.255.11.135","255.255.111.35"]
+示例 1:
+输入:s = "25525511135"
+输出:["255.255.11.135","255.255.111.35"]
-示例 2:
-输入:s = "0000"
-输出:["0.0.0.0"]
+示例 2:
+输入:s = "0000"
+输出:["0.0.0.0"]
示例 3:
-输入:s = "1111"
-输出:["1.1.1.1"]
+输入:s = "1111"
+输出:["1.1.1.1"]
-示例 4:
-输入:s = "010010"
-输出:["0.10.0.10","0.100.1.0"]
+示例 4:
+输入:s = "010010"
+输出:["0.10.0.10","0.100.1.0"]
-示例 5:
-输入:s = "101023"
-输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
+示例 5:
+输入:s = "101023"
+输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
-提示:
-0 <= s.length <= 3000
-s 仅由数字组成
+提示:
+0 <= s.length <= 3000
+s 仅由数字组成
-# 思路
+## 思路
做这道题目之前,最好先把[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)这个做了。
@@ -48,9 +56,9 @@ s 仅由数字组成

-## 回溯三部曲
+## 回溯三部曲
-* 递归参数
+* 递归参数
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我们就提到切割问题类似组合问题。
@@ -86,7 +94,7 @@ if (pointNum == 3) { // 逗点数量为3时,分隔结束
}
```
-* 单层搜索的逻辑
+* 单层搜索的逻辑
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中已经讲过在循环遍历中如何截取子串。
@@ -118,14 +126,14 @@ for (int i = startIndex; i < s.size(); i++) {
}
```
-## 判断子串是否合法
+## 判断子串是否合法
最后就是在写一个判断段位是否是有效段位了。
主要考虑到如下三点:
* 段位以0为开头的数字不合法
-* 段位里有非正整数字符不合法
+* 段位里有非正整数字符不合法
* 段位如果大于255了不合法
代码如下:
@@ -153,7 +161,7 @@ bool isValid(const string& s, int start, int end) {
}
```
-## C++代码
+## C++代码
根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板:
@@ -175,7 +183,7 @@ void backtracking(参数) {
可以写出如下回溯算法C++代码:
-```
+```C++
class Solution {
private:
vector result;// 记录结果
@@ -229,7 +237,7 @@ public:
```
-# 总结
+## 总结
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我列举的分割字符串的难点,本题都覆盖了。
@@ -239,15 +247,100 @@ public:
在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少!
-**就酱,「代码随想录」值得推荐给你的朋友们!**
-一些录友表示跟不上现在的节奏,想从头开始打卡学习起来,可以在在公众号左下方,「算法汇总」可以找到历史文章,都是按系列排好顺序的,挨个看就可以了,别忘了打卡。
+## 其他语言版本
-**很多录友都在从头开始打卡学习,看看前面文章的留言区就知道了,你并不孤单!**
+java 版本:
+
+```java
+class Solution {
+ List result = new ArrayList<>();
+
+ public List restoreIpAddresses(String s) {
+ if (s.length() > 12) return result; // 算是剪枝了
+ backTrack(s, 0, 0);
+ return result;
+ }
+
+ // startIndex: 搜索的起始位置, pointNum:添加逗点的数量
+ private void backTrack(String s, int startIndex, int pointNum) {
+ if (pointNum == 3) {// 逗点数量为3时,分隔结束
+ // 判断第四段⼦字符串是否合法,如果合法就放进result中
+ if (isValid(s,startIndex,s.length()-1)) {
+ result.add(s);
+ }
+ return;
+ }
+ for (int i = startIndex; i < s.length(); i++) {
+ if (isValid(s, startIndex, i)) {
+ s = s.substring(0, i + 1) + "." + s.substring(i + 1); //在str的后⾯插⼊⼀个逗点
+ pointNum++;
+ backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
+ pointNum--;// 回溯
+ s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
+ } else {
+ break;
+ }
+ }
+ }
+
+ // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
+ private Boolean isValid(String s, int start, int end) {
+ if (start > end) {
+ return false;
+ }
+ if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
+ return false;
+ }
+ int num = 0;
+ for (int i = start; i <= end; i++) {
+ if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
+ return false;
+ }
+ num = num * 10 + (s.charAt(i) - '0');
+ if (num > 255) { // 如果⼤于255了不合法
+ return false;
+ }
+ }
+ return true;
+ }
+}
+```
+
+python版本:
+
+```python
+class Solution(object):
+ def restoreIpAddresses(self, s):
+ """
+ :type s: str
+ :rtype: List[str]
+ """
+ ans = []
+ path = []
+ def backtrack(path, startIndex):
+ if len(path) == 4:
+ if startIndex == len(s):
+ ans.append(".".join(path[:]))
+ return
+ for i in range(startIndex+1, min(startIndex+4, len(s)+1)): # 剪枝
+ string = s[startIndex:i]
+ if not 0 <= int(string) <= 255:
+ continue
+ if not string == "0" and not string.lstrip('0') == string:
+ continue
+ path.append(string)
+ backtrack(path, i)
+ path.pop()
+
+ backtrack([], 0)
+ return ans```
+```
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
-
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0094.二叉树的中序遍历.md b/problems/0094.二叉树的中序遍历.md
deleted file mode 100644
index e63819c1..00000000
--- a/problems/0094.二叉树的中序遍历.md
+++ /dev/null
@@ -1,84 +0,0 @@
-## 题目地址
-https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
-
-## 思路
-
-详细题解请看这篇:[一文学通二叉树前中后序递归法与迭代法](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
-
-## C++代码
-
-### 递归
-```
-class Solution {
-public:
- void traversal(TreeNode* cur, vector& vec) {
- if (cur == NULL) return;
- traversal(cur->left, vec);
- vec.push_back(cur->val);
- traversal(cur->right, vec);
- }
- vector inorderTraversal(TreeNode* root) {
- vector result;
- traversal(root, result);
- return result;
- }
-};
-```
-
-### 栈
-```
-class Solution {
-public:
- vector inorderTraversal(TreeNode* root) {
- vector result;
- stack st;
- TreeNode* cur = root;
- while (cur != NULL || !st.empty()) {
- if (cur != NULL) {
- st.push(cur);
- cur = cur->left;
- } else {
- cur = st.top();
- st.pop();
- result.push_back(cur->val);
- cur = cur->right;
- }
- }
- return result;
- }
-};
-```
-
-### 栈 通用模板
-
-```
-class Solution {
-public:
- vector inorderTraversal(TreeNode* root) {
- vector result;
- stack st;
- if (root != NULL) st.push(root);
- while (!st.empty()) {
- TreeNode* node = st.top();
- if (node != NULL) {
- st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
- if (node->right) st.push(node->right); // 添加右节点
-
- st.push(node); // 添加中节点
- st.push(NULL); // 中节点访问过,但是还没有处理,需要做一下标记。
-
- if (node->left) st.push(node->left); // 添加左节点
- } else {
- st.pop(); // 将空节点弹出
- node = st.top(); // 重新取出栈中元素
- st.pop();
- result.push_back(node->val); // 加入到数组中
- }
- }
- return result;
- }
-};
-```
-
-
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md
new file mode 100644
index 00000000..2764277c
--- /dev/null
+++ b/problems/0096.不同的二叉搜索树.md
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 96.不同的二叉搜索树
+
+题目链接:https://leetcode-cn.com/problems/unique-binary-search-trees/
+
+给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
+
+示例:
+
+
+
+## 思路
+
+这道题目描述很简短,但估计大部分同学看完都是懵懵的状态,这得怎么统计呢?
+
+关于什么是二叉搜索树,我们之前在讲解二叉树专题的时候已经详细讲解过了,也可以看看这篇[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)在回顾一波。
+
+了解了二叉搜索树之后,我们应该先举几个例子,画画图,看看有没有什么规律,如图:
+
+
+
+n为1的时候有一棵树,n为2有两棵树,这个是很直观的。
+
+
+
+来看看n为3的时候,有哪几种情况。
+
+当1为头结点的时候,其右子树有两个节点,看这两个节点的布局,是不是和 n 为2的时候两棵树的布局是一样的啊!
+
+(可能有同学问了,这布局不一样啊,节点数值都不一样。别忘了我们就是求不同树的数量,并不用把搜索树都列出来,所以不用关心其具体数值的差异)
+
+当3为头结点的时候,其左子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是一样的啊!
+
+当2位头结点的时候,其左右子树都只有一个节点,布局是不是和n为1的时候只有一棵树的布局也是一样的啊!
+
+发现到这里,其实我们就找到的重叠子问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种方式。
+
+思考到这里,这道题目就有眉目了。
+
+dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
+
+元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
+
+元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
+
+元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
+
+有2个元素的搜索树数量就是dp[2]。
+
+有1个元素的搜索树数量就是dp[1]。
+
+有0个元素的搜索树数量就是dp[0]。
+
+所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
+
+如图所示:
+
+
+
+
+此时我们已经找到的递推关系了,那么可以用动规五部曲在系统分析一遍。
+
+1. 确定dp数组(dp table)以及下标的含义
+
+**dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]**。
+
+也可以理解是i的不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。
+
+以下分析如果想不清楚,就来回想一下dp[i]的定义
+
+2. 确定递推公式
+
+在上面的分析中,其实已经看出其递推关系, dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量]
+
+j相当于是头结点的元素,从1遍历到i为止。
+
+所以递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
+
+3. dp数组如何初始化
+
+初始化,只需要初始化dp[0]就可以了,推导的基础,都是dp[0]。
+
+那么dp[0]应该是多少呢?
+
+从定义上来讲,空节点也是一颗二叉树,也是一颗二叉搜索树,这是可以说得通的。
+
+从递归公式上来讲,dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] 中以j为头结点左子树节点数量为0,也需要dp[以j为头结点左子树节点数量] = 1, 否则乘法的结果就都变成0了。
+
+所以初始化dp[0] = 1
+
+4. 确定遍历顺序
+
+首先一定是遍历节点数,从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。
+
+那么遍历i里面每一个数作为头结点的状态,用j来遍历。
+
+代码如下:
+
+```C++
+for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= i; j++) {
+ dp[i] += dp[j - 1] * dp[i - j];
+ }
+}
+```
+
+5. 举例推导dp数组
+
+n为5时候的dp数组状态如图:
+
+
+
+当然如果自己画图举例的话,基本举例到n为3就可以了,n为4的时候,画图已经比较麻烦了。
+
+**我这里列到了n为5的情况,是为了方便大家 debug代码的时候,把dp数组打出来,看看哪里有问题**。
+
+综上分析完毕,C++代码如下:
+
+```C++
+class Solution {
+public:
+ int numTrees(int n) {
+ vector dp(n + 1);
+ dp[0] = 1;
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= i; j++) {
+ dp[i] += dp[j - 1] * dp[i - j];
+ }
+ }
+ return dp[n];
+ }
+};
+```
+* 时间复杂度O(n^2)
+* 空间复杂度O(n)
+
+大家应该发现了,我们分析了这么多,最后代码却如此简单!
+
+## 总结
+
+这道题目虽然在力扣上标记是中等难度,但可以算是困难了!
+
+首先这道题想到用动规的方法来解决,就不太好想,需要举例,画图,分析,才能找到递推的关系。
+
+然后难点就是确定递推公式了,如果把递推公式想清楚了,遍历顺序和初始化,就是自然而然的事情了。
+
+可以看出我依然还是用动规五部曲来进行分析,会把题目的方方面面都覆盖到!
+
+**而且具体这五部分析是我自己平时总结的经验,找不出来第二个的,可能过一阵子 其他题解也会有动规五部曲了,哈哈**。
+
+当时我在用动规五部曲讲解斐波那契的时候,一些录友和我反应,感觉讲复杂了。
+
+其实当时我一直强调简单题是用来练习方法论的,并不能因为简单我就代码一甩,简单解释一下就完事了。
+
+可能当时一些同学不理解,现在大家应该感受方法论的重要性了,加油💪
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md
index ac827324..baa3f435 100644
--- a/problems/0098.验证二叉搜索树.md
+++ b/problems/0098.验证二叉搜索树.md
@@ -1,8 +1,16 @@
-## 题目地址
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 学习完二叉搜索树的特性了,那么就验证一波
-# 98.验证二叉搜索树
+## 98.验证二叉搜索树
+
+题目地址:https://leetcode-cn.com/problems/validate-binary-search-tree/
+
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
@@ -12,9 +20,9 @@
* 节点的右子树只包含大于当前节点的数。
* 所有左子树和右子树自身必须也是二叉搜索树。
-
+
-# 思路
+## 思路
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
@@ -75,9 +83,9 @@ public:
这道题目比较容易陷入两个陷阱:
-* 陷阱1
+* 陷阱1
-**不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了**。
+**不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了**。
写出了类似这样的代码:
@@ -97,7 +105,7 @@ if (root->val > root->left->val && root->val < root->right->val) {
节点10小于左节点5,大于右节点15,但右子树里出现了一个6 这就不符合了!
-* 陷阱2
+* 陷阱2
样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。
@@ -109,7 +117,7 @@ if (root->val > root->left->val && root->val < root->right->val) {
递归三部曲:
-* 确定递归函数,返回值以及参数
+* 确定递归函数,返回值以及参数
要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。
@@ -117,14 +125,14 @@ if (root->val > root->left->val && root->val < root->right->val) {
其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。
-代码如下:
+代码如下:
```
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
-bool isValidBST(TreeNode* root)
+bool isValidBST(TreeNode* root)
```
-* 确定终止条件
+* 确定终止条件
如果是空节点 是不是二叉搜索树呢?
@@ -136,11 +144,11 @@ bool isValidBST(TreeNode* root)
if (root == NULL) return true;
```
-* 确定单层递归的逻辑
+* 确定单层递归的逻辑
-中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。
+中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。
-代码如下:
+代码如下:
```
bool left = isValidBST(root->left); // 左
@@ -172,7 +180,7 @@ public:
};
```
-以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。
+以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。
如果测试数据中有 longlong的最小值,怎么办?
@@ -183,7 +191,7 @@ public:
```
class Solution {
public:
- TreeNode* pre = NULL; // 用来记录前一个节点
+ TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
@@ -199,7 +207,7 @@ public:
最后这份代码看上去整洁一些,思路也清晰。
-## 迭代法
+## 迭代法
可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)
@@ -233,7 +241,7 @@ public:
在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。
-# 总结
+## 总结
这道题目是一个简单题,但对于没接触过的同学还是有难度的。
@@ -241,6 +249,23 @@ public:
只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。
-**就酱,学到了的话,就转发给身边需要的同学吧!**
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0100.相同的树.md b/problems/0100.相同的树.md
deleted file mode 100644
index ac495f51..00000000
--- a/problems/0100.相同的树.md
+++ /dev/null
@@ -1,163 +0,0 @@
-## 题目地址
-https://leetcode-cn.com/problems/same-tree/
-
-
-# 100. 相同的树
-
-给定两个二叉树,编写一个函数来检验它们是否相同。
-
-如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
-
-
-
-# 思路
-
-在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
-
-理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。
-
-**如果没有读过[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)这一篇,请认真读完再做这道题,就会有感觉了。**
-
-递归三部曲中:
-
-1. 确定递归函数的参数和返回值
-
-我们要比较的是两个树是否是相互相同的,参数也就是两个树的根节点。
-
-返回值自然是bool类型。
-
-代码如下:
-```
-bool compare(TreeNode* tree1, TreeNode* tree2)
-```
-
-分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。
-
-2. 确定终止条件
-
-**要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。**
-
-节点为空的情况有:
-
-* tree1为空,tree2不为空,不对称,return false
-* tree1不为空,tree2为空,不对称 return false
-* tree1,tree2都为空,对称,返回true
-
-此时已经排除掉了节点为空的情况,那么剩下的就是tree1和tree2不为空的时候:
-
-* tree1、tree2都不为空,比较节点数值,不相同就return false
-
-此时tree1、tree2节点不为空,且数值也不相同的情况我们也处理了。
-
-代码如下:
-```
-if (tree1 == NULL && tree2 != NULL) return false;
-else if (tree1 != NULL && tree2 == NULL) return false;
-else if (tree1 == NULL && tree2 == NULL) return true;
-else if (tree1->val != tree2->val) return false; // 注意这里我没有使用else
-```
-
-分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)
-
-3. 确定单层递归的逻辑
-
-* 比较二叉树是否相同 :传入的是tree1的左孩子,tree2的右孩子。
-* 如果左右都相同就返回true ,有一侧不相同就返回false 。
-
-代码如下:
-
-```
-bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左
-bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右
-bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理)
-return isSame;
-```
-最后递归的C++整体代码如下:
-
-```
-class Solution {
-public:
- bool compare(TreeNode* tree1, TreeNode* tree2) {
- if (tree1 == NULL && tree2 != NULL) return false;
- else if (tree1 != NULL && tree2 == NULL) return false;
- else if (tree1 == NULL && tree2 == NULL) return true;
- else if (tree1->val != tree2->val) return false; // 注意这里我没有使用else
-
- // 此时就是:左右节点都不为空,且数值相同的情况
- // 此时才做递归,做下一层的判断
- bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左
- bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右
- bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理)
- return isSame;
-
- }
- bool isSameTree(TreeNode* p, TreeNode* q) {
- return compare(p, q);
- }
-};
-```
-
-
-**我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。**
-
-如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。
-
-**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。**
-
-当然我可以把如上代码整理如下:
-
-## 递归
-
-```
-class Solution {
-public:
- bool compare(TreeNode* left, TreeNode* right) {
- if (left == NULL && right != NULL) return false;
- else if (left != NULL && right == NULL) return false;
- else if (left == NULL && right == NULL) return true;
- else if (left->val != right->val) return false;
- else return compare(left->left, right->left) && compare(left->right, right->right);
-
- }
- bool isSameTree(TreeNode* p, TreeNode* q) {
- return compare(p, q);
- }
-};
-```
-
-## 迭代法
-
-```
-lass Solution {
-public:
-
- bool isSameTree(TreeNode* p, TreeNode* q) {
- if (p == NULL && q == NULL) return true;
- if (p == NULL || q == NULL) return false;
- queue que;
- que.push(p); //
- que.push(q); //
- while (!que.empty()) { //
- TreeNode* leftNode = que.front(); que.pop();
- TreeNode* rightNode = que.front(); que.pop();
- if (!leftNode && !rightNode) { //
- continue;
- }
- //
- if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
- return false;
- }
- que.push(leftNode->left); //
- que.push(rightNode->left); //
- que.push(leftNode->right); //
- que.push(rightNode->right); //
- }
- return true;
- }
-};
-```
-
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
-
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
-
diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md
index b454aa7d..561d0470 100644
--- a/problems/0101.对称二叉树.md
+++ b/problems/0101.对称二叉树.md
@@ -1,17 +1,23 @@
-## 题目地址
-https://leetcode-cn.com/problems/symmetric-tree/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 又是一道“简单题”
-# 101. 对称二叉树
+## 101. 对称二叉树
-给定一个二叉树,检查它是否是镜像对称的。
+题目地址:https://leetcode-cn.com/problems/symmetric-tree/
-
+给定一个二叉树,检查它是否是镜像对称的。
-# 思路
+
-**首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!**
+## 思路
+
+**首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!**
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
@@ -19,7 +25,7 @@ https://leetcode-cn.com/problems/symmetric-tree/
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
-
+
那么遍历的顺序应该是什么样的呢?
@@ -35,11 +41,11 @@ https://leetcode-cn.com/problems/symmetric-tree/
那么我们先来看看递归法的代码应该怎么写。
-## 递归法
+## 递归法
-### 递归三部曲
+递归三部曲
-1. 确定递归函数的参数和返回值
+1. 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
@@ -50,15 +56,15 @@ https://leetcode-cn.com/problems/symmetric-tree/
bool compare(TreeNode* left, TreeNode* right)
```
-2. 确定终止条件
+2. 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(**注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点**)
-* 左节点为空,右节点不为空,不对称,return false
+* 左节点为空,右节点不为空,不对称,return false
* 左不为空,右为空,不对称 return false
-* 左右都为空,对称,返回true
+* 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
@@ -70,19 +76,19 @@ bool compare(TreeNode* left, TreeNode* right)
```
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
-else if (left == NULL && right == NULL) return true;
+else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false; // 注意这里我没有使用else
```
注意上面最后一种情况,我没有使用else,而是elseif, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
-3. 确定单层递归的逻辑
+3. 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。
* 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
-* 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
+* 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
* 如果左右都对称就返回true ,有一侧不对称就返回false 。
代码如下:
@@ -153,7 +159,7 @@ public:
**所以建议大家做题的时候,一定要想清楚逻辑,每一步做什么。把道题目所有情况想到位,相应的代码写出来之后,再去追求简洁代码的效果。**
-## 迭代法
+## 迭代法
这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。
@@ -163,7 +169,8 @@ public:
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:
-
+
+
如下的条件判断和递归的逻辑是一样的。
@@ -179,14 +186,14 @@ public:
que.push(root->left); // 将左子树头结点加入队列
que.push(root->right); // 将右子树头结点加入队列
while (!que.empty()) { // 接下来就要判断这这两个树是否相互翻转
- TreeNode* leftNode = que.front(); que.pop();
+ TreeNode* leftNode = que.front(); que.pop();
TreeNode* rightNode = que.front(); que.pop();
if (!leftNode && !rightNode) { // 左节点为空、右节点为空,此时说明是对称的
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同,返回false
- if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
+ if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
que.push(leftNode->left); // 加入左节点左孩子
@@ -232,7 +239,7 @@ public:
};
```
-# 总结
+## 总结
这次我们又深度剖析了一道二叉树的“简单题”,大家会发现,真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。
@@ -243,4 +250,38 @@ public:
如果已经做过这道题目的同学,读完文章可以再去看看这道题目,思考一下,会有不一样的发现!
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+JavaScript
+```javascript
+var isSymmetric = function(root) {
+ return check(root, root)
+};
+
+const check = (leftPtr, rightPtr) => {
+ // 如果只有根节点,返回true
+ if (!leftPtr && !rightPtr) return true
+ // 如果左右节点只存在一个,则返回false
+ if (!leftPtr || !rightPtr) return false
+
+ return leftPtr.val === rightPtr.val && check(leftPtr.left, rightPtr.right) && check(leftPtr.right, rightPtr.left)
+}
+```
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md
index 7b404623..cfbe09f3 100644
--- a/problems/0102.二叉树的层序遍历.md
+++ b/problems/0102.二叉树的层序遍历.md
@@ -1,7 +1,12 @@
-## 题目地址
-https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 我要打十个!
+# 二叉树的层序遍历
看完这篇文章虽然不能打十个,但是可以迅速打八个!而且够快!
@@ -13,17 +18,19 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
* 637.二叉树的层平均值
* 429.N叉树的前序遍历
* 515.在每个树行中找最大值
-* 116. 填充每个节点的下一个右侧节点指针
+* 116. 填充每个节点的下一个右侧节点指针
* 117.填充每个节点的下一个右侧节点指针II
-# 102.二叉树的层序遍历
+## 102.二叉树的层序遍历
+
+题目地址:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
-
+
-## 思路
+思路:
我们之前讲过了三篇关于二叉树的深度优先遍历的文章:
@@ -41,14 +48,13 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
使用队列实现二叉树广度优先遍历,动画如下:
-
-
+
这样就实现了层序从左到右遍历二叉树。
代码如下:**这份代码也可以作为二叉树层序遍历的模板,以后再打七个就靠它了**。
-## C++代码
+C++代码:
```
class Solution {
@@ -77,19 +83,21 @@ public:
**此时我们就掌握了二叉树的层序遍历了,那么如下五道leetcode上的题目,只需要修改模板的一两行代码(不能再多了),便可打倒!**
-# 107.二叉树的层次遍历 II
+## 107.二叉树的层次遍历 II
-给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
+题目链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
-
+给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
-## 思路
+
-相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
+思路:
-## C++代码
+相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
-```
+C++代码:
+
+```C++
class Solution {
public:
vector> levelOrderBottom(TreeNode* root) {
@@ -99,7 +107,7 @@ public:
while (!que.empty()) {
int size = que.size();
vector vec;
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
@@ -116,19 +124,21 @@ public:
```
-# 199.二叉树的右视图
+## 199.二叉树的右视图
+
+题目链接:https://leetcode-cn.com/problems/binary-tree-right-side-view/
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
-
+
-## 思路
+思路:
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
vector rightSideView(TreeNode* root) {
@@ -150,19 +160,21 @@ public:
};
```
-# 637.二叉树的层平均值
+## 637.二叉树的层平均值
-给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
+题目链接:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/
-
+给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
-## 思路
+
+
+思路:
本题就是层序遍历的时候把一层求个总和在取一个均值。
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
vector averageOfLevels(TreeNode* root) {
@@ -172,7 +184,7 @@ public:
while (!que.empty()) {
int size = que.size();
double sum = 0; // 统计每一层的和
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
sum += node->val;
@@ -187,31 +199,32 @@ public:
```
-# 429.N叉树的层序遍历
+## 429.N叉树的层序遍历
+
+题目链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
-
-
+
返回其层序遍历:
-[
- [1],
- [3,2,4],
- [5,6]
-]
+[
+ [1],
+ [3,2,4],
+ [5,6]
+]
-## 思路
+思路:
-这道题依旧是模板题,只不过一个节点有多个孩子了
+这道题依旧是模板题,只不过一个节点有多个孩子了
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
vector> levelOrder(Node* root) {
@@ -221,7 +234,7 @@ public:
while (!que.empty()) {
int size = que.size();
vector vec;
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < size; i++) {
Node* node = que.front();
que.pop();
vec.push_back(node->val);
@@ -237,19 +250,21 @@ public:
};
```
-# 515.在每个树行中找最大值
+## 515.在每个树行中找最大值
-您需要在二叉树的每一行中找到最大的值。
+题目链接:https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/
-
+您需要在二叉树的每一行中找到最大的值。
-## 思路
+
+
+思路:
层序遍历,取每一层的最大值
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
vector largestValues(TreeNode* root) {
@@ -273,29 +288,35 @@ public:
};
```
-# 116.填充每个节点的下一个右侧节点指针
+## 116.填充每个节点的下一个右侧节点指针
+
+题目链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
+```
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
+```
+
+
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
-
+
-## 思路
+思路:
本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
Node* connect(Node* root) {
@@ -328,15 +349,17 @@ public:
};
```
-# 117.填充每个节点的下一个右侧节点指针II
+## 117.填充每个节点的下一个右侧节点指针II
-## 思路
+题目地址:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/
+
+思路:
这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道
-## C++代码
+C++代码:
-```
+```C++
class Solution {
public:
Node* connect(Node* root) {
@@ -369,7 +392,7 @@ public:
```
-# 总结
+## 总结
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。
@@ -381,13 +404,32 @@ public:
* 637.二叉树的层平均值
* 429.N叉树的前序遍历
* 515.在每个树行中找最大值
-* 116. 填充每个节点的下一个右侧节点指针
+* 116. 填充每个节点的下一个右侧节点指针
* 117.填充每个节点的下一个右侧节点指针II
-如果非要打十个,还得找叶师傅!
+如果非要打十个,还得找叶师傅!
-
+
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md
index 3aa5c4b6..814beb55 100644
--- a/problems/0104.二叉树的最大深度.md
+++ b/problems/0104.二叉树的最大深度.md
@@ -1,15 +1,19 @@
-(寻找更节点可以用unordered_map来优化一下,元素都是独一无二的)
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-## 题目地址
-https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
-
-> “简单题”系列
看完本篇可以一起做了如下两道题目:
* 104.二叉树的最大深度
* 559.N叉树的最大深度
-# 104.二叉树的最大深度
+## 104.二叉树的最大深度
+
+题目地址:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
给定一个二叉树,找出其最大深度。
@@ -17,16 +21,14 @@ https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
说明: 叶子节点是指没有子节点的节点。
-示例:
-给定二叉树 [3,9,20,null,null,15,7],
+示例:
+给定二叉树 [3,9,20,null,null,15,7],
-
+
返回它的最大深度 3 。
-# 思路
-
-## 递归法
+### 递归法
本题其实也要后序遍历(左右中),依然是因为要通过递归函数的返回值做计算树的高度。
@@ -41,7 +43,7 @@ int getDepth(TreeNode* node)
2. 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
-代码如下:
+代码如下:
```
if (node == NULL) return 0;
```
@@ -59,7 +61,7 @@ return depth;
所以整体C++代码如下:
-```
+```C++
class Solution {
public:
int getDepth(TreeNode* node) {
@@ -76,7 +78,7 @@ public:
```
代码精简之后C++代码如下:
-```
+```C++
class Solution {
public:
int maxDepth(TreeNode* root) {
@@ -90,7 +92,7 @@ public:
**精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。**
-## 迭代法
+### 迭代法
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
@@ -104,7 +106,7 @@ public:
C++代码如下:
-```
+```C++
class Solution {
public:
int maxDepth(TreeNode* root) {
@@ -127,10 +129,11 @@ public:
};
```
-那么我们可以顺便解决一下N叉树的最大深度问题
+那么我们可以顺便解决一下N叉树的最大深度问题
-# 559.N叉树的最大深度
-https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/
+## 559.N叉树的最大深度
+
+题目地址:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/
给定一个 N 叉树,找到其最大深度。
@@ -138,19 +141,19 @@ https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/
例如,给定一个 3叉树 :
-
+
我们应返回其最大深度,3。
-# 思路
+思路:
依然可以提供递归法和迭代法,来解决这个问题,思路是和二叉树思路一样的,直接给出代码如下:
-## 递归法
+### 递归法
C++代码:
-```
+```C++
class Solution {
public:
int maxDepth(Node* root) {
@@ -163,17 +166,17 @@ public:
}
};
```
-## 迭代法
+### 迭代法
依然是层序遍历,代码如下:
-```
+```C++
class Solution {
public:
int maxDepth(Node* root) {
queue que;
if (root != NULL) que.push(root);
- int depth = 0;
+ int depth = 0;
while (!que.empty()) {
int size = que.size();
depth++; // 记录深度
@@ -190,9 +193,9 @@ public:
};
```
-使用栈来模拟后序遍历依然可以
+使用栈来模拟后序遍历依然可以
-```
+```C++
class Solution {
public:
int maxDepth(TreeNode* root) {
@@ -223,4 +226,23 @@ public:
}
};
```
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0105.从前序与中序遍历序列构造二叉树.md b/problems/0105.从前序与中序遍历序列构造二叉树.md
deleted file mode 100644
index eb39b313..00000000
--- a/problems/0105.从前序与中序遍历序列构造二叉树.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# 链接
-
-https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
-
-# 思路:
-
-详细见
-[0106.从中序与后序遍历序列构造二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0106.从中序与后序遍历序列构造二叉树.md)
diff --git a/problems/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md
index 892f2167..f1f30b71 100644
--- a/problems/0106.从中序与后序遍历序列构造二叉树.md
+++ b/problems/0106.从中序与后序遍历序列构造二叉树.md
@@ -1,15 +1,20 @@
-## 题目地址
-https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-> 给出两个序列 (可以加unorder_map优化一下)
-看完本文,可以一起解决如下两道题目
+看完本文,可以一起解决如下两道题目
-* 106.从中序与后序遍历序列构造二叉树
+* 106.从中序与后序遍历序列构造二叉树
* 105.从前序与中序遍历序列构造二叉树
+## 106.从中序与后序遍历序列构造二叉树
-# 106.从中序与后序遍历序列构造二叉树
+题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
根据一棵树的中序遍历与后序遍历构造二叉树。
@@ -22,9 +27,9 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
-
+
-## 思路
+### 思路
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
@@ -32,7 +37,7 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
流程如图:
-
+
那么代码应该怎么写呢?
@@ -42,9 +47,9 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
* 第一步:如果数组大小为零的话,说明是空节点了。
-* 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
+* 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-* 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
+* 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
* 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
@@ -54,34 +59,34 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
不难写出如下代码:(先把框架写出来)
-```
- TreeNode* traversal (vector& inorder, vector& postorder) {
+```C++
+TreeNode* traversal (vector& inorder, vector& postorder) {
- // 第一步
- if (postorder.size() == 0) return NULL;
+ // 第一步
+ if (postorder.size() == 0) return NULL;
- // 第二步:后序遍历数组最后一个元素,就是当前的中间节点
- int rootValue = postorder[postorder.size() - 1];
- TreeNode* root = new TreeNode(rootValue);
+ // 第二步:后序遍历数组最后一个元素,就是当前的中间节点
+ int rootValue = postorder[postorder.size() - 1];
+ TreeNode* root = new TreeNode(rootValue);
- // 叶子节点
- if (postorder.size() == 1) return root;
+ // 叶子节点
+ if (postorder.size() == 1) return root;
- // 第三步:找切割点
- int delimiterIndex;
- for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
- if (inorder[delimiterIndex] == rootValue) break;
- }
-
- // 第四步:切割中序数组,得到 中序左数组和中序右数组
- // 第五步:切割后序数组,得到 后序左数组和后序右数组
-
- // 第六步
- root->left = traversal(中序左数组, 后序左数组);
- root->right = traversal(中序右数组, 后序右数组);
-
- return root;
+ // 第三步:找切割点
+ int delimiterIndex;
+ for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
+ if (inorder[delimiterIndex] == rootValue) break;
}
+
+ // 第四步:切割中序数组,得到 中序左数组和中序右数组
+ // 第五步:切割后序数组,得到 后序左数组和后序右数组
+
+ // 第六步
+ root->left = traversal(中序左数组, 后序左数组);
+ root->right = traversal(中序右数组, 后序右数组);
+
+ return root;
+}
```
**难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。**
@@ -148,9 +153,9 @@ root->right = traversal(rightInorder, rightPostorder);
完整代码如下:
-### C++完整代码
+### C++完整代码
-```
+```C++
class Solution {
private:
TreeNode* traversal (vector& inorder, vector& postorder) {
@@ -204,7 +209,7 @@ public:
加了日志的代码如下:(加了日志的代码不要在leetcode上提交,容易超时)
-```
+```C++
class Solution {
private:
TreeNode* traversal (vector& inorder, vector& postorder) {
@@ -272,7 +277,7 @@ public:
下面给出用下表索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下表索引来分割)
### C++优化版本
-```
+```C++
class Solution {
private:
// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
@@ -320,7 +325,7 @@ public:
那么这个版本写出来依然要打日志进行调试,打日志的版本如下:(**该版本不要在leetcode上提交,容易超时**)
-```
+```C++
class Solution {
private:
TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& postorder, int postorderBegin, int postorderEnd) {
@@ -389,7 +394,9 @@ public:
};
```
-# 105.从前序与中序遍历序列构造二叉树
+## 105.从前序与中序遍历序列构造二叉树
+
+题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
根据一棵树的前序遍历与中序遍历构造二叉树。
@@ -402,9 +409,9 @@ public:
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
-
+
-## 思路
+### 思路
本题和106是一样的道理。
@@ -412,7 +419,7 @@ public:
带日志的版本C++代码如下: (**带日志的版本仅用于调试,不要在leetcode上提交,会超时**)
-```
+```C++
class Solution {
private:
TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& preorder, int preorderBegin, int preorderEnd) {
@@ -434,14 +441,14 @@ private:
// 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
-
+
// 切割前序数组
// 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin; // 终止位置是起始位置加上中序左区间的大小size
// 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
int rightPreorderBegin = preorderBegin + 1 + (delimiterIndex - inorderBegin);
- int rightPreorderEnd = preorderEnd;
+ int rightPreorderEnd = preorderEnd;
cout << "----------" << endl;
cout << "leftInorder :";
@@ -486,7 +493,7 @@ public:
105.从前序与中序遍历序列构造二叉树,最后版本,C++代码:
-```
+```C++
class Solution {
private:
TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& preorder, int preorderBegin, int preorderEnd) {
@@ -508,14 +515,14 @@ private:
// 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
-
+
// 切割前序数组
// 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin; // 终止位置是起始位置加上中序左区间的大小size
// 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
int rightPreorderBegin = preorderBegin + 1 + (delimiterIndex - inorderBegin);
- int rightPreorderEnd = preorderEnd;
+ int rightPreorderEnd = preorderEnd;
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, preorder, leftPreorderBegin, leftPreorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd);
@@ -533,7 +540,7 @@ public:
};
```
-# 思考题
+## 思考题
前序和中序可以唯一确定一颗二叉树。
@@ -545,17 +552,17 @@ public:
举一个例子:
-
+
tree1 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
-那么tree1 和 tree2 的前序和后序完全相同,这是一棵树么,很明显是两棵树!
+那么tree1 和 tree2 的前序和后序完全相同,这是一棵树么,很明显是两棵树!
所以前序和后序不能唯一确定一颗二叉树!
-# 总结
+## 总结
之前我们讲的二叉树题目都是各种遍历二叉树,这次开始构造二叉树了,思路其实比较简单,但是真正代码实现出来并不容易。
@@ -569,8 +576,24 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
认真研究完本篇,相信大家对二叉树的构造会清晰很多。
-如果学到了,就赶紧转发给身边需要的同学吧!
-
-加个油!
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0107.二叉树的层次遍历II.md b/problems/0107.二叉树的层次遍历II.md
deleted file mode 100644
index 91ca6a94..00000000
--- a/problems/0107.二叉树的层次遍历II.md
+++ /dev/null
@@ -1,46 +0,0 @@
-## 题目地址
-https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
-
-## 思路
-
-这道题目相对于[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md),就把结果倒叙过来,就可以了。
-
-层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
-
-需要借用一个辅助数据结构队列来实现,**队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。**
-
-使用队列实现广度优先遍历,动画如下:
-
-
-
-这样就实现了层序从左到右遍历二叉树。
-
-代码如下:这份代码也可以作为二叉树层序遍历的模板。
-
-## C++代码
-
-```
-class Solution {
-public:
- vector> levelOrderBottom(TreeNode* root) {
- queue que;
- if (root != NULL) que.push(root);
- vector> result;
- while (!que.empty()) {
- int size = que.size();
- vector vec;
- for (int i = 0; i < size; i++) {// 这里一定要使用固定大小size,不要使用que.size()
- TreeNode* node = que.front();
- que.pop();
- vec.push_back(node->val);
- if (node->left) que.push(node->left);
- if (node->right) que.push(node->right);
- }
- result.push_back(vec);
- }
- reverse(result.begin(), result.end());
- return result;
-
- }
-};
-```
diff --git a/problems/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md
index 3afc8d6a..12d47e6a 100644
--- a/problems/0108.将有序数组转换为二叉搜索树.md
+++ b/problems/0108.将有序数组转换为二叉搜索树.md
@@ -1,6 +1,17 @@
-> 构造二叉搜索树,一不小心就平衡了
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 108.将有序数组转换为二叉搜索树
+
+> 构造二叉搜索树,一不小心就平衡了
+
+## 108.将有序数组转换为二叉搜索树
+
+题目链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
@@ -10,13 +21,13 @@

-# 思路
+## 思路
做这道题目之前大家可以了解一下这几道:
* [106.从中序与后序遍历序列构造二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
* [654.最大二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中其实已经讲过了,如果根据数组构造一颗二叉树。
-* [701.二叉搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)
+* [701.二叉搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)
* [450.删除二叉搜索树中的节点](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)
@@ -39,21 +50,21 @@
取哪一个都可以,只不过构成了不同的平衡二叉搜索树。
-例如:输入:[-10,-3,0,5,9]
+例如:输入:[-10,-3,0,5,9]
如下两棵树,都是这个数组的平衡二叉搜索树:
-
+
-如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2。
+如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2。
**这也是题目中强调答案不是唯一的原因。 理解这一点,这道题目算是理解到位了**。
-## 递归
+## 递归
递归三部曲:
-* 确定递归函数返回值及其参数
+* 确定递归函数返回值及其参数
删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。
@@ -67,7 +78,7 @@
```
// 左闭右闭区间[left, right]
-TreeNode* traversal(vector& nums, int left, int right)
+TreeNode* traversal(vector& nums, int left, int right)
```
这里注意,**我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量**。
@@ -75,7 +86,7 @@ TreeNode* traversal(vector& nums, int left, int right)
在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg),[35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) 和[59.螺旋矩阵II](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)都详细讲过循环不变量。
-* 确定递归终止条件
+* 确定递归终止条件
这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。
@@ -85,22 +96,22 @@ TreeNode* traversal(vector& nums, int left, int right)
if (left > right) return nullptr;
```
-* 确定单层递归的逻辑
+* 确定单层递归的逻辑
-首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`,**这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在[二分法](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中尤其需要注意!**
+首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`,**这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在[二分法](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中尤其需要注意!**
-所以可以这么写:`int mid = left + ((right - left) / 2);`
+所以可以这么写:`int mid = left + ((right - left) / 2);`
-但本题leetcode的测试数据并不会越界,所以怎么写都可以。但需要有这个意识!
+但本题leetcode的测试数据并不会越界,所以怎么写都可以。但需要有这个意识!
-取了中间位置,就开始以中间位置的元素构造节点,代码:`TreeNode* root = new TreeNode(nums[mid]);`。
+取了中间位置,就开始以中间位置的元素构造节点,代码:`TreeNode* root = new TreeNode(nums[mid]);`。
接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点。
最后返回root节点,单层递归整体代码如下:
```
-int mid = left + ((right - left) / 2);
+int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
@@ -111,12 +122,12 @@ return root;
* 递归整体代码如下:
-```
+```C++
class Solution {
private:
TreeNode* traversal(vector& nums, int left, int right) {
if (left > right) return nullptr;
- int mid = left + ((right - left) / 2);
+ int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
@@ -139,7 +150,7 @@ public:
模拟的就是不断分割的过程,C++代码如下:(我已经详细注释)
-```
+```C++
class Solution {
public:
TreeNode* sortedArrayToBST(vector& nums) {
@@ -158,7 +169,7 @@ public:
nodeQue.pop();
int left = leftQue.front(); leftQue.pop();
int right = rightQue.front(); rightQue.pop();
- int mid = left + ((right - left) / 2);
+ int mid = left + ((right - left) / 2);
curNode->val = nums[mid]; // 将mid对应的元素给中间节点
@@ -181,7 +192,7 @@ public:
};
```
-# 总结
+## 总结
**在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 和 [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)之后,我们顺理成章的应该构造一下二叉搜索树了,一不小心还是一棵平衡二叉搜索树**。
@@ -189,9 +200,27 @@ public:
此时相信大家应该对通过递归函数的返回值来增删二叉树很熟悉了,这也是常规操作。
-在定义区间的过程中我们又一次强调了循环不变量的重要性。
+在定义区间的过程中我们又一次强调了循环不变量的重要性。
最后依然给出迭代的方法,其实就是模拟取中间元素,然后不断分割去构造二叉树的过程。
-**就酱,如果对你有帮助的话,也转发给身边需要的同学吧!**
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md
index 09d5598a..5d55910c 100644
--- a/problems/0110.平衡二叉树.md
+++ b/problems/0110.平衡二叉树.md
@@ -1,9 +1,17 @@
-## 题目地址
-https://leetcode-cn.com/problems/balanced-binary-tree/
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
> 求高度还是求深度,你搞懂了不?
-# 110.平衡二叉树
+## 110.平衡二叉树
+
+题目地址:https://leetcode-cn.com/problems/balanced-binary-tree/
给定一个二叉树,判断它是否是高度平衡的二叉树。
@@ -13,7 +21,7 @@ https://leetcode-cn.com/problems/balanced-binary-tree/
给定二叉树 [3,9,20,null,null,15,7]
-
+
返回 true 。
@@ -21,11 +29,11 @@ https://leetcode-cn.com/problems/balanced-binary-tree/
给定二叉树 [1,2,2,3,3,null,null,4,4]
-
+
返回 false 。
-# 题外话
+## 题外话
咋眼一看这道题目和[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。
@@ -36,7 +44,7 @@ https://leetcode-cn.com/problems/balanced-binary-tree/
但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:
-
+
关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。
@@ -48,7 +56,7 @@ https://leetcode-cn.com/problems/balanced-binary-tree/
在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历)
-```
+```C++
class Solution {
public:
int result;
@@ -82,7 +90,7 @@ public:
注意以上代码是为了把细节体现出来,简化一下代码如下:
-```
+```C++
class Solution {
public:
int result;
@@ -106,15 +114,15 @@ public:
};
```
-# 本题思路
+## 本题思路
-## 递归
+### 递归
此时大家应该明白了既然要求比较高度,必然是要后序遍历。
递归三步曲分析:
-1. 明确递归函数的参数和返回值
+1. 明确递归函数的参数和返回值
参数的话为传入的节点指针,就没有其他参数需要传递了,返回值要返回传入节点为根节点树的深度。
@@ -132,9 +140,9 @@ public:
int getDepth(TreeNode* node)
```
-2. 明确终止条件
+2. 明确终止条件
-递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的书高度为0
+递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的书高度为0
代码如下:
@@ -144,7 +152,7 @@ if (node == NULL) {
}
```
-3. 明确单层递归的逻辑
+3. 明确单层递归的逻辑
如何判断当前传入节点为根节点的二叉树是否是平衡二叉树呢,当然是左子树高度和右子树高度相差。
@@ -154,7 +162,7 @@ if (node == NULL) {
```
int leftDepth = depth(node->left); // 左
-if (leftDepth == -1) return -1;
+if (leftDepth == -1) return -1;
int rightDepth = depth(node->right); // 右
if (rightDepth == -1) return -1;
@@ -172,7 +180,7 @@ return result;
```
int leftDepth = getDepth(node->left);
-if (leftDepth == -1) return -1;
+if (leftDepth == -1) return -1;
int rightDepth = getDepth(node->right);
if (rightDepth == -1) return -1;
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
@@ -182,7 +190,7 @@ return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
getDepth整体代码如下:
-```
+```C++
int getDepth(TreeNode* node) {
if (node == NULL) {
return 0;
@@ -197,7 +205,7 @@ int getDepth(TreeNode* node) {
最后本题整体递归代码如下:
-```
+```C++
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1
@@ -212,12 +220,12 @@ public:
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
}
bool isBalanced(TreeNode* root) {
- return getDepth(root) == -1 ? false : true;
+ return getDepth(root) == -1 ? false : true;
}
};
```
-## 迭代
+### 迭代
在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。
@@ -227,7 +235,7 @@ public:
代码如下:
-```
+```C++
// cur节点的最大深度,就是cur的高度
int getDepth(TreeNode* cur) {
stack st;
@@ -334,7 +342,7 @@ public:
因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
-# 总结
+## 总结
通过本题可以了解求二叉树深度 和 二叉树高度的差异,求深度适合用前序遍历,而求高度适合用后序遍历。
@@ -342,4 +350,23 @@ public:
但是递归方式是一定要掌握的!
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md
index eebfd76e..430cd5d6 100644
--- a/problems/0111.二叉树的最小深度.md
+++ b/problems/0111.二叉树的最小深度.md
@@ -1,10 +1,17 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-## 题目地址
-https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
> 和求最大深度一个套路?
-# 111.二叉树的最小深度
+## 111.二叉树的最小深度
+
+题目地址:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
给定一个二叉树,找出其最小深度。
@@ -16,11 +23,11 @@ https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
给定二叉树 [3,9,20,null,null,15,7],
-
+
-返回它的最小深度 2.
+返回它的最小深度 2.
-# 思路
+## 思路
看完了这篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。
@@ -28,13 +35,13 @@ https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
遍历顺序上依然是后序遍历(因为要比较递归返回之后的结果),但在处理中间节点的逻辑上,最大深度很容易理解,最小深度可有一个误区,如图:
-
+
这就重新审题了,题目中说的是:**最小深度是从根节点到最近叶子节点的最短路径上的节点数量。**,注意是**叶子节点**。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
-## 递归法
+## 递归法
来来来,一起递归三部曲:
@@ -48,9 +55,9 @@ https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
int getDepth(TreeNode* node)
```
-2. 确定终止条件
+2. 确定终止条件
-终止条件也是遇到空节点返回0,表示当前节点的高度为0。
+终止条件也是遇到空节点返回0,表示当前节点的高度为0。
代码如下:
@@ -58,7 +65,7 @@ int getDepth(TreeNode* node)
if (node == NULL) return 0;
```
-3. 确定单层递归的逻辑
+3. 确定单层递归的逻辑
这块和求最大深度可就不一样了,一些同学可能会写如下代码:
```
@@ -70,7 +77,7 @@ return result;
这个代码就犯了此图中的误区:
-
+
如果这么求的话,没有左孩子的分支会算为最短深度。
@@ -80,7 +87,7 @@ return result;
代码如下:
-```
+```C++
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
@@ -92,14 +99,14 @@ if (node->left == NULL && node->right != NULL) {
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
-int result = 1 + min(leftDepth, rightDepth);
+int result = 1 + min(leftDepth, rightDepth);
return result;
```
-遍历的顺序为后序(左右中),可以看出:**求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。**
+遍历的顺序为后序(左右中),可以看出:**求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。**
整体递归代码如下:
-```
+```C++
class Solution {
public:
int getDepth(TreeNode* node) {
@@ -115,7 +122,7 @@ public:
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
- int result = 1 + min(leftDepth, rightDepth);
+ int result = 1 + min(leftDepth, rightDepth);
return result;
}
@@ -127,7 +134,7 @@ public:
精简之后代码如下:
-```
+```C++
class Solution {
public:
int minDepth(TreeNode* root) {
@@ -155,7 +162,7 @@ public:
代码如下:(详细注释)
-```
+```C++
class Solution {
public:
@@ -182,4 +189,23 @@ public:
};
```
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md
index 29fc1ca2..40df1e7a 100644
--- a/problems/0112.路径总和.md
+++ b/problems/0112.路径总和.md
@@ -1,4 +1,11 @@
-## 题目地址
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
> 递归函数什么时候需要返回值
@@ -9,7 +16,9 @@
* 112. 路径总和
* 113. 路径总和II
-# 112. 路径总和
+## 112. 路径总和
+
+题目地址:https://leetcode-cn.com/problems/path-sum/
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
@@ -18,16 +27,15 @@
示例:
给定如下二叉树,以及目标和 sum = 22,
-
+
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
-# 思路
-
+### 思路
这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和。
-## 递归
+### 递归
可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树
@@ -47,7 +55,7 @@
如图所示:
-
+
图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
@@ -58,7 +66,7 @@ bool traversal(TreeNode* cur, int count) // 注意函数的返回类型
```
-2. 确定终止条件
+2. 确定终止条件
首先计数器如何统计这一条路径的和呢?
@@ -75,15 +83,15 @@ if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
```
-3. 确定单层递归的逻辑
+3. 确定单层递归的逻辑
-因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
+因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
代码如下:
-```
+```C++
if (cur->left) { // 左 (空节点不遍历)
// 遇到叶子节点返回true,则直接返回true
if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑
@@ -101,15 +109,15 @@ return false;
为了把回溯的过程体现出来,可以改为如下代码:
-```
+```C++
if (cur->left) { // 左
count -= cur->left->val; // 递归,处理节点;
if (traversal(cur->left, count)) return true;
count += cur->left->val; // 回溯,撤销处理结果
}
-if (cur->right) { // 右
+if (cur->right) { // 右
count -= cur->right->val;
- if (traversal(cur->right, count - cur->right->val)) return true;
+ if (traversal(cur->right, count - cur->right->val)) return true;
count += cur->right->val;
}
return false;
@@ -118,7 +126,7 @@ return false;
整体代码如下:
-```
+```C++
class Solution {
private:
bool traversal(TreeNode* cur, int count) {
@@ -146,9 +154,9 @@ public:
};
```
-以上代码精简之后如下:
+以上代码精简之后如下:
-```
+```C++
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
@@ -164,11 +172,11 @@ public:
**是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,在追求代码精简。** 这一点我已经强调很多次了!
-## 迭代
+### 迭代
-如果使用栈模拟递归的话,那么如果做回溯呢?
+如果使用栈模拟递归的话,那么如果做回溯呢?
-**此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。**
+**此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。**
C++就我们用pair结构来存放这个栈里的元素。
@@ -178,7 +186,7 @@ C++就我们用pair结构来存放这个栈里的元素。
如下代码是使用栈模拟的前序遍历,如下:(详细注释)
-```
+```C++
class Solution {
public:
@@ -210,7 +218,9 @@ public:
如果大家完全理解了本地的递归方法之后,就可以顺便把leetcode上113. 路径总和II做了。
-# 113. 路径总和II
+## 113. 路径总和II
+
+题目地址:https://leetcode-cn.com/problems/path-sum-ii/
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
@@ -220,27 +230,27 @@ public:
给定如下二叉树,以及目标和 sum = 22,
-
+
-## 思路
+### 思路
113.路径总和II要遍历整个树,找到所有路径,**所以递归函数不要返回值!**
如图:
-
+
为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰**)
-```
+```C++
class Solution {
private:
vector> result;
vector path;
// 递归函数不需要返回值,因为我们要遍历整个树
void traversal(TreeNode* cur, int count) {
- if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点切找到了和为sum的路径
+ if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
result.push_back(path);
return;
}
@@ -278,7 +288,7 @@ public:
至于113. 路径总和II 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。
-# 总结
+## 总结
本篇通过leetcode上112. 路径总和 和 113. 路径总和II 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。
@@ -286,6 +296,27 @@ public:
对于112. 路径总和,我依然给出了递归法和迭代法,这种题目其实用迭代法会复杂一些,能掌握递归方式就够了!
-今天是长假最后一天了,内容多一些,也是为了尽快让大家恢复学习状态,哈哈。
-加个油!
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0113.路径总和II.md b/problems/0113.路径总和II.md
deleted file mode 100644
index b12f27f2..00000000
--- a/problems/0113.路径总和II.md
+++ /dev/null
@@ -1,76 +0,0 @@
-
-## 链接
-
-## 思路
-
-这道题目与其说是递归,不如说是回溯问题,题目要找到所有的路径。
-
-这道题目相对于[112. 路径总和](https://leetcode-cn.com/problems/path-sum/) ,是要求出所有的路径和。
-
-
-**相信很多同学都疑惑递归的过程中究竟什么时候需要返回值,什么时候不需要返回值?**
-
-我在[112. 路径总和题解](https://leetcode-cn.com/problems/path-sum/solution/112-lu-jing-zong-he-di-gui-hui-su-die-dai-xiang-ji/)中给出了详细的解释。
-
-**如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。**
-
-而本题要遍历整个树,找到所有路径,**所以本题的递归函数不要返回值!**
-
-如图:
-
-
-
-
-这道题目其实比[112. 路径总和](https://leetcode-cn.com/problems/path-sum/)简单一些,大家做完了本题,可以在做[112. 路径总和](https://leetcode-cn.com/problems/path-sum/)。
-
-为了尽可能的把回溯过程体现出来,我写出如下代码(**这个代码一定不是最简洁的,但是比较清晰的,过于简洁的代码不方便读者理解**)
-
-
-## 回溯C++代码
-
-```
-class Solution {
-private:
- vector> result;
- vector path;
- // 递归函数不需要返回值,因为我们要遍历整个树
- void traversal(TreeNode* cur, int count) {
- if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点切找到了和为sum的路径
- result.push_back(path);
- return;
- }
-
- if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回
-
- if (cur->left) { // 左 (空节点不遍历)
- path.push_back(cur->left->val);
- count -= cur->left->val;
- traversal(cur->left, count); // 递归
- count += cur->left->val; // 回溯
- path.pop_back(); // 回溯
- }
- if (cur->right) { // 右 (空节点不遍历)
- path.push_back(cur->right->val);
- count -= cur->right->val;
- traversal(cur->right, count); // 递归
- count += cur->right->val; // 回溯
- path.pop_back(); // 回溯
- }
- return ;
- }
-
-public:
- vector> pathSum(TreeNode* root, int sum) {
- result.clear();
- path.clear();
- if (root == NULL) return result;
- path.push_back(root->val); // 把根节点放进路径
- traversal(root, sum - root->val);
- return result;
- }
-};
-```
-
-这道题目也可以用迭代法,相对于112.路径总和,每个节点不仅要保存当前路径和,也要保存当前路径,其实比较麻烦,也没有必要,因为回溯法虽然也是递归,但是如果用迭代来实现回溯法的话,是很费劲的,因为回溯的过程需要用栈模拟出来非常麻烦。
-
-这也是为什么我在后面讲解回溯算法的时候,都是使用递归,也没有人会有栈模拟回溯算法(自己找麻烦,哈哈)。
diff --git a/problems/0115.不同的子序列.md b/problems/0115.不同的子序列.md
new file mode 100644
index 00000000..d48f598b
--- /dev/null
+++ b/problems/0115.不同的子序列.md
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 115.不同的子序列
+
+题目链接:https://leetcode-cn.com/problems/distinct-subsequences/
+
+给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
+
+字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
+
+题目数据保证答案符合 32 位带符号整数范围。
+
+
+
+提示:
+
+0 <= s.length, t.length <= 1000
+s 和 t 由英文字母组成
+
+## 思路
+
+这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用KMP。
+
+这道题目相对于72. 编辑距离,简单了不少,因为本题相当于只有删除操作,不用考虑替换增加之类的。
+
+但相对于刚讲过的[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下:
+
+1. 确定dp数组(dp table)以及下标的含义
+
+dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
+
+2. 确定递推公式
+
+这一类问题,基本是要分析两种情况
+
+* s[i - 1] 与 t[j - 1]相等
+* s[i - 1] 与 t[j - 1] 不相等
+
+当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
+
+一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
+
+一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
+
+这里可能有同学不明白了,为什么还要考虑 不用s[i - 1]来匹配,都相同了指定要匹配啊。
+
+例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。
+
+当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。
+
+所以当s[i - 1] 与 t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
+
+当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
+
+所以递推公式为:dp[i][j] = dp[i - 1][j];
+
+3. dp数组如何初始化
+
+从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][0] 和dp[0][j]是一定要初始化的。
+
+每次当初始化的时候,都要回顾一下dp[i][j]的定义,不要凭感觉初始化。
+
+dp[i][0]表示什么呢?
+
+dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
+
+那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
+
+再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
+
+那么dp[0][j]一定都是0,s如论如何也变成不了t。
+
+最后就要看一个特殊位置了,即:dp[0][0] 应该是多少。
+
+dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
+
+初始化分析完毕,代码如下:
+
+```C++
+vector> dp(s.size() + 1, vector(t.size() + 1));
+for (int i = 0; i <= s.size(); i++) dp[i][0] = 1;
+for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 其实这行代码可以和dp数组初始化的时候放在一起,但我为了凸显初始化的逻辑,所以还是加上了。
+
+```
+
+4. 确定遍历顺序
+
+从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j]都是根据左上方和正上方推出来的。
+
+所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。
+
+代码如下:
+
+```C++
+for (int i = 1; i <= s.size(); i++) {
+ for (int j = 1; j <= t.size(); j++) {
+ if (s[i - 1] == t[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
+ } else {
+ dp[i][j] = dp[i - 1][j];
+ }
+ }
+}
+```
+
+5. 举例推导dp数组
+
+以s:"baegg",t:"bag"为例,推导dp数组状态如下:
+
+
+
+如果写出来的代码怎么改都通过不了,不妨把dp数组打印出来,看一看,是不是这样的。
+
+
+动规五部曲分析完毕,代码如下:
+
+```C++
+class Solution {
+public:
+ int numDistinct(string s, string t) {
+ vector> dp(s.size() + 1, vector(t.size() + 1));
+ for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
+ for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
+ for (int i = 1; i <= s.size(); i++) {
+ for (int j = 1; j <= t.size(); j++) {
+ if (s[i - 1] == t[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
+ } else {
+ dp[i][j] = dp[i - 1][j];
+ }
+ }
+ }
+ return dp[s.size()][t.size()];
+ }
+};
+```
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0116.填充每个节点的下一个右侧节点指针.md b/problems/0116.填充每个节点的下一个右侧节点指针.md
deleted file mode 100644
index def34c1d..00000000
--- a/problems/0116.填充每个节点的下一个右侧节点指针.md
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-# 链接
-https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
-
-## 思路
-
-
-注意题目提示内容,:
-* 你只能使用常量级额外空间。
-* 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
-
-基本上就是要求使用递归了,迭代的方式一定会用到栈或者队列。
-
-### 递归
-
-一想用递归怎么做呢,虽然层序遍历是最直观的,但是递归的方式确实不好想。
-
-如图,假如当前操作的节点是cur:
-
-
-
-最关键的点是可以通过上一层递归 搭出来的线,进行本次搭线。
-
-图中cur节点为元素4,那么搭线的逻辑代码:(**注意注释中操作1和操作2和图中的对应关系**)
-
-```
-if (cur->left) cur->left->next = cur->right; // 操作1
-if (cur->right) {
- if (cur->next) cur->right->next = cur->next->left; // 操作2
- else cur->right->next = NULL;
-}
-```
-
-理解到这里,使用前序遍历,那么不难写出如下代码:
-
-如果对二叉树的前中后序不了解看这篇:[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
-
-
-```
-class Solution {
-private:
- void traversal(Node* cur) {
- if (cur == NULL) return;
- // 中
- if (cur->left) cur->left->next = cur->right; // 操作1
- if (cur->right) {
- if (cur->next) cur->right->next = cur->next->left; // 操作2
- else cur->right->next = NULL;
- }
- traversal(cur->left); // 左
- traversal(cur->right); // 右
- }
-public:
- Node* connect(Node* root) {
- traversal(root);
- return root;
- }
-};
-```
-
-### 迭代(层序遍历)
-
-本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)。
-
-层序遍历本来就是一层一层的去遍历,记录一层的头结点(nodePre),然后让nodePre指向当前遍历的节点就可以了。
-
-代码如下:
-
-```
-
-class Solution {
-public:
- Node* connect(Node* root) {
- queue que;
- if (root != NULL) que.push(root);
- while (!que.empty()) {
- int size = que.size();
- vector vec;
- Node* nodePre;
- Node* node;
- for (int i = 0; i < size; i++) { // 开始每一层的遍历
- if (i == 0) {
- nodePre = que.front(); // 记录一层的头结点
- que.pop();
- node = nodePre;
- } else {
- node = que.front();
- que.pop();
- nodePre->next = node; // 本层前一个节点next指向本节点
- nodePre = nodePre->next;
- }
- if (node->left) que.push(node->left);
- if (node->right) que.push(node->right);
- }
- nodePre->next = NULL; // 本层最后一个节点指向NULL
- }
- return root;
- }
-};
-```
diff --git a/problems/0117.填充每个节点的下一个右侧节点指针II.md b/problems/0117.填充每个节点的下一个右侧节点指针II.md
deleted file mode 100644
index 43b56b91..00000000
--- a/problems/0117.填充每个节点的下一个右侧节点指针II.md
+++ /dev/null
@@ -1,41 +0,0 @@
-
-# 链接
-https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/
-
-## 思路
-
-这道题目使用递归还是有难度的,不是完美二叉树,应该怎么办。
-
-## C++代码
-
-```
-class Solution {
-public:
- Node* connect(Node* root) {
- queue que;
- if (root != NULL) que.push(root);
- while (!que.empty()) {
- int size = que.size();
- vector vec;
- Node* nodePre;
- Node* node;
- for (int i = 0; i < size; i++) {
- if (i == 0) {
- nodePre = que.front(); // 取出一层的头结点
- que.pop();
- node = nodePre;
- } else {
- node = que.front();
- que.pop();
- nodePre->next = node; // 本层前一个节点next指向本节点
- nodePre = nodePre->next;
- }
- if (node->left) que.push(node->left);
- if (node->right) que.push(node->right);
- }
- nodePre->next = NULL; // 本层最后一个节点指向NULL
- }
- return root;
- }
-};
-```
diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md
new file mode 100644
index 00000000..3d564892
--- /dev/null
+++ b/problems/0121.买卖股票的最佳时机.md
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 121. 买卖股票的最佳时机
+
+题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
+
+给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
+
+你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
+
+返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
+
+示例 1:
+输入:[7,1,5,3,6,4]
+输出:5
+解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
+
+示例 2:
+输入:prices = [7,6,4,3,1]
+输出:0
+解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
+
+
+## 思路
+
+### 暴力
+
+这道题目最直观的想法,就是暴力,找最优间距了。
+
+```
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int result = 0;
+ for (int i = 0; i < prices.size(); i++) {
+ for (int j = i + 1; j < prices.size(); j++){
+ result = max(result, prices[j] - prices[i]);
+ }
+ }
+ return result;
+ }
+};
+```
+
+* 时间复杂度:O(n^2)
+* 空间复杂度:O(1)
+
+当然该方法超时了。
+
+### 贪心
+
+因为股票就买卖一次,那么贪心的想法很自然就是取最左最小值,取最右最大值,那么得到的差值就是最大利润。
+
+C++代码如下:
+
+```C++
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int low = INT_MAX;
+ int result = 0;
+ for (int i = 0; i < prices.size(); i++) {
+ low = min(low, prices[i]); // 取最左最小价格
+ result = max(result, prices[i] - low); // 直接取最大区间利润
+ }
+ return result;
+ }
+};
+```
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+### 动态规划
+
+动规五部曲分析如下:
+
+1. 确定dp数组(dp table)以及下标的含义
+
+dp[i][0] 表示第i天持有股票所得最多现金 ,**这里可能有同学疑惑,本题中只能买卖一次,持有股票之后哪还有现金呢?**
+
+其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。
+
+dp[i][1] 表示第i天不持有股票所得最多现金
+
+**注意这里说的是“持有”,“持有”不代表就是当天“买入”!也有可能是昨天就买入了,今天保持持有的状态**
+
+很多同学把“持有”和“买入”没分区分清楚。
+
+在下面递推公式分析中,我会进一步讲解。
+
+2. 确定递推公式
+
+如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
+* 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
+* 第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
+
+那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
+
+如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
+* 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
+* 第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
+
+同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
+
+这样递归公式我们就分析完了
+
+3. dp数组如何初始化
+
+由递推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]); 和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);可以看出
+
+其基础都是要从dp[0][0]和dp[0][1]推导出来。
+
+那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];
+
+dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
+
+4. 确定遍历顺序
+
+从递推公式可以看出dp[i]都是有dp[i - 1]推导出来的,那么一定是从前向后遍历。
+
+5. 举例推导dp数组
+
+以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:
+
+
+
+
+dp[5][1]就是最终结果。
+
+为什么不是dp[5][0]呢?
+
+**因为本题中不持有股票状态所得金钱一定比持有股票状态得到的多!**
+
+以上分析完毕,C++代码如下:
+
+```C++
+// 版本一
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int len = prices.size();
+ if (len == 0) return 0;
+ vector> dp(len, vector(2));
+ dp[0][0] -= prices[0];
+ dp[0][1] = 0;
+ for (int i = 1; i < len; i++) {
+ dp[i][0] = max(dp[i - 1][0], -prices[i]);
+ dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
+ }
+ return dp[len - 1][1];
+ }
+};
+```
+* 时间复杂度:O(n)
+* 空间复杂度:O(n)
+
+从递推公式可以看出,dp[i]只是依赖于dp[i - 1]的状态。
+
+```
+dp[i][0] = max(dp[i - 1][0], -prices[i]);
+dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
+```
+
+那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,代码如下:
+
+```C++
+// 版本二
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int len = prices.size();
+ vector> dp(2, vector(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
+ dp[0][0] -= prices[0];
+ dp[0][1] = 0;
+ for (int i = 1; i < len; i++) {
+ dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
+ dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
+ }
+ return dp[(len - 1) % 2][1];
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+这里能写出版本一就可以了,版本二虽然原理都一样,但是想直接写出版本二还是有点麻烦,容易自己给自己找bug。
+
+所以建议是先写出版本一,然后在版本一的基础上优化成版本二,而不是直接就写出版本二。
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md
index 00470497..1a9b4f7f 100644
--- a/problems/0122.买卖股票的最佳时机II.md
+++ b/problems/0122.买卖股票的最佳时机II.md
@@ -1,6 +1,13 @@
-> 贪心有时候比动态规划更巧妙,更好用!
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 122.买卖股票的最佳时机II
+
+## 122.买卖股票的最佳时机II
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
@@ -11,35 +18,35 @@
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
-示例 1:
-输入: [7,1,5,3,6,4]
-输出: 7
-解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
+示例 1:
+输入: [7,1,5,3,6,4]
+输出: 7
+解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
-示例 2:
-输入: [1,2,3,4,5]
-输出: 4
+示例 2:
+输入: [1,2,3,4,5]
+输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
-示例 3:
-输入: [7,6,4,3,1]
-输出: 0
-解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
+示例 3:
+输入: [7,6,4,3,1]
+输出: 0
+解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
-提示:
+提示:
* 1 <= prices.length <= 3 * 10 ^ 4
* 0 <= prices[i] <= 10 ^ 4
-# 思路
+## 思路
本题首先要清楚两点:
* 只有一只股票!
-* 当前只有买股票或者买股票的操作
+* 当前只有买股票或者买股票的操作
想获得利润至少要两天为一个交易单元。
-## 贪心算法
+## 贪心算法
这道题目可能我们只会想,选一个低的买入,在选个高的卖,在选一个低的买入.....循环反复。
@@ -88,7 +95,7 @@ public:
* 时间复杂度O(n)
* 空间复杂度O(1)
-## 动态规划
+## 动态规划
动态规划将在下一个系列详细讲解,本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。
@@ -114,7 +121,7 @@ public:
* 时间复杂度O(n)
* 空间复杂度O(n)
-# 总结
+## 总结
股票问题其实是一个系列的,属于动态规划的范畴,因为目前在讲解贪心系列,所以股票问题会在之后的动态规划系列中详细讲解。
@@ -122,13 +129,24 @@ public:
**本题中理解利润拆分是关键点!** 不要整块的去看,而是把整体利润拆为每天的利润。
-一旦想到这里了,很自然就会想到贪心了,即:只收集每天的正利润,最后稳稳的就是最大利润了。
+一旦想到这里了,很自然就会想到贪心了,即:只收集每天的正利润,最后稳稳的就是最大利润了。
-就酱,「代码随想录」是技术公众号里的一抹清流,值得推荐给你的朋友同学们!
+## 其他语言版本
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+Java:
+
+
+Python:
+
+
+Go:
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0122.买卖股票的最佳时机II(动态规划).md b/problems/0122.买卖股票的最佳时机II(动态规划).md
new file mode 100644
index 00000000..3444ca73
--- /dev/null
+++ b/problems/0122.买卖股票的最佳时机II(动态规划).md
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 122.买卖股票的最佳时机II
+
+题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
+
+给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
+
+设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
+
+注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
+
+
+示例 1:
+输入: [7,1,5,3,6,4]
+输出: 7
+解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
+
+示例 2:
+输入: [1,2,3,4,5]
+输出: 4
+解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
+
+示例 3:
+输入: [7,6,4,3,1]
+输出: 0
+解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
+
+提示:
+* 1 <= prices.length <= 3 * 10 ^ 4
+* 0 <= prices[i] <= 10 ^ 4
+
+## 思路
+
+本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。
+
+
+本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的唯一区别本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)
+
+**在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)一样一样的**。
+
+所以我们重点讲一讲递推公式。
+
+这里重申一下dp数组的含义:
+
+* dp[i][0] 表示第i天持有股票所得现金。
+* dp[i][1] 表示第i天不持有股票所得最多现金
+
+
+如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
+* 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
+* 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
+
+
+**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。
+
+在[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。
+
+而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。
+
+那么第i天持有股票即dp[i][0],如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]。
+
+在来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来
+* 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
+* 第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
+
+**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!**
+
+代码如下:(注意代码中的注释,标记了和121.买卖股票的最佳时机唯一不同的地方)
+
+```C++
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int len = prices.size();
+ vector> dp(len, vector(2, 0));
+ dp[0][0] -= prices[0];
+ dp[0][1] = 0;
+ for (int i = 1; i < len; i++) {
+ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
+ dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
+ }
+ return dp[len - 1][1];
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(n)
+
+大家可以本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的代码几乎一样,唯一的区别在:
+
+```
+dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
+```
+
+**这正是因为本题的股票可以买卖多次!** 所以买入股票的时候,可能会有之前买卖的利润即:dp[i - 1][1],所以dp[i - 1][1] - prices[i]。
+
+想到到这一点,对这两道题理解的比较深刻了。
+
+这里我依然给出滚动数组的版本,C++代码如下:
+
+```C++
+// 版本二
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ int len = prices.size();
+ vector> dp(2, vector(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
+ dp[0][0] -= prices[0];
+ dp[0][1] = 0;
+ for (int i = 1; i < len; i++) {
+ dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
+ dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
+ }
+ return dp[(len - 1) % 2][1];
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md
new file mode 100644
index 00000000..0e718cf1
--- /dev/null
+++ b/problems/0123.买卖股票的最佳时机III.md
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+## 123.买卖股票的最佳时机III
+
+题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/
+
+
+给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
+
+设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
+
+注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
+
+示例 1:
+输入:prices = [3,3,5,0,0,3,1,4]
+输出:6
+解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3。
+
+示例 2:
+输入:prices = [1,2,3,4,5]
+输出:4
+解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
+
+示例 3:
+输入:prices = [7,6,4,3,1]
+输出:0
+解释:在这个情况下, 没有交易完成, 所以最大利润为0。
+
+示例 4:
+输入:prices = [1]
+输出:0
+
+提示:
+
+* 1 <= prices.length <= 10^5
+* 0 <= prices[i] <= 10^5
+
+## 思路
+
+
+这道题目相对 [121.买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) 和 [122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 难了不少。
+
+关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。
+
+接来下我用动态规划五部曲详细分析一下:
+
+1. 确定dp数组以及下标的含义
+
+一天一共就有五个状态,
+0. 没有操作
+1. 第一次买入
+2. 第一次卖出
+3. 第二次买入
+4. 第二次卖出
+
+dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
+
+2. 确定递推公式
+
+需要注意:dp[i][1],**表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区**。
+
+达到dp[i][1]状态,有两个具体操作:
+
+* 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
+* 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
+
+那么dp[i][1]究竟选 dp[i-1][0] - prices[i],还是dp[i - 1][1]呢?
+
+一定是选最大的,所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
+
+同理dp[i][2]也有两个操作:
+
+* 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
+* 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
+
+所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
+
+同理可推出剩下状态部分:
+
+dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
+dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
+
+
+3. dp数组如何初始化
+
+第0天没有操作,这个最容易想到,就是0,即:dp[0][0] = 0;
+
+第0天做第一次买入的操作,dp[0][1] = -prices[0];
+
+第0天做第一次卖出的操作,这个初始值应该是多少呢?
+
+首先卖出的操作一定是收获利润,整个股票买卖最差情况也就是没有盈利即全程无操作现金为0,
+
+从递推公式中可以看出每次是取最大值,那么既然是收获利润如果比0还小了就没有必要收获这个利润了。
+
+所以dp[0][2] = 0;
+
+第0天第二次买入操作,初始值应该是多少呢?
+
+不用管第几次,现在手头上没有现金,只要买入,现金就做相应的减少。
+
+所以第二次买入操作,初始化为:dp[0][3] = -prices[0];
+
+同理第二次卖出初始化dp[0][4] = 0;
+
+4. 确定遍历顺序
+
+从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。
+
+5. 举例推导dp数组
+
+以输入[1,2,3,4,5]为例
+
+
+
+大家可以看到红色框为最后两次卖出的状态。
+
+现在最大的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出。
+
+所以最终最大利润是dp[4][4]
+
+以上五部都分析完了,不难写出如下代码:
+
+```C++
+// 版本一
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ if (prices.size() == 0) return 0;
+ vector> dp(prices.size(), vector(5, 0));
+ dp[0][1] = -prices[0];
+ dp[0][3] = -prices[0];
+ for (int i = 1; i < prices.size(); i++) {
+ dp[i][0] = dp[i - 1][0];
+ dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
+ dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
+ dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
+ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
+ }
+ return dp[prices.size() - 1][4];
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(n * 5)
+
+当然,大家可以看到力扣官方题解里的一种优化空间写法,我这里给出对应的C++版本:
+
+```C++
+// 版本二
+class Solution {
+public:
+ int maxProfit(vector& prices) {
+ if (prices.size() == 0) return 0;
+ vector dp(5, 0);
+ dp[1] = -prices[0];
+ dp[3] = -prices[0];
+ for (int i = 1; i < prices.size(); i++) {
+ dp[1] = max(dp[1], dp[0] - prices[i]);
+ dp[2] = max(dp[2], dp[1] + prices[i]);
+ dp[3] = max(dp[3], dp[2] - prices[i]);
+ dp[4] = max(dp[4], dp[3] + prices[i]);
+ }
+ return dp[4];
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+大家会发现dp[2]利用的是当天的dp[1]。 但结果也是对的。
+
+我来简单解释一下:
+
+dp[1] = max(dp[1], dp[0] - prices[i]); 如果dp[1]取dp[1],即保持买入股票的状态,那么 dp[2] = max(dp[2], dp[1] + prices[i]);中dp[1] + prices[i] 就是今天卖出。
+
+如果dp[1]取dp[0] - prices[i],今天买入股票,那么dp[2] = max(dp[2], dp[1] + prices[i]);中的dp[1] + prices[i]相当于是尽在再卖出股票,一买一卖收益为0,对所得现金没有影响。相当于今天买入股票又卖出股票,等于没有操作,保持昨天卖出股票的状态了。
+
+**这种写法看上去简单,其实思路很绕,不建议大家这么写,这么思考,很容易把自己绕进去!**
+
+对于本题,把版本一的写法研究明白,足以!
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0127.单词接龙.md b/problems/0127.单词接龙.md
deleted file mode 100644
index 575fc4c0..00000000
--- a/problems/0127.单词接龙.md
+++ /dev/null
@@ -1,73 +0,0 @@
-
-## 题目链接
-
-https://leetcode-cn.com/problems/word-ladder/
-
-## 思路
-
-以示例1为例,从这个图中可以看出 hit 到 cog的路线,不止一条,有三条,两条是最短的长度为5,一条长度为6。
-
-
-
-本题只需要求出最短长度就可以了,不用找出路径。
-
-所以这道题要解决两个问题:
-
-* 图中的线是如何连在一起的
-* 起点和终点的最短路径长度
-
-
-首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。
-
-然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
-
-本题如果用深搜,会非常麻烦。
-
-另外需要有一个注意点:
-
-* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
-* 本题给出集合是数组型的,可以转成set结构,查找更快一些
-
-C++代码如下:(详细注释)
-
-```
-class Solution {
-public:
- int ladderLength(string beginWord, string endWord, vector& wordList) {
- // 将vector转成unordered_set,提高查询速度
- unordered_set wordSet(wordList.begin(), wordList.end());
- // 如果endWord没有在wordSet出现,直接返回0
- if (wordSet.find(endWord) == wordSet.end()) return 0;
- // 记录word是否访问过
- unordered_map visitMap; //
- // 初始化队列
- queue que;
- que.push(beginWord);
- // 初始化visitMap
- visitMap.insert(pair(beginWord, 1));
-
- while(!que.empty()) {
- string word = que.front();
- que.pop();
- int path = visitMap[word]; // 这个word的路径长度
- for (int i = 0; i < word.size(); i++) {
- string newWord = word; // 用一个新单词替换word,因为每次置换一个字母
- for (int j = 0 ; j < 26; j++) {
- newWord[i] = j + 'a';
- if (newWord == endWord) return path + 1; // 找到了end,返回path+1
- // wordSet出现了newWord,并且newWord没有被访问过
- if (wordSet.find(newWord) != wordSet.end()
- && visitMap.find(newWord) == visitMap.end()) {
- // 添加访问信息
- visitMap.insert(pair(newWord, path + 1));
- que.push(newWord);
- }
- }
- }
- }
- return 0;
- }
-};
-```
-> 我是[程序员Carl](https://github.com/youngyangyang04),组队刷题可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),期待你的关注!
-
diff --git a/problems/0129.求根到叶子节点数字之和.md b/problems/0129.求根到叶子节点数字之和.md
deleted file mode 100644
index 7d061a43..00000000
--- a/problems/0129.求根到叶子节点数字之和.md
+++ /dev/null
@@ -1,160 +0,0 @@
-## 链接
-https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
-
-## 思路
-
-本题和[113.路径总和II](https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0113.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8CII.md)是类似的思路,做完这道题,可以顺便把[113.路径总和II](https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0113.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8CII.md) 和 [112.路径总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0112.路径总和.md) 做了。
-
-结合112.路径总和 和 113.路径总和II,我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。
-
-接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,我详细讲解了二叉树的递归中,如何使用了回溯。
-
-接下来我们来看题:
-
-首先思路很明确,就是要遍历整个树把更节点到叶子节点组成的数字相加。
-
-那么先按递归三部曲来分析:
-
-### 递归三部曲
-
-如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
-
-* 确定递归函数返回值及其参数
-
-这里我们要遍历整个二叉树,且需要要返回值做逻辑处理,所有返回值为void,在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中,详细讲解了返回值问题。
-
-参数只需要把根节点传入,此时还需要定义两个全局遍历,一个是result,记录最终结果,一个是vector path。
-
-**为什么用vector类型(就是数组)呢? 因为用vector方便我们做回溯!**
-
-所以代码如下:
-
-```
-int result;
-vector path;
-void traversal(TreeNode* cur)
-```
-
-* 确定终止条件
-
-递归什么时候终止呢?
-
-当然是遇到叶子节点,此时要收集结果了,通知返回本层递归,因为单条路径的结果使用vector,我们需要一个函数vectorToInt把vector转成int。
-
-终止条件代码如下:
-
-```
-if (!cur->left && !cur->right) { // 遇到了叶子节点
- result += vectorToInt(path);
- return;
-}
-```
-
-这里vectorToInt函数就是把数组转成int,代码如下:
-
-```
-int vectorToInt(const vector& vec) {
- int sum = 0;
- for (int i = 0; i < vec.size(); i++) {
- sum = sum * 10 + vec[i];
- }
- return sum;
-}
-```
-
-
-* 确定递归单层逻辑
-
-本题其实采用前中后序都不无所谓, 因为也没有中间几点的处理逻辑。
-
-这里主要是当左节点不为空,path收集路径,并递归左孩子,右节点同理。
-
-**但别忘了回溯**。
-
-如图:
-
-
-
-
-代码如下:
-
-```
- // 中
-if (cur->left) { // 左 (空节点不遍历)
- path.push_back(cur->left->val);
- traversal(cur->left); // 递归
- path.pop_back(); // 回溯
-}
-if (cur->right) { // 右 (空节点不遍历)
- path.push_back(cur->right->val);
- traversal(cur->right); // 递归
- path.pop_back(); // 回溯
-}
-```
-
-这里要注意回溯和递归要永远在一起,一个递归,对应一个回溯,是一对一的关系,有的同学写成如下代码:
-
-```
-if (cur->left) { // 左 (空节点不遍历)
- path.push_back(cur->left->val);
- traversal(cur->left); // 递归
-}
-if (cur->right) { // 右 (空节点不遍历)
- path.push_back(cur->right->val);
- traversal(cur->right); // 递归
-}
-path.pop_back(); // 回溯
-```
-**把回溯放在花括号外面了,世界上最遥远的距离,是你在花括号里,而我在花括号外!** 这就不对了。
-
-### 整体C++代码
-
-关键逻辑分析完了,整体C++代码如下:
-
-```
-class Solution {
-private:
- int result;
- vector path;
- // 把vector转化为int
- int vectorToInt(const vector& vec) {
- int sum = 0;
- for (int i = 0; i < vec.size(); i++) {
- sum = sum * 10 + vec[i];
- }
- return sum;
- }
- void traversal(TreeNode* cur) {
- if (!cur->left && !cur->right) { // 遇到了叶子节点
- result += vectorToInt(path);
- return;
- }
-
- if (cur->left) { // 左 (空节点不遍历)
- path.push_back(cur->left->val); // 处理节点
- traversal(cur->left); // 递归
- path.pop_back(); // 回溯,撤销
- }
- if (cur->right) { // 右 (空节点不遍历)
- path.push_back(cur->right->val); // 处理节点
- traversal(cur->right); // 递归
- path.pop_back(); // 回溯,撤销
- }
- return ;
- }
-public:
- int sumNumbers(TreeNode* root) {
- path.clear();
- if (root == nullptr) return 0;
- path.push_back(root->val);
- traversal(root);
- return result;
- }
-};
-```
-
-# 总结
-
-过于简洁的代码,很容易让初学者忽视了本题中回溯的精髓,甚至作者本身都没有想清楚自己用了回溯。
-
-**我这里提供的代码把整个回溯过程充分体现出来,希望可以帮助大家看的明明白白!**
diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md
index ee2e6079..01ff35c5 100644
--- a/problems/0131.分割回文串.md
+++ b/problems/0131.分割回文串.md
@@ -1,7 +1,15 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
> 切割问题其实是一种组合问题!
-# 131.分割回文串
+## 131.分割回文串
题目链接:https://leetcode-cn.com/problems/palindrome-partitioning/
@@ -9,16 +17,18 @@
返回 s 所有可能的分割方案。
-示例:
-输入: "aab"
-输出:
-[
- ["aa","b"],
- ["a","a","b"]
-]
+示例:
+输入: "aab"
+输出:
+[
+ ["aa","b"],
+ ["a","a","b"]
+]
-# 思路
+## 思路
+
+关于本题,大家也可以看我在B站的视频讲解:[131.分割回文串(B站视频)](https://www.bilibili.com/video/BV1c54y1e7k6)
本题这涉及到两个关键问题:
@@ -29,9 +39,9 @@
这种题目,想用for循环暴力解法,可能都不那么容易写出来,所以要换一种暴力的方式,就是回溯。
-一些同学可能想不清楚 回溯究竟是如果切割字符串呢?
+一些同学可能想不清楚 回溯究竟是如何切割字符串呢?
-我们来分析一下切割,**其实切割问题类似组合问题**。
+我们来分析一下切割,**其实切割问题类似组合问题**。
例如对于字符串abcdef:
@@ -42,15 +52,15 @@
所以切割问题,也可以抽象为一颗树形结构,如图:
-
+
递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。
此时可以发现,切割问题的回溯搜索的过程和组合问题的回溯搜索的过程是差不多的。
-## 回溯三部曲
+## 回溯三部曲
-* 递归函数参数
+* 递归函数参数
全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)
@@ -60,7 +70,7 @@
代码如下:
-```
+```C++
vector> result;
vector path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
@@ -68,7 +78,7 @@ void backtracking (const string& s, int startIndex) {
* 递归函数终止条件
-
+
从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止终止条件。
@@ -78,7 +88,7 @@ void backtracking (const string& s, int startIndex) {
所以终止条件代码如下:
-```
+```C++
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
@@ -90,7 +100,7 @@ void backtracking (const string& s, int startIndex) {
* 单层搜索的逻辑
-**来看看在递归循环,中如何截取子串呢?**
+**来看看在递归循环,中如何截取子串呢?**
在`for (int i = startIndex; i < s.size(); i++)`循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。
@@ -98,7 +108,7 @@ void backtracking (const string& s, int startIndex) {
代码如下:
-```
+```C++
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
@@ -137,11 +147,11 @@ for (int i = startIndex; i < s.size(); i++) {
此时关键代码已经讲解完毕,整体代码如下(详细注释了)
-# C++整体代码
+## C++整体代码
根据Carl给出的回溯算法模板:
-```
+```C++
void backtracking(参数) {
if (终止条件) {
存放结果;
@@ -200,7 +210,7 @@ public:
};
```
-# 总结
+## 总结
这道题目在leetcode上是中等,但可以说是hard的题目了,但是代码其实就是按照模板的样子来的。
@@ -212,9 +222,9 @@ public:
* 如何模拟那些切割线
* 切割问题中递归如何终止
* 在递归循环中如何截取子串
-* 如何判断回文
+* 如何判断回文
-**我们平时在做难题的时候,总结出来难究竟难在哪里也是一种需要锻炼的能力**。
+**我们平时在做难题的时候,总结出来难究竟难在哪里也是一种需要锻炼的能力**。
一些同学可能遇到题目比较难,但是不知道题目难在哪里,反正就是很难。其实这样还是思维不够清晰,这种总结的能力需要多接触多锻炼。
@@ -224,17 +234,34 @@ public:
**但接下来如何模拟切割线,如何终止,如何截取子串,其实都不好想,最后判断回文算是最简单的了**。
+关于模拟切割线,其实就是index是上一层已经确定了的分割线,i是这一层试图寻找的新分割线
+
除了这些难点,**本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1**。
所以本题应该是一个道hard题目了。
**可能刷过这道题目的录友都没感受到自己原来克服了这么多难点,就把这道题目AC了**,这应该叫做无招胜有招,人码合一,哈哈哈。
-当然,本题131.分割回文串还可以用暴力搜索一波,132.分割回文串II和1278.分割回文串III 爆搜就会超时,需要使用动态规划了,我们会在动态规划系列中,详细讲解!
-**就酱,如果感觉「代码随想录」不错,就把Carl宣传一波吧!**
-> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-**如果感觉对你有帮助,不要吝啬给一个👍吧!**
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0134.加油站.md b/problems/0134.加油站.md
index 491dc61a..9c72e84d 100644
--- a/problems/0134.加油站.md
+++ b/problems/0134.加油站.md
@@ -1,6 +1,13 @@
-今天开始继续贪心题目系列,让大家久等啦!
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 134. 加油站
+
+## 134. 加油站
题目链接:https://leetcode-cn.com/problems/gas-station/
@@ -16,38 +23,37 @@
* 输入数组均为非空数组,且长度相同。
* 输入数组中的元素均为非负数。
-示例 1:
-输入:
-gas = [1,2,3,4,5]
-cost = [3,4,5,1,2]
+示例 1:
+输入:
+gas = [1,2,3,4,5]
+cost = [3,4,5,1,2]
-输出: 3
-解释:
-从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
-开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
-开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
-开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
-开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
-开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
-因此,3 可为起始索引。
+输出: 3
+解释:
+从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
+开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
+开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
+开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
+开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
+开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
+因此,3 可为起始索引。
-示例 2:
-输入:
-gas = [2,3,4]
-cost = [3,4,3]
+示例 2:
+输入:
+gas = [2,3,4]
+cost = [3,4,3]
-输出: -1
-解释:
-你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
-我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
-开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
-开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
-你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
-因此,无论怎样,你都不可能绕环路行驶一周。
+输出: -1
+解释:
+你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
+我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
+开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
+开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
+你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
+因此,无论怎样,你都不可能绕环路行驶一周。
-# 思路
-## 暴力方法
+## 暴力方法
暴力的方法很明显就是O(n^2)的,遍历每一个加油站为起点的情况,模拟一圈。
@@ -59,7 +65,7 @@ cost = [3,4,3]
C++代码如下:
-```
+```C++
class Solution {
public:
int canCompleteCircuit(vector& gas, vector& cost) {
@@ -76,20 +82,20 @@ public:
return -1;
}
};
-```
+```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
C++暴力解法在leetcode上提交也可以过。
-## 贪心算法(方法一)
+## 贪心算法(方法一)
直接从全局进行贪心选择,情况如下:
-* 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
+* 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
* 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
-* 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。
+* 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。
C++代码如下:
@@ -111,7 +117,7 @@ public:
// 情况3
for (int i = gas.size() - 1; i >= 0; i--) {
int rest = gas[i] - cost[i];
- min += rest;
+ min += rest;
if (min >= 0) {
return i;
}
@@ -131,7 +137,7 @@ public:
但不管怎么说,解法毕竟还是巧妙的,不用过于执着于其名字称呼。
-## 贪心算法(方法二)
+## 贪心算法(方法二)
可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
@@ -142,7 +148,7 @@ i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i
如图:

-那么为什么一旦[i,j] 区间和为负数,起始位置就可以是j+1呢,j+1后面就不会出现更大的负数?
+那么为什么一旦[i,j] 区间和为负数,起始位置就可以是j+1呢,j+1后面就不会出现更大的负数?
如果出现更大的负数,就是更新j,那么起始位置又变成新的j+1了。
@@ -179,7 +185,7 @@ public:
**说这种解法为贪心算法,才是是有理有据的,因为全局最优解是根据局部最优推导出来的**。
-# 总结
+## 总结
对于本题首先给出了暴力解法,暴力解法模拟跑一圈的过程其实比较考验代码技巧的,要对while使用的很熟练。
@@ -187,12 +193,24 @@ public:
对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。
-就酱,「代码随想录」值得推荐给身边每一位学习算法的同学朋友,很多录友关注后都感觉相见恨晚!
-> **我是[程序员Carl](https://github.com/youngyangyang04),[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png)可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
+## 其他语言版本
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+
+Java:
+
+
+Python:
+
+
+Go:
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0135.分发糖果.md b/problems/0135.分发糖果.md
index 80fbf596..0595cff6 100644
--- a/problems/0135.分发糖果.md
+++ b/problems/0135.分发糖果.md
@@ -1,32 +1,38 @@
-> 好了
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
-# 135. 分发糖果
+## 135. 分发糖果
-链接:https://leetcode-cn.com/problems/candy/
+链接:https://leetcode-cn.com/problems/candy/
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
-* 每个孩子至少分配到 1 个糖果。
+* 每个孩子至少分配到 1 个糖果。
* 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
-示例 1:
-输入: [1,0,2]
-输出: 5
-解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
+示例 1:
+输入: [1,0,2]
+输出: 5
+解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
-示例 2:
-输入: [1,2,2]
-输出: 4
-解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。
-第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
+示例 2:
+输入: [1,2,2]
+输出: 4
+解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。
+第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
-# 思路
+## 思路
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,**如果两边一起考虑一定会顾此失彼**。
@@ -37,12 +43,12 @@
局部最优可以推出全局最优。
-如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1
+如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1
代码如下:
```C++
-// 从前向后
+// 从前向后
for (int i = 1; i < ratings.size(); i++) {
if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}
@@ -52,13 +58,13 @@ for (int i = 1; i < ratings.size(); i++) {

-再确定左孩子大于右孩子的情况(从后向前遍历)
+再确定左孩子大于右孩子的情况(从后向前遍历)
-遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
+遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
-因为如果从前向后遍历,根据 ratings[i + 1] 来确定 ratings[i] 对应的糖果,那么每次都不能利用上前一次的比较结果了。
+因为如果从前向后遍历,根据 ratings[i + 1] 来确定 ratings[i] 对应的糖果,那么每次都不能利用上前一次的比较结果了。
-**所以确定左孩子大于右孩子的情况一定要从后向前遍历!**
+**所以确定左孩子大于右孩子的情况一定要从后向前遍历!**
如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
@@ -107,7 +113,7 @@ public:
};
```
-# 总结
+## 总结
这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
@@ -118,11 +124,24 @@ public:
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
-就酱,如果感觉「代码随想录」干货满满,就推荐给身边的朋友同学们吧,关注后就会发现相见恨晚!
-> **我是[程序员Carl](https://github.com/youngyangyang04),[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png)可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
-
-**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
+## 其他语言版本
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md
index aed68179..ec996565 100644
--- a/problems/0139.单词拆分.md
+++ b/problems/0139.单词拆分.md
@@ -1,12 +1,50 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+# 动态规划:单词拆分
-// 如何往 完全背包上靠?
-// 用多次倒是可以往 完全背包上靠一靠
-// 和单词分割的问题有点像
+## 139.单词拆分
-[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)
+题目链接:https://leetcode-cn.com/problems/word-break/
-回溯法代码:
-```
+给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
+
+说明:
+
+拆分时可以重复使用字典中的单词。
+
+你可以假设字典中没有重复的单词。
+
+示例 1:
+输入: s = "leetcode", wordDict = ["leet", "code"]
+输出: true
+解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
+
+示例 2:
+输入: s = "applepenapple", wordDict = ["apple", "pen"]
+输出: true
+解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
+ 注意你可以重复使用字典中的单词。
+
+示例 3:
+输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
+输出: false
+
+## 思路
+
+看到这道题目的时候,大家应该回想起我们之前讲解回溯法专题的时候,讲过的一道题目[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q),就是枚举字符串的所有分割情况。
+
+[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q):是枚举分割后的所有子串,判断是否回文。
+
+本道是枚举分割所有字符串,判断是否在字典里出现过。
+
+那么这里我也给出回溯法C++代码:
+
+```C++
class Solution {
private:
bool backtracking (const string& s, const unordered_set& wordSet, int startIndex) {
@@ -29,15 +67,25 @@ public:
};
```
+* 时间复杂度:O(2^n),因为每一个单词都有两个状态,切割和不切割
+* 空间复杂度:O(n),算法递归系统调用栈的空间
+
+那么以上代码很明显要超时了,超时的数据如下:
+
```
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
["a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"]
```
-可以使用一个一维数组保存一下,递归过程中计算的结果,C++代码如下:
+递归的过程中有很多重复计算,可以使用数组保存一下递归过程中计算的结果。
-使用memory数组保存 每次计算的以startIndex起始的计算结果,如果memory[startIndex]里已经被赋值了,直接用memory[startIndex]的结果。
-```
+这个叫做记忆化递归,这种方法我们之前已经提过很多次了。
+
+使用memory数组保存每次计算的以startIndex起始的计算结果,如果memory[startIndex]里已经被赋值了,直接用memory[startIndex]的结果。
+
+C++代码如下:
+
+```C++
class Solution {
private:
bool backtracking (const string& s,
@@ -68,29 +116,134 @@ public:
};
```
+这个时间复杂度其实也是:O(2^n)。只不过对于上面那个超时测试用例优化效果特别明显。
-得好好分析一下,完全背包和01背包,这个对于刷leetcode太重要了
+**这个代码就可以AC了,当然回溯算法不是本题的主菜,背包才是!**
-注意这里要空出一个 dp[0] 来做起始位置
-```
+## 背包问题
+
+单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。
+
+拆分时可以重复使用字典中的单词,说明就是一个完全背包!
+
+动规五部曲分析如下:
+
+1. 确定dp数组以及下标的含义
+
+**dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词**。
+
+2. 确定递推公式
+
+如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。
+
+所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
+
+3. dp数组如何初始化
+
+从递归公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递归的根基,dp[0]一定要为true,否则递归下去后面都都是false了。
+
+那么dp[0]有没有意义呢?
+
+dp[0]表示如果字符串为空的话,说明出现在字典里。
+
+但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。
+
+下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。
+
+4. 确定遍历顺序
+
+题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。
+
+还要讨论两层for循环的前后循序。
+
+**如果求组合数就是外层for循环遍历物品,内层for遍历背包**。
+
+**如果求排列数就是外层for遍历背包,内层for循环遍历物品**。
+
+对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://mp.weixin.qq.com/s/znj-9j8mWymRFaPjJN2Qnw),这篇本周小节中,我做了如下总结:
+
+求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)
+求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)
+求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)
+
+本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!
+
+**那么本题使用求排列的方式,还是求组合的方式都可以**。
+
+即:外层for循环遍历物品,内层for遍历背包 或者 外层for遍历背包,内层for循环遍历物品 都是可以的。
+
+但本题还有特殊性,因为是要求子串,最好是遍历背包放在外循环,将遍历物品放在内循环。
+
+如果要是外层for循环遍历物品,内层for遍历背包,就需要把所有的子串都预先放在一个容器里。(如果不理解的话,可以自己尝试这么写一写就理解了)
+
+**所以最终我选择的遍历顺序为:遍历背包放在外循环,将遍历物品放在内循环。内循环从前到后**。
+
+
+5. 举例推导dp[i]
+
+以输入: s = "leetcode", wordDict = ["leet", "code"]为例,dp状态如图:
+
+
+
+dp[s.size()]就是最终结果。
+
+动规五部曲分析完毕,C++代码如下:
+
+```C++
class Solution {
public:
bool wordBreak(string s, vector& wordDict) {
unordered_set wordSet(wordDict.begin(), wordDict.end());
vector dp(s.size() + 1, false);
dp[0] = true;
- for (int i = 1; i <= s.size(); i++) {
- for (int j = 0; j < i; j++) {
+ for (int i = 1; i <= s.size(); i++) { // 遍历背包
+ for (int j = 0; j < i; j++) { // 遍历物品
string word = s.substr(j, i - j); //substr(起始位置,截取的个数)
if (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
- //for (int k = 0; k <=i; k++) cout << dp[k] << " ";
- //cout << endl;
}
return dp[s.size()];
}
};
```
-时间复杂度起始是O(n^3),因为substr返回子串的副本是O(n)的复杂度(n是substring的长度)
+* 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)
+* 空间复杂度:O(n)
+
+
+## 总结
+
+本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)非常像,所以我也给出了对应的回溯解法。
+
+稍加分析,便可知道本题是完全背包,而且是求能否组成背包,所以遍历顺序理论上来讲 两层for循环谁先谁后都可以!
+
+但因为分割子串的特殊性,遍历背包放在外循环,将遍历物品放在内循环更方便一些。
+
+本题其实递推公式都不是重点,遍历顺序才是重点,如果我直接把代码贴出来,估计同学们也会想两个for循环的顺序理所当然就是这样,甚至都不会想为什么遍历背包的for循环为什么在外层。
+
+不分析透彻不是Carl的风格啊,哈哈
+
+
+
+
+
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+
+Go:
+
+
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0141.环形链表.md b/problems/0141.环形链表.md
deleted file mode 100644
index bdb12c5b..00000000
--- a/problems/0141.环形链表.md
+++ /dev/null
@@ -1,50 +0,0 @@
-
-## 思路
-
-可以使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
-
-为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?
-
-首先第一点: **fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。**
-
-那么来看一下,**为什么fast指针和slow指针一定会相遇呢?**
-
-可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
-
-会发现最终都是这种情况, 如下图:
-
-
-
-fast和slow各自再走一步, fast和slow就相遇了
-
-这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。
-
-动画如下:
-
-
-
-
-
-## C++代码如下
-
-```
-class Solution {
-public:
- bool hasCycle(ListNode *head) {
- ListNode* fast = head;
- ListNode* slow = head;
- while(fast != NULL && fast->next != NULL) {
- slow = slow->next;
- fast = fast->next->next;
- // 快慢指针相遇,说明有环
- if (slow == fast) return true;
- }
- return false;
- }
-};
-```
-## 扩展
-
-做完这道题目,可以在做做142.环形链表II,不仅仅要找环,还要找环的入口。
-
-142.环形链表II题解:[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md
index 48aaed93..9622affc 100644
--- a/problems/0142.环形链表II.md
+++ b/problems/0142.环形链表II.md
@@ -1,11 +1,20 @@
+
+
+
+
+
+
+欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
+
+
+
> 找到有没有环已经很不容易了,还要让我找到环的入口?
-# 题目地址
+
+## 142.环形链表II
+
https://leetcode-cn.com/problems/linked-list-cycle-ii/
-
-# 第142题.环形链表II
-
题意:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
@@ -15,7 +24,7 @@ https://leetcode-cn.com/problems/linked-list-cycle-ii/

-# 思路
+## 思路
这道题目,不仅考察对链表的操作,而且还需要一些数学运算。
@@ -24,7 +33,7 @@ https://leetcode-cn.com/problems/linked-list-cycle-ii/
* 判断链表是否环
* 如果有环,如何找到这个环的入口
-## 判断链表是否有环
+### 判断链表是否有环
可以使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
@@ -34,11 +43,12 @@ https://leetcode-cn.com/problems/linked-list-cycle-ii/
那么来看一下,**为什么fast指针和slow指针一定会相遇呢?**
-可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
+可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
-
+
+
fast和slow各自再走一步, fast和slow就相遇了
@@ -46,11 +56,10 @@ fast和slow各自再走一步, fast和slow就相遇了
动画如下:
-
-
+
-## 如果有环,如何找到这个环的入口
+### 如果有环,如何找到这个环的入口
**此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。**
@@ -58,7 +67,7 @@ fast和slow各自再走一步, fast和slow就相遇了
环形入口节点到 fast指针与slow指针相遇节点 节点数为y。
从相遇节点 再到环形入口节点节点数为 z。 如图所示:
-
+
那么相遇时:
slow指针走过的节点数为: `x + y`,
@@ -68,9 +77,9 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走
`(x + y) * 2 = x + y + n (y + z)`
-两边消掉一个(x+y): `x + y = n (y + z) `
+两边消掉一个(x+y): `x + y = n (y + z) `
-因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
+因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:`x = n (y + z) - y` ,
@@ -78,7 +87,7 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走
这个公式说明什么呢?
-先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
+先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 `x = z`,
@@ -91,17 +100,16 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走
动画如下:
-
+
-那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
+那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
+代码如下:
-# C++代码
-
-```
+```C++
/**
* Definition for singly-linked list.
* struct ListNode {
@@ -136,32 +144,32 @@ public:
## 补充
-在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?**
+在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?**
即文章[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中如下的地方:
-
+
首先slow进环的时候,fast一定是先进环来了。
如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
-
+
可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。
重点来了,slow进环的时候,fast一定是在环的任意一个位置,如图:
-
+
那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。
-因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。
+因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。
**也就是说slow一定没有走到环入口3,而fast已经到环入口3了**。
-这说明什么呢?
+这说明什么呢?
**在slow开始走的那一环已经和fast相遇了**。
@@ -169,9 +177,44 @@ public:
好了,这次把为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)的补充。
-# 总结
+## 总结
这次可以说把环形链表这道题目的各个细节,完完整整的证明了一遍,说这是全网最详细讲解不为过吧,哈哈。
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
+## 其他语言版本
+
+
+Java:
+
+
+Python:
+
+```python
+class Solution:
+ def detectCycle(self, head: ListNode) -> ListNode:
+ slow, fast = head, head
+ while fast and fast.next:
+ slow = slow.next
+ fast = fast.next.next
+ # 如果相遇
+ if slow == fast:
+ p = head
+ q = slow
+ while p!=q:
+ p = p.next
+ q = q.next
+ #你也可以return q
+ return p
+
+ return None
+```
+
+Go:
+
+
+-----------------------
+* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
+* B站视频:[代码随想录](https://space.bilibili.com/525438321)
+* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
+
diff --git a/problems/0143.重排链表.md b/problems/0143.重排链表.md
deleted file mode 100644
index 9011692b..00000000
--- a/problems/0143.重排链表.md
+++ /dev/null
@@ -1,161 +0,0 @@
-
-# 思路
-
-本篇将给出三种C++实现的方法
-
-* 数组模拟
-* 双向队列模拟
-* 直接分割链表
-
-## 方法一
-
-把链表放进数组中,然后通过双指针法,一前一后,来遍历数组,构造链表。
-
-代码如下:
-
-```
-class Solution {
-public:
- void reorderList(ListNode* head) {
- vector vec;
- ListNode* cur = head;
- if (cur == nullptr) return;
- while(cur != nullptr) {
- vec.push_back(cur);
- cur = cur->next;
- }
- cur = head;
- int i = 1;
- int j = vec.size() - 1; // i j为之前前后的双指针
- int count = 0; // 计数,偶数去后面,奇数取前面
- while (i <= j) {
- if (count % 2 == 0) {
- cur->next = vec[j];
- j--;
- } else {
- cur->next = vec[i];
- i++;
- }
- cur = cur->next;
- count++;
- }
- if (vec.size() % 2 == 0) { // 如果是偶数,还要多处理中间的一个
- cur->next = vec[i];
- cur = cur->next;
- }
- cur->next = nullptr; // 注意结尾
- }
-};
-```
-
-## 方法二
-
-把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了
-```
-class Solution {
-public:
- void reorderList(ListNode* head) {
- deque que;
- ListNode* cur = head;
- if (cur == nullptr) return;
-
- while(cur->next != nullptr) {
- que.push_back(cur->next);
- cur = cur->next;
- }
-
- cur = head;
- int count = 0; // 计数,偶数去后面,奇数取前面
- ListNode* node;
- while(que.size()) {
- if (count % 2 == 0) {
- node = que.back();
- que.pop_back();
- } else {
- node = que.front();
- que.pop_front();
- }
- count++;
- cur->next = node;
- cur = cur->next;
- }
- cur->next = nullptr; // 注意结尾
- }
-};
-```
-
-## 方法三
-
-将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表。
-
-如图:
-
-
-
-这种方法,比较难,平均切割链表,看上去很简单,真正代码写的时候有很多细节,同时两个链表最后拼装整一个新的链表也有一些细节需要注意!
-
-代码如下:
-
-```
-class Solution {
-private:
- // 反转链表
- ListNode* reverseList(ListNode* head) {
- ListNode* temp; // 保存cur的下一个节点
- ListNode* cur = head;
- ListNode* pre = NULL;
- while(cur) {
- temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
- cur->next = pre; // 翻转操作
- // 更新pre 和 cur指针
- pre = cur;
- cur = temp;
- }
- return pre;
- }
-
-public:
- void reorderList(ListNode* head) {
- if (head == nullptr) return;
- // 使用快慢指针法,将链表分成长度均等的两个链表head1和head2
- // 如果总链表长度为奇数,则head1相对head2多一个节点
- ListNode* fast = head;
- ListNode* slow = head;
- while (fast && fast->next && fast->next->next) {
- fast = fast->next->next;
- slow = slow->next;
- }
- ListNode* head1 = head;
- ListNode* head2;
- head2 = slow->next;
- slow->next = nullptr;
-
- // 对head2进行翻转
- head2 = reverseList(head2);
-
- // 将head1和head2交替生成新的链表head
- ListNode* cur1 = head1;
- ListNode* cur2 = head2;
- ListNode* cur = head;
- cur1 = cur1->next;
- int count = 0; // 偶数取head2的元素,奇数取head1的元素
- while (cur1 && cur2) {
- if (count % 2 == 0) {
- cur->next = cur2;
- cur2 = cur2->next;
- } else {
- cur->next = cur1;
- cur1 = cur1->next;
- }
- count++;
- cur = cur->next;
- }
- if (cur2 != nullptr) { // 处理结尾
- cur->next = cur2;
- }
- if (cur1 != nullptr) {
- cur->next = cur1;
- }
- }
-};
-```
diff --git a/problems/0144.二叉树的前序遍历.md b/problems/0144.二叉树的前序遍历.md
deleted file mode 100644
index 7e2e1260..00000000
--- a/problems/0144.二叉树的前序遍历.md
+++ /dev/null
@@ -1,387 +0,0 @@
-# 题目地址
-https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
-
-# 思路
-这篇文章,**彻底讲清楚应该如何写递归,并给出了前中后序三种不同的迭代法,然后分析迭代法的代码风格为什么没有统一,最后给出统一的前中后序迭代法的代码,帮大家彻底吃透二叉树的深度优先遍历。**
-
-对二叉树基础理论还不清楚的话,可以看看这个[关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)。
-
-[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)介绍了二叉树的前后中序的递归遍历方式。
-
-[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)介绍了二叉树的前后中序迭代写法。
-
-[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) 介绍了二叉树前中后迭代方式的统一写法。
-
-以下开始开始正文:
-
-* 二叉树深度优先遍历
- * 前序遍历: [0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
- * 后序遍历: [0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md)
- * 中序遍历: [0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md)
-* 二叉树广度优先遍历
- * 层序遍历:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
-
-这几道题目建议大家都做一下,本题解先只写二叉树深度优先遍历,二叉树广度优先遍历请看题解[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
-
-这里想帮大家一下,明确一下二叉树的遍历规则:
-
-
-
-以上述中,前中后序遍历顺序如下:
-
-* 前序遍历(中左右):5 4 1 2 6 7 8
-* 中序遍历(左中右):1 4 2 5 7 6 8
-* 后序遍历(左右中):1 2 4 7 8 6 5
-
-# 递归法
-
-接下来我们来好好谈一谈递归,为什么很多同学看递归算法都是“一看就会,一写就废”。主要是对递归不成体系,没有方法论,每次写递归算法 ,都是靠玄学来写代码,代码能不能编过都靠运气。
-
-这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
-
-
-1. **确定递归函数的参数和返回值:**
-确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
-2. **确定终止条件:**
-写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
-3. **确定单层递归的逻辑:**
-确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
-
-好了,我们确认了递归的三要素,接下来就来练练手:
-
-**以下以前序遍历为例:**
-
-1. **确定递归函数的参数和返回值**:因为要打印出前序遍历节点的数值,所以参数里需要传入vector在放节点的数值,除了这一点就不需要在处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
-
-```
-void traversal(TreeNode* cur, vector& vec)
-```
-
-2. **确定终止条件**:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
-
-```
-if (cur == NULL) return;
-```
-
-3. 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
-
-```
-vec.push_back(cur->val); // 中
-traversal(cur->left, vec); // 左
-traversal(cur->right, vec); // 右
-```
-
-单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,在看一下完整代码:
-
-前序遍历:
-
-```
-class Solution {
-public:
- void traversal(TreeNode* cur, vector& vec) {
- if (cur == NULL) return;
- vec.push_back(cur->val); // 中
- traversal(cur->left, vec); // 左
- traversal(cur->right, vec); // 右
- }
- vector preorderTraversal(TreeNode* root) {
- vector result;
- traversal(root, result);
- return result;
- }
-};
-```
-
-中序遍历:
-
-```
- void traversal(TreeNode* cur, vector& vec) {
- if (cur == NULL) return;
- traversal(cur->left, vec); // 左
- vec.push_back(cur->val); // 中
- traversal(cur->right, vec); // 右
- }
-```
-
-后序遍历:
-
-```
- void traversal(TreeNode* cur, vector& vec) {
- if (cur == NULL) return;
- traversal(cur->left, vec); // 左
- traversal(cur->right, vec); // 右
- vec.push_back(cur->val); // 中
- }
-```
-
-# 迭代法
-
-实践过的同学,应该会发现使用迭代法实现先中后序遍历,很难写出统一的代码,不像是递归法,实现了其中的一种遍历方式,其他两种只要稍稍改一下节点顺序就可以了。而迭代法,貌似需要每一种遍历都要写出不同风格的代码。
-
-那么接下来我先带大家看一看其中的根本原因,其实是可以针对三种遍历方式,使用迭代法可以写出统一风格的代码。
-
-前序遍历(迭代法)不难写出如下代码:
-
-```
-class Solution {
-public:
- vector preorderTraversal(TreeNode* root) {
- stack st;
- vector result;
- st.push(root);
- while (!st.empty()) {
- TreeNode* node = st.top();
- st.pop();
- if (node != NULL) result.push_back(node->val);
- else continue;
- st.push(node->right);
- st.push(node->left);
- }
- return result;
- }
-};
-```
-
-这时会发现貌似使用迭代法写出先序遍历并不难,确实不难,但难却难在,我们再用迭代法写中序遍历的时候,发现套路又不一样了,目前的这个逻辑无法直接应用到中序遍历上。
-
-## 前后中遍历迭代法不统一的写法
-
-为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作,**一个是处理:将元素放进result数组中,一个是访问:遍历节点。**
-
-分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,要先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,**因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。**
-
-那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了**处理顺序和访问顺序是不一致的。**
-
-那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
-
-**中序遍历,可以写出如下代码:**
-
-```
-class Solution {
-public:
- vector inorderTraversal(TreeNode* root) {
- vector result;
- stack st;
- TreeNode* cur = root;
- while (cur != NULL || !st.empty()) {
- if (cur != NULL) {
- st.push(cur);
- cur = cur->left;
- } else {
- cur = st.top();
- st.pop();
- result.push_back(cur->val);
- cur = cur->right;
- }
- }
- return result;
- }
-};
-
-```
-
-再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序
-,然后在反转result数组,输出的结果顺序就是左右中了,如下图:
-
-
-**所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下:**
-
-```
-class Solution {
-public:
-
- vector