diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..4bd29f751f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.DS_Store +.vscode +.temp +.cache +*.iml +__pycache__ diff --git a/README.md b/README.md index 9853bbc40b..d9f060b7c2 100644 --- a/README.md +++ b/README.md @@ -1,393 +1,478 @@ -
-
-
+ 🌍 海外英文版 · + 🌍🇸 英文仓库 · + 🇨🇳 国内在线阅读 · + 🇨 Gitee 同步 +
-# 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/) +--- -(持续更新中....) +## 🔗 快速入口 -# LeetCode 刷题攻略 +- 📘 **出版书籍**:[《代码随想录》](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BASMJK1olXwABU1pUCU0SCl8IGV8WVAICU24ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYAUF1UDEsQHDZNRwYlVEBGPAIccE51dQ1cfjpVCnsHUjYbTkcbM244GFIXWQYAUV5VOHsXBF9adYOj696n5UKJosTCi_g4GmsVWwILVFhZCUIXBWgMK1wVVDZfHAIVXwAnM18LK1wVVBIEJh8PHE1lM18IK1glXQcCVVpYDU8RB2YUG18QXA4BSF5bDEIXBWsJHlgVXAEyVl9cDEInM7GFqyYQWHkHVBY1TUxoBmZtXT7L0LYTKClfCkMWEl8BGCMVCkFGBg01Dg5zSgcJUCxeD2AKNRwzChFKfGx3HQtCDnN3XV0aDB1KM2o4G10VXzY) +- 🧾 **PDF 精讲**:[算法精讲 PDF](https://programmercarl.com/qita/algo_pdf.html) +- 🎬 **算法公开课**:[170期硬核视频](https://www.bilibili.com/video/BV1fA4y1o715) +- 🧠 **卡码笔记**:[最强八股文](https://notes.kamacoder.com/) +- 👥 **学习社区**:项目 / 面经 / 学习方法 / 面试技巧 → 加入 [「代码随想录」知识星球](https://programmercarl.com/other/kstar.html) +- 🤝 **参与贡献**:本仓讲解以 C++ 为主,含 Java / Python / Go / JS 多语言实现。想点亮头像 👉 [如何提交代码](https://www.programmercarl.com/qita/join.html) · [致谢贡献者](https://github.com/youngyangyang04/leetcode-master/graphs/contributors) +- 📢 **转载须知**:全部为原创,引用请标注来源;恶意搬运将依法维权。 -## 刷题攻略的背景 +--- -很多刚开始刷题的同学都有一个困惑:面对leetcode上近两千道题目,从何刷起。 +## 📚 为什么选这套刷题路线? -其实我之前在知乎上回答过这个问题,回答内容大概是按照如下类型来刷数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心->动态规划->图论->高级数据结构,再从简单刷起,做了几个类型题目之后,再慢慢做中等题目、困难题目。 +- **不再海选题目**:README 就是刷题路线,**按顺序刷**即可。 +- **全链路学习体验**:每个专题含「理论基础 → 实战题目 → 总结复盘」。 +- **经典高频必会**:题目均为**高频面试题**与**典型考点**。 +- **多语言覆盖**:除 C++ 主线,还有社区贡献的多语言实现。 -但我能设身处地的感受到:即使有这样一个整体规划,对于一位初学者甚至算法老手寻找合适自己的题目也是很困难,时间成本很高,而且题目还不一定就是经典题目。 +
+
+
+
+
+
+1. [关于二叉树,你该了解这些!](./problems/二叉树理论基础.md)
+2. [二叉树:二叉树的递归遍历](./problems/二叉树的递归遍历.md)
+3. [二叉树:二叉树的迭代遍历](./problems/二叉树的迭代遍历.md)
+4. [二叉树:二叉树的统一迭代法](./problems/二叉树的统一迭代法.md)
+5. [二叉树:二叉树的层序遍历](./problems/0102.二叉树的层序遍历.md)
+6. [二叉树:226.翻转二叉树](./problems/0226.翻转二叉树.md)
+7. [本周小结!(二叉树)](./problems/周总结/20200927二叉树周末总结.md)
+8. [二叉树:101.对称二叉树](./problems/0101.对称二叉树.md)
+9. [二叉树:104.二叉树的最大深度](./problems/0104.二叉树的最大深度.md)
+10. [二叉树:111.二叉树的最小深度](./problems/0111.二叉树的最小深度.md)
+11. [二叉树:222.完全二叉树的节点个数](./problems/0222.完全二叉树的节点个数.md)
+12. [二叉树:110.平衡二叉树](./problems/0110.平衡二叉树.md)
+13. [二叉树:257.二叉树的所有路径](./problems/0257.二叉树的所有路径.md)
+14. [本周总结!(二叉树)](./problems/周总结/20201003二叉树周末总结.md)
+16. [二叉树:404.左叶子之和](./problems/0404.左叶子之和.md)
+17. [二叉树:513.找树左下角的值](./problems/0513.找树左下角的值.md)
+18. [二叉树:112.路径总和](./problems/0112.路径总和.md)
+19. [二叉树:106.构造二叉树](./problems/0106.从中序与后序遍历序列构造二叉树.md)
+20. [二叉树:654.最大二叉树](./problems/0654.最大二叉树.md)
+21. [本周小结!(二叉树)](./problems/周总结/20201010二叉树周末总结.md)
+22. [二叉树:617.合并两个二叉树](./problems/0617.合并二叉树.md)
+23. [二叉树:700.二叉搜索树登场!](./problems/0700.二叉搜索树中的搜索.md)
+24. [二叉树:98.验证二叉搜索树](./problems/0098.验证二叉搜索树.md)
+25. [二叉树:530.搜索树的最小绝对差](./problems/0530.二叉搜索树的最小绝对差.md)
+26. [二叉树:501.二叉搜索树中的众数](./problems/0501.二叉搜索树中的众数.md)
+27. [二叉树:236.公共祖先问题](./problems/0236.二叉树的最近公共祖先.md)
+28. [本周小结!(二叉树)](./problems/周总结/20201017二叉树周末总结.md)
+29. [二叉树:235.搜索树的最近公共祖先](./problems/0235.二叉搜索树的最近公共祖先.md)
+30. [二叉树:701.搜索树中的插入操作](./problems/0701.二叉搜索树中的插入操作.md)
+31. [二叉树:450.搜索树中的删除操作](./problems/0450.删除二叉搜索树中的节点.md)
+32. [二叉树:669.修剪二叉搜索树](./problems/0669.修剪二叉搜索树.md)
+33. [二叉树:108.将有序数组转换为二叉搜索树](./problems/0108.将有序数组转换为二叉搜索树.md)
+34. [二叉树:538.把二叉搜索树转换为累加树](./problems/0538.把二叉搜索树转换为累加树.md)
+35. [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](./problems/二叉树总结篇.md)
+
+
+1. [关于回溯算法,你该了解这些!](./problems/回溯算法理论基础.md)
+2. [回溯算法:77.组合](./problems/0077.组合.md)
+3. [回溯算法:77.组合优化](./problems/0077.组合优化.md)
+4. [回溯算法:216.组合总和III](./problems/0216.组合总和III.md)
+5. [回溯算法:17.电话号码的字母组合](./problems/0017.电话号码的字母组合.md)
+6. [本周小结!(回溯算法系列一)](./problems/周总结/20201030回溯周末总结.md)
+7. [回溯算法:39.组合总和](./problems/0039.组合总和.md)
+8. [回溯算法:40.组合总和II](./problems/0040.组合总和II.md)
+9. [回溯算法:131.分割回文串](./problems/0131.分割回文串.md)
+10. [回溯算法:93.复原IP地址](./problems/0093.复原IP地址.md)
+11. [回溯算法:78.子集](./problems/0078.子集.md)
+12. [本周小结!(回溯算法系列二)](./problems/周总结/20201107回溯周末总结.md)
+13. [回溯算法:90.子集II](./problems/0090.子集II.md)
+14. [回溯算法:491.递增子序列](./problems/0491.递增子序列.md)
+15. [回溯算法:46.全排列](./problems/0046.全排列.md)
+16. [回溯算法:47.全排列II](./problems/0047.全排列II.md)
+17. [本周小结!(回溯算法系列三)](./problems/周总结/20201112回溯周末总结.md)
+18. [回溯算法去重问题的另一种写法](./problems/回溯算法去重问题的另一种写法.md)
+19. [回溯算法:332.重新安排行程](./problems/0332.重新安排行程.md)
+20. [回溯算法:51.N皇后](./problems/0051.N皇后.md)
+21. [回溯算法:37.解数独](./problems/0037.解数独.md)
+22. [回溯算法总结篇](./problems/回溯总结.md)
+
+
+1. [关于贪心算法,你该了解这些!](./problems/贪心算法理论基础.md)
+2. [贪心算法:455.分发饼干](./problems/0455.分发饼干.md)
+3. [贪心算法:376.摆动序列](./problems/0376.摆动序列.md)
+4. [贪心算法:53.最大子序和](./problems/0053.最大子序和.md)
+5. [本周小结!(贪心算法系列一)](./problems/周总结/20201126贪心周末总结.md)
+6. [贪心算法:122.买卖股票的最佳时机II](./problems/0122.买卖股票的最佳时机II.md)
+7. [贪心算法:55.跳跃游戏](./problems/0055.跳跃游戏.md)
+8. [贪心算法:45.跳跃游戏II](./problems/0045.跳跃游戏II.md)
+9. [贪心算法:1005.K次取反后最大化的数组和](./problems/1005.K次取反后最大化的数组和.md)
+10. [本周小结!(贪心算法系列二)](./problems/周总结/20201203贪心周末总结.md)
+11. [贪心算法:134.加油站](./problems/0134.加油站.md)
+12. [贪心算法:135.分发糖果](./problems/0135.分发糖果.md)
+13. [贪心算法:860.柠檬水找零](./problems/0860.柠檬水找零.md)
+14. [贪心算法:406.根据身高重建队列](./problems/0406.根据身高重建队列.md)
+15. [本周小结!(贪心算法系列三)](./problems/周总结/20201217贪心周末总结.md)
+16. [贪心算法:406.根据身高重建队列(续集)](./problems/根据身高重建队列(vector原理讲解).md)
+17. [贪心算法:452.用最少数量的箭引爆气球](./problems/0452.用最少数量的箭引爆气球.md)
+18. [贪心算法:435.无重叠区间](./problems/0435.无重叠区间.md)
+19. [贪心算法:763.划分字母区间](./problems/0763.划分字母区间.md)
+20. [贪心算法:56.合并区间](./problems/0056.合并区间.md)
+21. [本周小结!(贪心算法系列四)](./problems/周总结/20201224贪心周末总结.md)
+22. [贪心算法:738.单调递增的数字](./problems/0738.单调递增的数字.md)
+23. [贪心算法:968.监控二叉树](./problems/0968.监控二叉树.md)
+24. [贪心算法:总结篇!(每逢总结必经典)](./problems/贪心算法总结篇.md)
+
+1. [关于动态规划,你该了解这些!](./problems/动态规划理论基础.md)
+2. [动态规划:509.斐波那契数](./problems/0509.斐波那契数.md)
+3. [动态规划:70.爬楼梯](./problems/0070.爬楼梯.md)
+4. [动态规划:746.使用最小花费爬楼梯](./problems/0746.使用最小花费爬楼梯.md)
+5. [本周小结!(动态规划系列一)](./problems/周总结/20210107动规周末总结.md)
+6. [动态规划:62.不同路径](./problems/0062.不同路径.md)
+7. [动态规划:63.不同路径II](./problems/0063.不同路径II.md)
+8. [动态规划:343.整数拆分](./problems/0343.整数拆分.md)
+9. [动态规划:96.不同的二叉搜索树](./problems/0096.不同的二叉搜索树.md)
+10. [本周小结!(动态规划系列二)](./problems/周总结/20210114动规周末总结.md)
-* [二叉树学习手册PDF开放下载!!](https://mp.weixin.qq.com/s/uSyeCq0UF6kwRKcWkLnXuw)
-* [回溯算法学习手册PDF开放下载!!](https://mp.weixin.qq.com/s/aSToAWOPnPV4GiDf9n0p-w)
-* [贪心算法学习手册PDF开放下载!!](https://mp.weixin.qq.com/s/dUUWPVB7aVMpStPOG8cKnQ)
-* [背包问题学习手册PDF开放下载!](https://mp.weixin.qq.com/s/X220c9ouxSW-gBrKC8ZAEw)
+背包问题系列:
-(将陆续整理各个专题的PDF下载版本)
+
+
+
+11. [动态规划:01背包理论基础(二维dp数组)](./problems/背包理论基础01背包-1.md)
+12. [动态规划:01背包理论基础(一维dp数组)](./problems/背包理论基础01背包-2.md)
+13. [动态规划:416.分割等和子集](./problems/0416.分割等和子集.md)
+14. [动态规划:1049.最后一块石头的重量II](./problems/1049.最后一块石头的重量II.md)
+15. [本周小结!(动态规划系列三)](./problems/周总结/20210121动规周末总结.md)
+16. [动态规划:494.目标和](./problems/0494.目标和.md)
+17. [动态规划:474.一和零](./problems/0474.一和零.md)
+18. [动态规划:完全背包理论基础(二维dp数组)](./problems/背包问题理论基础完全背包.md)
+19. [动态规划:完全背包理论基础(一维dp数组)](./problems/背包问题完全背包一维.md)
+20. [动态规划:518.零钱兑换II](./problems/0518.零钱兑换II.md)
+21. [本周小结!(动态规划系列四)](./problems/周总结/20210128动规周末总结.md)
+22. [动态规划:377.组合总和Ⅳ](./problems/0377.组合总和Ⅳ.md)
+23. [动态规划:70.爬楼梯(完全背包版本)](./problems/0070.爬楼梯完全背包版本.md)
+24. [动态规划:322.零钱兑换](./problems/0322.零钱兑换.md)
+25. [动态规划:279.完全平方数](./problems/0279.完全平方数.md)
+26. [本周小结!(动态规划系列五)](./problems/周总结/20210204动规周末总结.md)
+27. [动态规划:139.单词拆分](./problems/0139.单词拆分.md)
+28. [动态规划:多重背包理论基础](./problems/背包问题理论基础多重背包.md)
+29. [背包问题总结篇](./problems/背包总结篇.md)
-## 前序
+打家劫舍系列:
-* 编程语言
- * [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP)
+29. [动态规划:198.打家劫舍](./problems/0198.打家劫舍.md)
+30. [动态规划:213.打家劫舍II](./problems/0213.打家劫舍II.md)
+31. [动态规划:337.打家劫舍III](./problems/0337.打家劫舍III.md)
-* 编程素养
- * [看了这么多代码,谈一谈代码风格!](https://mp.weixin.qq.com/s/UR9ztxz3AyL3qdHn_zMbqw)
- * [力扣上的代码想在本地编译运行?](https://mp.weixin.qq.com/s/r1696t8lvcw7Rz4gb_jacw)
-* 工具
- * [一站式vim配置](https://github.com/youngyangyang04/PowerVim)
- * [程序员应该用什么用具来写文档?](https://mp.weixin.qq.com/s/s_hig9nioq8nT-2F7AL0SQ)
-
-* 求职
- * [程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)
- * [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw)
- * [北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/BKrjK4myNB-FYbMqW9f3yw)
- * [上海有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/iW4_rXQzc0fJDuSmPTUVdQ)
- * [深圳有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)
- * [广州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Ir_hQP0clbnvHrWzDL-qXg)
- * [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ)
- * [杭州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/33FmPJYrOU-ygovoxIaEUw)
-
-* 算法性能分析
- * [关于时间复杂度,你不知道的都在这里!](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)
-
-## 数组
-
-1. [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg)
-2. [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)
-3. [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
-4. [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg)
-5. [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)
-6. [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg)
-
-## 链表
-
-1. [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)
-2. [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)
-3. [链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/Cf95Lc6brKL4g2j8YyF3Mg)
-4. [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
-5. [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
-6. [链表:总结篇!](https://mp.weixin.qq.com/s/vK0JjSTHfpAbs8evz5hH8A)
-
-## 哈希表
-
-1. [关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)
-2. [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)
-3. [哈希表:哈希值太大了,还是得用set](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA)
-4. [哈希表:用set来判断快乐数](https://mp.weixin.qq.com/s/G4Q2Zfpfe706gLK7HpZHpA)
-5. [哈希表:map等候多时了](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)
-6. [哈希表:其实需要哈希的地方都能找到map的身影](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)
-7. [哈希表:这道题目我做过?](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ)
-8. [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
-9. [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
-10. [哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg)
-
-
-## 字符串
-
-1. [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
-2. [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)
-3. [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
-4. [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
-5. [字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)
-6. [帮你把KMP算法学个通透!(理论篇)B站视频](https://www.bilibili.com/video/BV1PD4y1o7nd)
-7. [帮你把KMP算法学个通透!(代码篇)B站视频](https://www.bilibili.com/video/BV1M5411j7Xx)
-8. [字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)
-9. [字符串:KMP算法还能干这个!](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ)
-10. [字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw)
-11. [字符串:总结篇!](https://mp.weixin.qq.com/s/gtycjyDtblmytvBRFlCZJg)
-
-## 双指针法
+股票系列:
-双指针法基本都是应用在数组,字符串与链表的题目上
+
+
+
+32. [动态规划:121.买卖股票的最佳时机](./problems/0121.买卖股票的最佳时机.md)
+33. [动态规划:本周小结(系列六)](./problems/周总结/20210225动规周末总结.md)
+34. [动态规划:122.买卖股票的最佳时机II](./problems/0122.买卖股票的最佳时机II(动态规划).md)
+35. [动态规划:123.买卖股票的最佳时机III](./problems/0123.买卖股票的最佳时机III.md)
+36. [动态规划:188.买卖股票的最佳时机IV](./problems/0188.买卖股票的最佳时机IV.md)
+37. [动态规划:309.最佳买卖股票时机含冷冻期](./problems/0309.最佳买卖股票时机含冷冻期.md)
+38. [动态规划:本周小结(系列七)](./problems/周总结/20210304动规周末总结.md)
+39. [动态规划:714.买卖股票的最佳时机含手续费](./problems/0714.买卖股票的最佳时机含手续费(动态规划).md)
+40. [动态规划:股票系列总结篇](./problems/动态规划-股票问题总结篇.md)
-1. [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
-2. [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
-3. [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
-4. [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
-5. [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
-6. [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
-7. [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
-8. [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
-9. [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)
-
-## 栈与队列
-
-1. [栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)
-2. [栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg)
-3. [栈与队列:用队列实现栈还有点别扭](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw)
-4. [栈与队列:系统中处处都是栈的应用](https://mp.weixin.qq.com/s/nLlmPMsDCIWSqAtr0jbrpQ)
-5. [栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)
-6. [栈与队列:有没有想过计算机是如何处理表达式的?](https://mp.weixin.qq.com/s/hneh2nnLT91rR8ms2fm_kw)
-7. [栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)
-8. [栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng)
-9. [栈与队列:总结篇!](https://mp.weixin.qq.com/s/xBcHyvHlWq4P13fzxEtkPg)
-
-## 二叉树
-
-题目分类大纲如下:
-
-
-1. [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)
-2. [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
-3. [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)
-4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)
-5. [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)
-6. [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)
-7. [本周小结!(二叉树)](https://mp.weixin.qq.com/s/JWmTeC7aKbBfGx4TY6uwuQ)
-8. [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)
-9. [二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)
-10. [二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)
-11. [二叉树:我有多少个节点?](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw)
-12. [二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)
-13. [二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)
-14. [还在玩耍的你,该总结啦!(本周小结之二叉树)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg)
-15. [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)
-16. [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA)
-17. [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)
-18. [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)
-19. [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
-20. [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)
-21. [本周小结!(二叉树系列三)](https://mp.weixin.qq.com/s/JLLpx3a_8jurXcz6ovgxtg)
-22. [二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ)
-23. [二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)
-24. [二叉树:我是不是一棵二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)
-25. [二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)
-26. [二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)
-27. [二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)
-28. [本周小结!(二叉树系列四)](https://mp.weixin.qq.com/s/CbdtOTP0N-HIP7DR203tSg)
-29. [二叉树:搜索树的公共祖先问题](https://mp.weixin.qq.com/s/Ja9dVw2QhBcg_vV-1fkiCg)
-30. [二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)
-31. [二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)
-32. [二叉树:修剪一棵搜索树](https://mp.weixin.qq.com/s/QzmGfYUMUWGkbRj7-ozHoQ)
-33. [二叉树:构造一棵搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw)
-34. [二叉树:搜索树转成累加树](https://mp.weixin.qq.com/s/hZtJh4T5lIGBarY-lZJf6Q)
-35. [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](https://mp.weixin.qq.com/s/-ZJn3jJVdF683ap90yIj4Q)
-
-## 回溯算法
-
-题目分类大纲如下:
-
-
-
-1. [关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)
-2. [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)
-3. [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)
-4. [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)
-5. [回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
-6. [本周小结!(回溯算法系列一)](https://mp.weixin.qq.com/s/m2GnTJdkYhAamustbb6lmw)
-7. [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)
-8. [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)
-9. [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)
-10. [回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)
-11. [回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)
-12. [本周小结!(回溯算法系列二)](https://mp.weixin.qq.com/s/uzDpjrrMCO8DOf-Tl5oBGw)
-13. [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)
-14. [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)
-15. [回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)
-16. [回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)
-17. [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)
-18. [本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)
-19. [视频来了!!带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM)
-20. [视频来了!!回溯算法(力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv)
-21. [视频来了!!回溯算法剪枝操作(力扣题目:77.组合)](https://www.bilibili.com/video/BV1wi4y157er)
-22. [视频来了!!回溯算法(力扣题目:39.组合总和)](https://www.bilibili.com/video/BV1KT4y1M7HJ/)
-23. [回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA)
-24. [回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)
-25. [回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)
-26. [一篇总结带你彻底搞透回溯算法!](https://mp.weixin.qq.com/s/r73thpBnK1tXndFDtlsdCQ)
-
-## 贪心算法
-
-题目分类大纲如下:
-
-
-
-1. [关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)
-2. [贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)
-3. [贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)
-4. [贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)
-5. [本周小结!(贪心算法系列一)](https://mp.weixin.qq.com/s/KQ2caT9GoVXgB1t2ExPncQ)
-6. [贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)
-7. [贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)
-8. [贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)
-9. [贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)
-10. [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ)
-11. [贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)
-12. [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)
-13. [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)
-14. [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)
-15. [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g)
-16. [贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)
-17. [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)
-18. [贪心算法:无重叠区间](https://mp.weixin.qq.com/s/oFOEoW-13Bm4mik-aqAOmw)
-19. [贪心算法:划分字母区间](https://mp.weixin.qq.com/s/pdX4JwV1AOpc_m90EcO2Hw)
-20. [贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)
-21. [本周小结!(贪心算法系列四)](https://mp.weixin.qq.com/s/zAMHT6JfB19ZSJNP713CAQ)
-22. [贪心算法:单调递增的数字](https://mp.weixin.qq.com/s/TAKO9qPYiv6KdMlqNq_ncg)
-23. [贪心算法:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/olWrUuDEYw2Jx5rMeG7XAg)
-24. [贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q)
-25. [贪心算法:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/ItyoYNr0moGEYeRtcjZL3Q)
-
-## 动态规划
+子序列系列:
-动态规划专题已经开始啦,来不及解释了,小伙伴们上车别掉队!
+
-1. [关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)
-2. [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)
-3. [动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw)
-4. [动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA)
-5. [本周小结!(动态规划系列一)](https://mp.weixin.qq.com/s/95VqGEDhtBBBSb-rM4QSMA)
-6. [动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)
-7. [动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww)
-8. [动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A)
-9. [动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw)
-10. [本周小结!(动态规划系列二)](https://mp.weixin.qq.com/s/VVsDwTP57g1f9aVsg6wShw)
-背包问题系列:
+41. [动态规划:300.最长递增子序列](./problems/0300.最长上升子序列.md)
+42. [动态规划:674.最长连续递增序列](./problems/0674.最长连续递增序列.md)
+43. [动态规划:718.最长重复子数组](./problems/0718.最长重复子数组.md)
+44. [动态规划:1143.最长公共子序列](./problems/1143.最长公共子序列.md)
+45. [动态规划:1035.不相交的线](./problems/1035.不相交的线.md)
+46. [动态规划:53.最大子序和](./problems/0053.最大子序和(动态规划).md)
+47. [动态规划:392.判断子序列](./problems/0392.判断子序列.md)
+48. [动态规划:115.不同的子序列](./problems/0115.不同的子序列.md)
+49. [动态规划:583.两个字符串的删除操作](./problems/0583.两个字符串的删除操作.md)
+50. [动态规划:72.编辑距离](./problems/0072.编辑距离.md)
+51. [编辑距离总结篇](./problems/为了绝杀编辑距离,卡尔做了三步铺垫.md)
+52. [动态规划:647.回文子串](./problems/0647.回文子串.md)
+53. [动态规划:516.最长回文子序列](./problems/0516.最长回文子序列.md)
+54. [动态规划总结篇](./problems/动态规划总结篇.md)
+
-
-11. [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)
-12. [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)
-13. [动态规划:分割等和子集可以用01背包!](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)
-14. [动态规划:最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg)
-15. [本周小结!(动态规划系列三)](https://mp.weixin.qq.com/s/7emRqR1O3scH63jbaE678A)
-16. [动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw)
-17. [动态规划:一和零!](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw)
-18. [动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)
-19. [动态规划:给你一些零钱,你要怎么凑?](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)
-20. [本周小结!(动态规划系列四)](https://mp.weixin.qq.com/s/vfEXwcOlrSBBcv9gg8VDJQ)
-21. [动态规划:Carl称它为排列总和!](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)
-22. [动态规划:以前我没得选,现在我选择再爬一次!](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)
-23. [动态规划: 给我个机会,我再兑换一次零钱](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)
-24. [动态规划:一样的套路,再求一次完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)
-25. [本周小结!(动态规划系列五)](https://mp.weixin.qq.com/s/znj-9j8mWymRFaPjJN2Qnw)
-26. [动态规划:单词拆分](https://mp.weixin.qq.com/s/3Spx1B6MbIYjS8YkVbByzA)
-27. [动态规划:关于多重背包,你该了解这些!](https://mp.weixin.qq.com/s/b-UUUmbvG7URWyCjQkiuuQ)
-28. [听说背包问题很难? 这篇总结篇来拯救你了](https://mp.weixin.qq.com/s/ZOehl3U1mDiyOQjFG1wNJA)
+
+
+
+
+
+* fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
+
+
+* fast和slow同时移动,直到fast指向末尾,如题:
+
+//图片中有错别词:应该将“只到”改为“直到”
+* 删除slow指向的下一个节点,如图:
+
+
+此时不难写出如下C++代码:
+
+```CPP
+class Solution {
+public:
+ ListNode* removeNthFromEnd(ListNode* head, int n) {
+ ListNode* dummyHead = new ListNode(0);
+ dummyHead->next = head;
+ ListNode* slow = dummyHead;
+ ListNode* fast = dummyHead;
+ while(n-- && fast != NULL) {
+ fast = fast->next;
+ }
+ fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
+ while (fast != NULL) {
+ fast = fast->next;
+ slow = slow->next;
+ }
+ slow->next = slow->next->next;
+
+ // ListNode *tmp = slow->next; C++释放内存的逻辑
+ // slow->next = tmp->next;
+ // delete tmp;
+
+ return dummyHead->next;
+ }
+};
+```
+
+* 时间复杂度: O(n)
+* 空间复杂度: O(1)
+
+
+## 其他语言版本
+
+### Java:
+
+```java
+class Solution {
+ public ListNode removeNthFromEnd(ListNode head, int n) {
+ //新建一个虚拟头节点指向head
+ ListNode dummyNode = new ListNode(0);
+ dummyNode.next = head;
+ //快慢指针指向虚拟头节点
+ ListNode fastIndex = dummyNode;
+ ListNode slowIndex = dummyNode;
+
+ // 只要快慢指针相差 n 个结点即可
+ for (int i = 0; i <= n; i++) {
+ fastIndex = fastIndex.next;
+ }
+ while (fastIndex != null) {
+ fastIndex = fastIndex.next;
+ slowIndex = slowIndex.next;
+ }
+
+ // 此时 slowIndex 的位置就是待删除元素的前一个位置。
+ // 具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
+ // 检查 slowIndex.next 是否为 null,以避免空指针异常
+ if (slowIndex.next != null) {
+ slowIndex.next = slowIndex.next.next;
+ }
+ return dummyNode.next;
+ }
+}
+```
+
+
+```java
+class Solution {
+ public ListNode removeNthFromEnd(ListNode head, int n) {
+ // 创建一个新的哑节点,指向原链表头
+ ListNode s = new ListNode(-1, head);
+ // 递归调用remove方法,从哑节点开始进行删除操作
+ remove(s, n);
+ // 返回新链表的头(去掉可能的哑节点)
+ return s.next;
+ }
+
+ public int remove(ListNode p, int n) {
+ // 递归结束条件:如果当前节点为空,返回0
+ if (p == null) {
+ return 0;
+ }
+ // 递归深入到下一个节点
+ int net = remove(p.next, n);
+ // 如果当前节点是倒数第n个节点,进行删除操作
+ if (net == n) {
+ p.next = p.next.next;
+ }
+ // 返回当前节点的总深度
+ return net + 1;
+ }
+}
+```
+
+
+### Python:
+
+```python
+# Definition for singly-linked list.
+# class ListNode:
+# def __init__(self, val=0, next=None):
+# self.val = val
+# self.next = next
+
+class Solution:
+ def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
+ # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
+ dummy_head = ListNode(0, head)
+
+ # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
+ slow = fast = dummy_head
+
+ # 快指针比慢指针快 n+1 步
+ for i in range(n+1):
+ fast = fast.next
+
+ # 移动两个指针,直到快速指针到达链表的末尾
+ while fast:
+ slow = slow.next
+ fast = fast.next
+
+ # 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
+ slow.next = slow.next.next
+
+ return dummy_head.next
+
+```
+### Go:
+
+```Go
+/**
+ * Definition for singly-linked list.
+ * type ListNode struct {
+ * Val int
+ * Next *ListNode
+ * }
+ */
+func removeNthFromEnd(head *ListNode, n int) *ListNode {
+ dummyNode := &ListNode{0, head}
+ fast, slow := dummyNode, dummyNode
+ for i := 0; i <= n; i++ { // 注意<=,否则快指针为空时,慢指针正好在倒数第n个上面
+ fast = fast.Next
+ }
+ for fast != nil {
+ fast = fast.Next
+ slow = slow.Next
+ }
+ slow.Next = slow.Next.Next
+ return dummyNode.Next
+}
+```
+
+### JavaScript:
+
+```js
+/**
+ * @param {ListNode} head
+ * @param {number} n
+ * @return {ListNode}
+ */
+var removeNthFromEnd = function (head, n) {
+ // 创建哨兵节点,简化解题逻辑
+ let dummyHead = new ListNode(0, head);
+ let fast = dummyHead;
+ let slow = dummyHead;
+ while (n--) fast = fast.next;
+ while (fast.next !== null) {
+ slow = slow.next;
+ fast = fast.next;
+ }
+ slow.next = slow.next.next;
+ return dummyHead.next;
+};
+```
+### TypeScript:
+
+版本一(快慢指针法):
+
+```typescript
+function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
+ let newHead: ListNode | null = new ListNode(0, head);
+ //根据leetcode题目的定义可推断这里快慢指针均不需要定义为ListNode | null。
+ let slowNode: ListNode = newHead;
+ let fastNode: ListNode = newHead;
+
+ while(n--) {
+ fastNode = fastNode.next!; //由虚拟头节点前进n个节点时,fastNode.next可推断不为null。
+ }
+ while(fastNode.next) { //遍历直至fastNode.next = null, 即尾部节点。 此时slowNode指向倒数第n个节点。
+ fastNode = fastNode.next;
+ slowNode = slowNode.next!;
+ }
+ slowNode.next = slowNode.next!.next; //倒数第n个节点可推断其next节点不为空。
+ return newHead.next;
+}
+```
+
+版本二(计算节点总数法):
+
+```typescript
+function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
+ let curNode: ListNode | null = head;
+ let listSize: number = 0;
+ while (curNode) {
+ curNode = curNode.next;
+ listSize++;
+ }
+ if (listSize === n) {
+ head = head.next;
+ } else {
+ curNode = head;
+ for (let i = 0; i < listSize - n - 1; i++) {
+ curNode = curNode.next;
+ }
+ curNode.next = curNode.next.next;
+ }
+ return head;
+};
+```
+
+版本三(递归倒退n法):
+
+```typescript
+function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
+ let newHead: ListNode | null = new ListNode(0, head);
+ let cnt = 0;
+ function recur(node) {
+ if (node === null) return;
+ recur(node.next);
+ cnt++;
+ if (cnt === n + 1) {
+ node.next = node.next.next;
+ }
+ }
+ recur(newHead);
+ return newHead.next;
+};
+```
+
+### Kotlin:
+
+```Kotlin
+fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? {
+ val pre = ListNode(0).apply {
+ this.next = head
+ }
+ var fastNode: ListNode? = pre
+ var slowNode: ListNode? = pre
+ for (i in 0..n) {
+ fastNode = fastNode?.next
+ }
+ while (fastNode != null) {
+ slowNode = slowNode?.next
+ fastNode = fastNode.next
+ }
+ slowNode?.next = slowNode?.next?.next
+ return pre.next
+}
+```
+
+### Swift:
+
+```swift
+func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
+ if head == nil {
+ return nil
+ }
+ if n == 0 {
+ return head
+ }
+ let dummyHead = ListNode(-1, head)
+ var fast: ListNode? = dummyHead
+ var slow: ListNode? = dummyHead
+ // fast 前移 n
+ for _ in 0 ..< n {
+ fast = fast?.next
+ }
+ while fast?.next != nil {
+ fast = fast?.next
+ slow = slow?.next
+ }
+ slow?.next = slow?.next?.next
+ return dummyHead.next
+}
+```
+
+### PHP:
+
+```php
+function removeNthFromEnd($head, $n) {
+ // 设置虚拟头节点
+ $dummyHead = new ListNode();
+ $dummyHead->next = $head;
+
+ $slow = $fast = $dummyHead;
+ while($n-- && $fast != null){
+ $fast = $fast->next;
+ }
+ // fast 再走一步,让 slow 指向删除节点的上一个节点
+ $fast = $fast->next;
+ while ($fast != NULL) {
+ $fast = $fast->next;
+ $slow = $slow->next;
+ }
+ $slow->next = $slow->next->next;
+ return $dummyHead->next;
+ }
+```
+
+### Scala:
+
+```scala
+object Solution {
+ def removeNthFromEnd(head: ListNode, n: Int): ListNode = {
+ val dummy = new ListNode(-1, head) // 定义虚拟头节点
+ var fast = head // 快指针从头开始走
+ var slow = dummy // 慢指针从虚拟头开始头
+ // 因为参数 n 是不可变量,所以不能使用 while(n>0){n-=1}的方式
+ for (i <- 0 until n) {
+ fast = fast.next
+ }
+ // 快指针和满指针一起走,直到fast走到null
+ while (fast != null) {
+ slow = slow.next
+ fast = fast.next
+ }
+ // 删除slow的下一个节点
+ slow.next = slow.next.next
+ // 返回虚拟头节点的下一个
+ dummy.next
+ }
+}
+```
+
+### Rust:
+
+```rust
+impl Solution {
+ pub fn remove_nth_from_end(head: Option
+
+## 算法公开课
+
+**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点](https://www.bilibili.com/video/BV1YT411g7br),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
+
+## 思路
+
+
+这道题目正常模拟就可以了。
+
+建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
+
+对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)。
+
+接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序**
+
+初始时,cur指向虚拟头结点,然后进行如下三步:
+
+
+
+操作之后,链表如下:
+
+
+
+看这个可能就更直观一些了:
+
+
+
+
+对应的C++代码实现如下: (注释中详细和如上图中的三步做对应)
+
+```CPP
+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移动两位,准备下一轮交换
+ }
+ ListNode* result = dummyHead->next;
+ delete dummyHead;
+ return result;
+ }
+};
+```
+
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+## 拓展
+
+**这里还是说一下,大家不必太在意力扣上执行用时,打败多少多少用户,这个统计不准确的。**
+
+做题的时候自己能分析出来时间复杂度就可以了,至于力扣上执行用时,大概看一下就行。
+
+上面的代码我第一次提交执行用时8ms,打败6.5%的用户,差点吓到我了。
+
+心想应该没有更好的方法了吧,也就 $O(n)$ 的时间复杂度,重复提交几次,这样了:
+
+
+
+力扣上的统计如果两份代码是 100ms 和 300ms的耗时,其实是需要注意的。
+
+如果一个是 4ms 一个是 12ms,看上去好像是一个打败了80%,一个打败了20%,其实是没有差别的。 只不过是力扣上统计的误差而已。
+
+
+## 其他语言版本
+
+### C:
+
+```c
+/**
+ * Definition for singly-linked list.
+ * struct ListNode {
+ * int val;
+ * struct ListNode *next;
+ * };
+ */
+//递归版本
+struct ListNode* swapPairs(struct ListNode* head){
+ //递归结束条件:头节点不存在或头节点的下一个节点不存在。此时不需要交换,直接返回head
+ if(!head || !head->next)
+ return head;
+ //创建一个节点指针类型保存头结点下一个节点
+ struct ListNode *newHead = head->next;
+ //更改头结点+2位节点后的值,并将头结点的next指针指向这个更改过的list
+ head->next = swapPairs(newHead->next);
+ //将新的头结点的next指针指向老的头节点
+ newHead->next = head;
+ return newHead;
+}
+```
+
+```c
+//迭代版本
+struct ListNode* swapPairs(struct ListNode* head){
+ //使用双指针避免使用中间变量
+ typedef struct ListNode ListNode;
+ ListNode *fakehead = (ListNode *)malloc(sizeof(ListNode));
+ fakehead->next = head;
+ ListNode* right = fakehead->next;
+ ListNode* left = fakehead;
+ while(left && right && right->next ){
+ left->next = right->next;
+ right->next = left->next->next;
+ left->next->next = right;
+ left = right;
+ right = left->next;
+ }
+ return fakehead->next;
+}
+```
+
+### Java:
+
+```Java
+// 递归版本
+class Solution {
+ public ListNode swapPairs(ListNode head) {
+ // base case 退出提交
+ if(head == null || head.next == null) return head;
+ // 获取当前节点的下一个节点
+ ListNode next = head.next;
+ // 进行递归
+ ListNode newNode = swapPairs(next.next);
+ // 这里进行交换
+ next.next = head;
+ head.next = newNode;
+
+ return next;
+ }
+}
+```
+
+```java
+class Solution {
+ public ListNode swapPairs(ListNode head) {
+ ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
+ dumyhead.next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
+ ListNode cur = dumyhead;
+ ListNode temp; // 临时节点,保存两个节点后面的节点
+ ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
+ ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
+ while (cur.next != null && cur.next.next != null) {
+ temp = cur.next.next.next;
+ firstnode = cur.next;
+ secondnode = cur.next.next;
+ cur.next = secondnode; // 步骤一
+ secondnode.next = firstnode; // 步骤二
+ firstnode.next = temp; // 步骤三
+ cur = firstnode; // cur移动,准备下一轮交换
+ }
+ return dumyhead.next;
+ }
+}
+```
+
+```java
+// 将步骤 2,3 交换顺序,这样不用定义 temp 节点
+public ListNode swapPairs(ListNode head) {
+ ListNode dummy = new ListNode(0, head);
+ ListNode cur = dummy;
+ while (cur.next != null && cur.next.next != null) {
+ ListNode node1 = cur.next;// 第 1 个节点
+ ListNode node2 = cur.next.next;// 第 2 个节点
+ cur.next = node2; // 步骤 1
+ node1.next = node2.next;// 步骤 3
+ node2.next = node1;// 步骤 2
+ cur = cur.next.next;
+ }
+ return dummy.next;
+}
+```
+
+### Python:
+
+```python
+# 递归版本
+# Definition for singly-linked list.
+# class ListNode:
+# def __init__(self, val=0, next=None):
+# self.val = val
+# self.next = next
+
+class Solution:
+ def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
+ if head is None or head.next is None:
+ return head
+
+ # 待翻转的两个node分别是pre和cur
+ pre = head
+ cur = head.next
+ next = head.next.next
+
+ cur.next = pre # 交换
+ pre.next = self.swapPairs(next) # 将以next为head的后续链表两两交换
+
+ return cur
+```
+
+```python
+# Definition for singly-linked list.
+# class ListNode:
+# def __init__(self, val=0, next=None):
+# self.val = val
+# self.next = next
+
+class Solution:
+ def swapPairs(self, head: ListNode) -> ListNode:
+ dummy_head = ListNode(next=head)
+ current = dummy_head
+
+ # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
+ while current.next and current.next.next:
+ temp = current.next # 防止节点修改
+ temp1 = current.next.next.next
+
+ current.next = current.next.next
+ current.next.next = temp
+ temp.next = temp1
+ current = current.next.next
+ return dummy_head.next
+
+```
+
+### Go:
+
+```go
+func swapPairs(head *ListNode) *ListNode {
+ dummy := &ListNode{
+ Next: head,
+ }
+ //head=list[i]
+ //pre=list[i-1]
+ pre := dummy
+ for head != nil && head.Next != nil {
+ pre.Next = head.Next
+ next := head.Next.Next
+ head.Next.Next = head
+ head.Next = next
+ //pre=list[(i+2)-1]
+ pre = head
+ //head=list[(i+2)]
+ head = next
+ }
+ return dummy.Next
+}
+```
+
+```go
+// 递归版本
+func swapPairs(head *ListNode) *ListNode {
+ if head == nil || head.Next == nil {
+ return head
+ }
+ next := head.Next
+ head.Next = swapPairs(next.Next)
+ next.Next = head
+ return next
+}
+```
+
+### JavaScript:
+
+```javascript
+var swapPairs = function (head) {
+ let ret = new ListNode(0, head), temp = ret;
+ while (temp.next && temp.next.next) {
+ let cur = temp.next.next, pre = temp.next;
+ pre.next = cur.next;
+ cur.next = pre;
+ temp.next = cur;
+ temp = pre;
+ }
+ return ret.next;
+};
+```
+
+```javascript
+// 递归版本
+var swapPairs = function (head) {
+ if (head == null || head.next == null) {
+ return head;
+ }
+
+ let after = head.next;
+ head.next = swapPairs(after.next);
+ after.next = head;
+
+ return after;
+};
+```
+
+### TypeScript:
+
+```typescript
+function swapPairs(head: ListNode | null): ListNode | null {
+ const dummyNode: ListNode = new ListNode(0, head);
+ let curNode: ListNode | null = dummyNode;
+ while (curNode && curNode.next && curNode.next.next) {
+ let firstNode: ListNode = curNode.next,
+ secNode: ListNode = curNode.next.next,
+ thirdNode: ListNode | null = curNode.next.next.next;
+ curNode.next = secNode;
+ secNode.next = firstNode;
+ firstNode.next = thirdNode;
+ curNode = firstNode;
+ }
+ return dummyNode.next;
+};
+```
+
+### Kotlin:
+
+```kotlin
+fun swapPairs(head: ListNode?): ListNode? {
+ val dummyNode = ListNode(0).apply {
+ this.next = head
+ }
+ var cur: ListNode? = dummyNode
+ while (cur?.next != null && cur.next?.next != null) {
+ val temp = cur.next
+ val temp2 = cur.next?.next?.next
+ cur.next = cur.next?.next
+ cur.next?.next = temp
+ cur.next?.next?.next = temp2
+ cur = cur.next?.next
+ }
+ return dummyNode.next
+}
+```
+
+### Swift:
+
+```swift
+func swapPairs(_ head: ListNode?) -> ListNode? {
+ if head == nil || head?.next == nil {
+ return head
+ }
+ let dummyHead: ListNode = ListNode(-1, head)
+ var current: ListNode? = dummyHead
+ while current?.next != nil && current?.next?.next != nil {
+ let temp1 = current?.next
+ let temp2 = current?.next?.next?.next
+
+ current?.next = current?.next?.next
+ current?.next?.next = temp1
+ current?.next?.next?.next = temp2
+
+ current = current?.next?.next
+ }
+ return dummyHead.next
+}
+```
+### Scala:
+
+```scala
+// 虚拟头节点
+object Solution {
+ def swapPairs(head: ListNode): ListNode = {
+ var dummy = new ListNode(0, head) // 虚拟头节点
+ var pre = dummy
+ var cur = head
+ // 当pre的下一个和下下个都不为空,才进行两两转换
+ while (pre.next != null && pre.next.next != null) {
+ var tmp: ListNode = cur.next.next // 缓存下一次要进行转换的第一个节点
+ pre.next = cur.next // 步骤一
+ cur.next.next = cur // 步骤二
+ cur.next = tmp // 步骤三
+ // 下面是准备下一轮的交换
+ pre = cur
+ cur = tmp
+ }
+ // 最终返回dummy虚拟头节点的下一个,return可以省略
+ dummy.next
+ }
+}
+```
+
+### PHP:
+
+```php
+//虚拟头结点
+function swapPairs($head) {
+ if ($head == null || $head->next == null) {
+ return $head;
+ }
+
+ $dummyNode = new ListNode(0, $head);
+ $preNode = $dummyNode; //虚拟头结点
+ $curNode = $head;
+ $nextNode = $head->next;
+ while($curNode && $nextNode) {
+ $nextNextNode = $nextNode->next; //存下一个节点
+ $nextNode->next = $curNode; //交换curHead 和 nextHead
+ $curNode->next = $nextNextNode;
+ $preNode->next = $nextNode; //上一个节点的下一个指向指向nextHead
+
+ //更新当前的几个指针
+ $preNode = $preNode->next->next;
+ $curNode = $nextNextNode;
+ $nextNode = $nextNextNode->next;
+ }
+
+ return $dummyNode->next;
+}
+
+//递归版本
+function swapPairs($head)
+{
+ // 终止条件
+ if ($head === null || $head->next === null) {
+ return $head;
+ }
+
+ //结果要返回的头结点
+ $next = $head->next;
+ $head->next = $this->swapPairs($next->next); //当前头结点->next指向更新
+ $next->next = $head; //当前第二个节点的->next指向更新
+ return $next; //返回翻转后的头结点
+}
+```
+
+### Rust:
+
+```rust
+// 虚拟头节点
+impl Solution {
+ pub fn swap_pairs(head: Option
+
+
+然后就找到了下标2,指向b,继续匹配:如图:
+
+
+以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!
+
+**下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。**
+
+所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。
+
+**很多介绍KMP的文章或者视频并没有把为什么要用前缀表?这个问题说清楚,而是直接默认使用前缀表。**
+
+### 如何计算前缀表
+
+接下来就要说一说怎么计算前缀表。
+
+如图:
+
+
+
+长度为前1个字符的子串`a`,最长相同前后缀的长度为0。(注意字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**;**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。)
+
+
+
+长度为前2个字符的子串`aa`,最长相同前后缀的长度为1。
+
+
+
+长度为前3个字符的子串`aab`,最长相同前后缀的长度为0。
+
+以此类推:
+长度为前4个字符的子串`aaba`,最长相同前后缀的长度为1。
+长度为前5个字符的子串`aabaa`,最长相同前后缀的长度为2。
+长度为前6个字符的子串`aabaaf`,最长相同前后缀的长度为0。
+
+那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
+
+
+可以看出模式串与前缀表对应位置的数字表示的就是:**下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
+
+再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:
+
+
+
+找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
+
+为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。
+
+所以要看前一位的 前缀表的数值。
+
+前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
+
+最后就在文本串中找到了和模式串匹配的子串了。
+
+### 前缀表与next数组
+
+很多KMP算法的实现都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?
+
+next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
+
+为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。
+
+其实**这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。**
+
+后面我会提供两种不同的实现代码,大家就明白了。
+
+### 使用next数组来匹配
+
+**以下我们以前缀表统一减一之后的next数组来做演示**。
+
+有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。
+
+注意next数组是新前缀表(旧前缀表统一减一了)。
+
+匹配过程动画如下:
+
+
+
+### 时间复杂度分析
+
+其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
+
+暴力的解法显而易见是O(n × m),所以**KMP在字符串匹配中极大地提高了搜索的效率。**
+
+为了和力扣题目28.实现strStr保持一致,方便大家理解,以下文章统称haystack为文本串, needle为模式串。
+
+都知道使用KMP算法,一定要构造next数组。
+
+### 构造next数组
+
+我们定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。 代码如下:
+
+```
+void getNext(int* next, const string& s)
+```
+
+**构造next数组其实就是计算模式串s,前缀表的过程。** 主要有如下三步:
+
+1. 初始化
+2. 处理前后缀不相同的情况
+3. 处理前后缀相同的情况
+
+接下来我们详解一下。
+
+1. 初始化:
+
+定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。
+
+然后还要对next数组进行初始化赋值,如下:
+
+```cpp
+int j = -1;
+next[0] = j;
+```
+
+j 为什么要初始化为 -1呢,因为之前说过 前缀表要统一减一的操作仅仅是其中的一种实现,我们这里选择j初始化为-1,下文我还会给出j不初始化为-1的实现代码。
+
+next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)
+
+所以初始化next[0] = j 。
+
+
+2. 处理前后缀不相同的情况
+
+
+因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。
+
+所以遍历模式串s的循环下标i 要从 1开始,代码如下:
+
+```cpp
+for (int i = 1; i < s.size(); i++) {
+```
+
+如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
+
+怎么回退呢?
+
+next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。
+
+那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
+
+所以,处理前后缀不相同的情况代码如下:
+
+```cpp
+while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
+ j = next[j]; // 向前回退
+}
+```
+
+3. 处理前后缀相同的情况
+
+如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
+
+代码如下:
+
+```
+if (s[i] == s[j + 1]) { // 找到相同的前后缀
+ j++;
+}
+next[i] = j;
+```
+
+最后整体构建next数组的函数代码如下:
+
+```CPP
+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]; // 向前回退
+ }
+ if (s[i] == s[j + 1]) { // 找到相同的前后缀
+ j++;
+ }
+ next[i] = j; // 将j(前缀的长度)赋给next[i]
+ }
+}
+```
+
+
+代码构造next数组的逻辑流程动画如下:
+
+
+
+得到了next数组之后,就要用这个来做匹配了。
+
+### 使用next数组来做匹配
+
+在文本串s里 找是否出现过模式串t。
+
+定义两个下标j 指向模式串起始位置,i指向文本串起始位置。
+
+那么j初始值依然为-1,为什么呢? **依然因为next数组里记录的起始位置为-1。**
+
+i就从0开始,遍历文本串,代码如下:
+
+```cpp
+for (int i = 0; i < s.size(); i++)
+```
+
+接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。
+
+如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。
+
+代码如下:
+
+```cpp
+while(j >= 0 && s[i] != t[j + 1]) {
+ j = next[j];
+}
+```
+
+如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:
+
+```cpp
+if (s[i] == t[j + 1]) {
+ j++; // i的增加在for循环里
+}
+```
+
+如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
+
+本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
+
+代码如下:
+
+```cpp
+if (j == (t.size() - 1) ) {
+ return (i - t.size() + 1);
+}
+```
+
+那么使用next数组,用模式串匹配文本串的整体代码如下:
+
+```CPP
+int j = -1; // 因为next数组里记录的起始位置为-1
+for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
+ while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
+ j = next[j]; // j 寻找之前匹配的位置
+ }
+ if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
+ j++; // i的增加在for循环里
+ }
+ if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
+ return (i - t.size() + 1);
+ }
+}
+```
+
+此时所有逻辑的代码都已经写出来了,力扣 28.实现strStr 题目的整体代码如下:
+
+### 前缀表统一减一 C++代码实现
+
+```CPP
+class Solution {
+public:
+ 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]; // 向前回退
+ }
+ if (s[i] == s[j + 1]) { // 找到相同的前后缀
+ j++;
+ }
+ next[i] = j; // 将j(前缀的长度)赋给next[i]
+ }
+ }
+ int strStr(string haystack, string needle) {
+ if (needle.size() == 0) {
+ return 0;
+ }
+ vector
+ * 时间复杂度:O(m*n)
+ * 空间复杂度:O(1)
+ * 注:n为haystack的长度,m为needle的长度
+ */
+ public int strStr(String haystack, String needle) {
+ int m = needle.length();
+ // 当 needle 是空字符串时我们应当返回 0
+ if (m == 0) {
+ return 0;
+ }
+ int n = haystack.length();
+ if (n < m) {
+ return -1;
+ }
+ int i = 0;
+ int j = 0;
+ while (i < n - m + 1) {
+ // 找到首字母相等
+ while (i < n && haystack.charAt(i) != needle.charAt(j)) {
+ i++;
+ }
+ if (i == n) {// 没有首字母相等的
+ return -1;
+ }
+ // 遍历后续字符,判断是否相等
+ i++;
+ j++;
+ while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
+ i++;
+ j++;
+ }
+ if (j == m) {// 找到
+ return i - j;
+ } else {// 未找到
+ i -= j - 1;
+ j = 0;
+ }
+ }
+ return -1;
+ }
+}
+```
+
+```java
+// 方法一
+class Solution {
+ public void getNext(int[] next, String s){
+ int j = -1;
+ next[0] = j;
+ for (int i = 1; i < s.length(); i++){
+ while(j >= 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 < haystack.length(); i++){
+ while(j>=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;
+ }
+}
+```
+
+```Java
+class Solution {
+ //前缀表(不减一)Java实现
+ public int strStr(String haystack, String needle) {
+ if (needle.length() == 0) return 0;
+ int[] next = new int[needle.length()];
+ getNext(next, needle);
+
+ int j = 0;
+ for (int i = 0; i < haystack.length(); i++) {
+ while (j > 0 && needle.charAt(j) != haystack.charAt(i))
+ j = next[j - 1];
+ if (needle.charAt(j) == haystack.charAt(i))
+ j++;
+ if (j == needle.length())
+ return i - needle.length() + 1;
+ }
+ return -1;
+
+ }
+
+ private void getNext(int[] next, String s) {
+ int j = 0;
+ next[0] = 0;
+ for (int i = 1; i < s.length(); i++) {
+ while (j > 0 && s.charAt(j) != s.charAt(i))
+ j = next[j - 1];
+ if (s.charAt(j) == s.charAt(i))
+ j++;
+ next[i] = j;
+ }
+ }
+}
+```
+
+### Python3:
+(版本一)前缀表(减一)
+
+```python
+class Solution:
+ def getNext(self, next, s):
+ j = -1
+ next[0] = j
+ for i in range(1, len(s)):
+ while j >= 0 and s[i] != s[j+1]:
+ j = next[j]
+ if s[i] == s[j+1]:
+ j += 1
+ next[i] = j
+
+ def strStr(self, haystack: str, needle: str) -> int:
+ if not needle:
+ return 0
+ next = [0] * len(needle)
+ self.getNext(next, needle)
+ j = -1
+ for i in range(len(haystack)):
+ while j >= 0 and haystack[i] != needle[j+1]:
+ j = next[j]
+ if haystack[i] == needle[j+1]:
+ j += 1
+ if j == len(needle) - 1:
+ return i - len(needle) + 1
+ return -1
+```
+(版本二)前缀表(不减一)
+
+```python
+class Solution:
+ def getNext(self, next: List[int], s: str) -> None:
+ j = 0
+ next[0] = 0
+ for i in range(1, len(s)):
+ while j > 0 and s[i] != s[j]:
+ j = next[j - 1]
+ if s[i] == s[j]:
+ j += 1
+ next[i] = j
+
+ def strStr(self, haystack: str, needle: str) -> int:
+ if len(needle) == 0:
+ return 0
+ next = [0] * len(needle)
+ self.getNext(next, needle)
+ j = 0
+ for i in range(len(haystack)):
+ while j > 0 and haystack[i] != needle[j]:
+ j = next[j - 1]
+ if haystack[i] == needle[j]:
+ j += 1
+ if j == len(needle):
+ return i - len(needle) + 1
+ return -1
+```
+
+
+(版本三)暴力法
+```python
+class Solution(object):
+ def strStr(self, haystack, needle):
+ """
+ :type haystack: str
+ :type needle: str
+ :rtype: int
+ """
+ m, n = len(haystack), len(needle)
+ for i in range(m):
+ if haystack[i:i+n] == needle:
+ return i
+ return -1
+```
+(版本四)使用 index
+```python
+class Solution:
+ def strStr(self, haystack: str, needle: str) -> int:
+ try:
+ return haystack.index(needle)
+ except ValueError:
+ return -1
+```
+(版本五)使用 find
+```python
+class Solution:
+ def strStr(self, haystack: str, needle: str) -> int:
+ return haystack.find(needle)
+
+```
+
+### Go:
+
+```go
+// 方法一:前缀表使用减1实现
+
+// getNext 构造前缀表next
+// params:
+// next 前缀表数组
+// s 模式串
+func getNext(next []int, s string) {
+ j := -1 // j表示 最长相等前后缀长度
+ next[0] = j
+
+ for i := 1; i < len(s); i++ {
+ for j >= 0 && s[i] != s[j+1] {
+ j = next[j] // 回退前一位
+ }
+ if s[i] == s[j+1] {
+ j++
+ }
+ next[i] = j // next[i]是i(包括i)之前的最长相等前后缀长度
+ }
+}
+func strStr(haystack string, needle string) int {
+ if len(needle) == 0 {
+ return 0
+ }
+ next := make([]int, len(needle))
+ getNext(next, needle)
+ j := -1 // 模式串的起始位置 next为-1 因此也为-1
+ for i := 0; i < len(haystack); i++ {
+ for j >= 0 && haystack[i] != needle[j+1] {
+ j = next[j] // 寻找下一个匹配点
+ }
+ if haystack[i] == needle[j+1] {
+ j++
+ }
+ if j == len(needle)-1 { // j指向了模式串的末尾
+ return i - len(needle) + 1
+ }
+ }
+ return -1
+}
+```
+
+```go
+// 方法二: 前缀表无减一或者右移
+
+// getNext 构造前缀表next
+// params:
+// next 前缀表数组
+// s 模式串
+func getNext(next []int, s string) {
+ j := 0
+ next[0] = j
+ for i := 1; i < len(s); i++ {
+ for j > 0 && s[i] != s[j] {
+ j = next[j-1]
+ }
+ if s[i] == s[j] {
+ j++
+ }
+ next[i] = j
+ }
+}
+func strStr(haystack string, needle string) int {
+ n := len(needle)
+ if n == 0 {
+ return 0
+ }
+ j := 0
+ next := make([]int, n)
+ getNext(next, needle)
+ for i := 0; i < len(haystack); i++ {
+ for j > 0 && haystack[i] != needle[j] {
+ j = next[j-1] // 回退到j的前一位
+ }
+ if haystack[i] == needle[j] {
+ j++
+ }
+ if j == n {
+ return i - n + 1
+ }
+ }
+ return -1
+}
+```
+
+### JavaScript:
+
+> 前缀表统一减一
+
+```javascript
+/**
+ * @param {string} haystack
+ * @param {string} needle
+ * @return {number}
+ */
+var strStr = function (haystack, needle) {
+ if (needle.length === 0)
+ return 0;
+
+ const getNext = (needle) => {
+ let next = [];
+ let j = -1;
+ next.push(j);
+
+ for (let i = 1; i < needle.length; ++i) {
+ while (j >= 0 && needle[i] !== needle[j + 1])
+ j = next[j];
+ if (needle[i] === needle[j + 1])
+ j++;
+ next.push(j);
+ }
+
+ return next;
+ }
+
+ let next = getNext(needle);
+ let j = -1;
+ for (let i = 0; i < haystack.length; ++i) {
+ while (j >= 0 && haystack[i] !== needle[j + 1])
+ j = next[j];
+ if (haystack[i] === needle[j + 1])
+ j++;
+ if (j === needle.length - 1)
+ return (i - needle.length + 1);
+ }
+
+ return -1;
+};
+```
+
+> 前缀表统一不减一
+
+```javascript
+/**
+ * @param {string} haystack
+ * @param {string} needle
+ * @return {number}
+ */
+var strStr = function (haystack, needle) {
+ if (needle.length === 0)
+ return 0;
+
+ const getNext = (needle) => {
+ let next = [];
+ let j = 0;
+ next.push(j);
+
+ for (let i = 1; i < needle.length; ++i) {
+ while (j > 0 && needle[i] !== needle[j])
+ j = next[j - 1];
+ if (needle[i] === needle[j])
+ j++;
+ next.push(j);
+ }
+
+ return next;
+ }
+
+ let next = getNext(needle);
+ let j = 0;
+ for (let i = 0; i < haystack.length; ++i) {
+ while (j > 0 && haystack[i] !== needle[j])
+ j = next[j - 1];
+ if (haystack[i] === needle[j])
+ j++;
+ if (j === needle.length)
+ return (i - needle.length + 1);
+ }
+
+ return -1;
+};
+```
+
+### TypeScript:
+
+> 前缀表统一减一
+
+```typescript
+function strStr(haystack: string, needle: string): number {
+ function getNext(str: string): number[] {
+ let next: number[] = [];
+ let j: number = -1;
+ next[0] = j;
+ for (let i = 1, length = str.length; i < length; i++) {
+ while (j >= 0 && str[i] !== str[j + 1]) {
+ j = next[j];
+ }
+ if (str[i] === str[j + 1]) {
+ j++;
+ }
+ next[i] = j;
+ }
+ return next;
+ }
+ if (needle.length === 0) return 0;
+ let next: number[] = getNext(needle);
+ let j: number = -1;
+ for (let i = 0, length = haystack.length; i < length; i++) {
+ while (j >= 0 && haystack[i] !== needle[j + 1]) {
+ j = next[j];
+ }
+ if (haystack[i] === needle[j + 1]) {
+ if (j === needle.length - 2) {
+ return i - j - 1;
+ }
+ j++;
+ }
+ }
+ return -1;
+};
+```
+
+> 前缀表不减一
+
+```typescript
+// 不减一版本
+function strStr(haystack: string, needle: string): number {
+ function getNext(str: string): number[] {
+ let next: number[] = [];
+ let j: number = 0;
+ next[0] = j;
+ for (let i = 1, length = str.length; i < length; i++) {
+ while (j > 0 && str[i] !== str[j]) {
+ j = next[j - 1];
+ }
+ if (str[i] === str[j]) {
+ j++;
+ }
+ next[i] = j;
+ }
+ return next;
+ }
+ if (needle.length === 0) return 0;
+ let next: number[] = getNext(needle);
+ let j: number = 0;
+ for (let i = 0, length = haystack.length; i < length; i++) {
+ while (j > 0 && haystack[i] !== needle[j]) {
+ j = next[j - 1];
+ }
+ if (haystack[i] === needle[j]) {
+ if (j === needle.length - 1) {
+ return i - j;
+ }
+ j++;
+ }
+ }
+ return -1;
+}
+```
+
+### Swift:
+
+> 前缀表统一减一
+
+```swift
+func strStr(_ haystack: String, _ needle: String) -> Int {
+
+ let s = Array(haystack), p = Array(needle)
+ guard p.count != 0 else { return 0 }
+
+ // 2 pointer
+ var j = -1
+ var next = [Int](repeating: -1, count: needle.count)
+ // KMP
+ getNext(&next, needle: p)
+ for i in 0 ..< s.count {
+ while j >= 0 && s[i] != p[j + 1] {
+ //不匹配之后寻找之前匹配的位置
+ j = next[j]
+ }
+ if s[i] == p[j + 1] {
+ //匹配,双指针同时后移
+ j += 1
+ }
+ if j == (p.count - 1) {
+ //出现匹配字符串
+ return i - p.count + 1
+ }
+ }
+ return -1
+}
+
+//前缀表统一减一
+func getNext(_ next: inout [Int], needle: [Character]) {
+
+ var j: Int = -1
+ next[0] = j
+
+ // i 从 1 开始
+ for i in 1 ..< needle.count {
+ while j >= 0 && needle[i] != needle[j + 1] {
+ j = next[j]
+ }
+ if needle[i] == needle[j + 1] {
+ j += 1;
+ }
+ next[i] = j
+ }
+ print(next)
+}
+
+```
+
+> 前缀表右移
+
+```swift
+func strStr(_ haystack: String, _ needle: String) -> Int {
+
+ let s = Array(haystack), p = Array(needle)
+ guard p.count != 0 else { return 0 }
+
+ var j = 0
+ var next = [Int].init(repeating: 0, count: p.count)
+ getNext(&next, p)
+
+ for i in 0 ..< s.count {
+
+ while j > 0 && s[i] != p[j] {
+ j = next[j]
+ }
+
+ if s[i] == p[j] {
+ j += 1
+ }
+
+ if j == p.count {
+ return i - p.count + 1
+ }
+ }
+
+ return -1
+ }
+
+ // 前缀表后移一位,首位用 -1 填充
+ func getNext(_ next: inout [Int], _ needle: [Character]) {
+
+ guard needle.count > 1 else { return }
+
+ var j = 0
+ next[0] = j
+
+ for i in 1 ..< needle.count-1 {
+
+ while j > 0 && needle[i] != needle[j] {
+ j = next[j-1]
+ }
+
+ if needle[i] == needle[j] {
+ j += 1
+ }
+
+ next[i] = j
+ }
+ next.removeLast()
+ next.insert(-1, at: 0)
+ }
+```
+
+> 前缀表统一不减一
+```swift
+
+func strStr(_ haystack: String, _ needle: String) -> Int {
+
+ let s = Array(haystack), p = Array(needle)
+ guard p.count != 0 else { return 0 }
+
+ var j = 0
+ var next = [Int](repeating: 0, count: needle.count)
+ // KMP
+ getNext(&next, needle: p)
+
+ for i in 0 ..< s.count {
+ while j > 0 && s[i] != p[j] {
+ j = next[j-1]
+ }
+
+ if s[i] == p[j] {
+ j += 1
+ }
+
+ if j == p.count {
+ return i - p.count + 1
+ }
+ }
+ return -1
+ }
+
+ //前缀表
+ func getNext(_ next: inout [Int], needle: [Character]) {
+
+ var j = 0
+ next[0] = j
+
+ for i in 1 ..< needle.count {
+
+ while j>0 && needle[i] != needle[j] {
+ j = next[j-1]
+ }
+
+ if needle[i] == needle[j] {
+ j += 1
+ }
+
+ next[i] = j
+
+ }
+ }
+
+```
+
+### PHP:
+
+> 前缀表统一减一
+```php
+function strStr($haystack, $needle) {
+ if (strlen($needle) == 0) return 0;
+ $next= [];
+ $this->getNext($next,$needle);
+
+ $j = -1;
+ for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
+ while($j >= 0 && $haystack[$i] != $needle[$j + 1]) {
+ $j = $next[$j];
+ }
+ if ($haystack[$i] == $needle[$j + 1]) {
+ $j++;
+ }
+ if ($j == (strlen($needle) - 1) ) {
+ return ($i - strlen($needle) + 1);
+ }
+ }
+ return -1;
+}
+
+function getNext(&$next, $s){
+ $j = -1;
+ $next[0] = $j;
+ for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
+ while ($j >= 0 && $s[$i] != $s[$j + 1]) {
+ $j = $next[$j];
+ }
+ if ($s[$i] == $s[$j + 1]) {
+ $j++;
+ }
+ $next[$i] = $j;
+ }
+}
+```
+
+> 前缀表统一不减一
+```php
+function strStr($haystack, $needle) {
+ if (strlen($needle) == 0) return 0;
+ $next= [];
+ $this->getNext($next,$needle);
+
+ $j = 0;
+ for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
+ while($j > 0 && $haystack[$i] != $needle[$j]) {
+ $j = $next[$j-1];
+ }
+ if ($haystack[$i] == $needle[$j]) {
+ $j++;
+ }
+ if ($j == strlen($needle)) {
+ return ($i - strlen($needle) + 1);
+ }
+ }
+ return -1;
+}
+
+function getNext(&$next, $s){
+ $j = 0;
+ $next[0] = $j;
+ for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
+ while ($j > 0 && $s[$i] != $s[$j]) {
+ $j = $next[$j-1];
+ }
+ if ($s[$i] == $s[$j]) {
+ $j++;
+ }
+ $next[$i] = $j;
+ }
+}
+```
+
+### Rust:
+
+> 前缀表统一不减一
+```Rust
+impl Solution {
+ pub fn get_next(next: &mut Vec
+
+对应的C++代码如下:
+
+```CPP
+class Solution {
+public:
+ void nextPermutation(vector> combinationSum(int[] candidates, int target) {
+ List
> res = new ArrayList<>();
+ Arrays.sort(candidates); // 先进行排序
+ backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
+ return res;
+ }
+
+ public void backtracking(List
> res, List
> ans = new ArrayList<>();
+ boolean[] used;
+ int sum = 0;
+
+ public List
> combinationSum2(int[] candidates, int target) {
+ used = new boolean[candidates.length];
+ // 加标志数组,用来辅助判断同层节点是否已经遍历
+ Arrays.fill(used, false);
+ // 为了将重复的数字都放到一起,所以先进行排序
+ Arrays.sort(candidates);
+ backTracking(candidates, target, 0);
+ return ans;
+ }
+
+ private void backTracking(int[] candidates, int target, int startIndex) {
+ if (sum == target) {
+ ans.add(new ArrayList(path));
+ }
+ for (int i = startIndex; i < candidates.length; i++) {
+ if (sum + candidates[i] > target) {
+ break;
+ }
+ // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
+ if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
+ continue;
+ }
+ used[i] = true;
+ sum += candidates[i];
+ path.add(candidates[i]);
+ // 每个节点仅能选择一次,所以从下一位开始
+ backTracking(candidates, target, i + 1);
+ used[i] = false;
+ sum -= candidates[i];
+ path.removeLast();
+ }
+ }
+}
+
+```
+**不使用标记数组**
+```Java
+class Solution {
+ List
> res = new ArrayList<>();
+ LinkedList
> combinationSum2( int[] candidates, int target ) {
+ //为了将重复的数字都放到一起,所以先进行排序
+ Arrays.sort( candidates );
+ backTracking( candidates, target, 0 );
+ return res;
+ }
+
+ private void backTracking( int[] candidates, int target, int start ) {
+ if ( sum == target ) {
+ res.add( new ArrayList<>( path ) );
+ return;
+ }
+ for ( int i = start; i < candidates.length && sum + candidates[i] <= target; i++ ) {
+ //正确剔除重复解的办法
+ //跳过同一树层使用过的元素
+ if ( i > start && candidates[i] == candidates[i - 1] ) {
+ continue;
+ }
+
+ sum += candidates[i];
+ path.add( candidates[i] );
+ // i+1 代表当前组内元素只选取一次
+ backTracking( candidates, target, i + 1 );
+
+ int temp = path.getLast();
+ sum -= temp;
+ path.removeLast();
+ }
+ }
+}
+```
+
+### Python
+回溯
+```python
+class Solution:
+
+
+ def backtracking(self, candidates, target, total, startIndex, path, result):
+ if total == target:
+ result.append(path[:])
+ return
+
+ for i in range(startIndex, len(candidates)):
+ if i > startIndex and candidates[i] == candidates[i - 1]:
+ continue
+
+ if total + candidates[i] > target:
+ break
+
+ total += candidates[i]
+ path.append(candidates[i])
+ self.backtracking(candidates, target, total, i + 1, path, result)
+ total -= candidates[i]
+ path.pop()
+
+ def combinationSum2(self, candidates, target):
+ result = []
+ candidates.sort()
+ self.backtracking(candidates, target, 0, 0, [], result)
+ return result
+
+```
+回溯 使用used
+```python
+class Solution:
+
+
+ def backtracking(self, candidates, target, total, startIndex, used, path, result):
+ if total == target:
+ result.append(path[:])
+ return
+
+ for i in range(startIndex, len(candidates)):
+ # 对于相同的数字,只选择第一个未被使用的数字,跳过其他相同数字
+ if i > startIndex and candidates[i] == candidates[i - 1] and not used[i - 1]:
+ continue
+
+ if total + candidates[i] > target:
+ break
+
+ total += candidates[i]
+ path.append(candidates[i])
+ used[i] = True
+ self.backtracking(candidates, target, total, i + 1, used, path, result)
+ used[i] = False
+ total -= candidates[i]
+ path.pop()
+
+ def combinationSum2(self, candidates, target):
+ used = [False] * len(candidates)
+ result = []
+ candidates.sort()
+ self.backtracking(candidates, target, 0, 0, used, [], result)
+ return result
+
+```
+回溯优化
+```python
+class Solution:
+ def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
+ candidates.sort()
+ results = []
+ self.combinationSumHelper(candidates, target, 0, [], results)
+ return results
+
+ def combinationSumHelper(self, candidates, target, index, path, results):
+ if target == 0:
+ results.append(path[:])
+ return
+ for i in range(index, len(candidates)):
+ if i > index and candidates[i] == candidates[i - 1]:
+ continue
+ if candidates[i] > target:
+ break
+ path.append(candidates[i])
+ self.combinationSumHelper(candidates, target - candidates[i], i + 1, path, results)
+ path.pop()
+```
+### Go
+主要在于如何在回溯中去重
+
+**使用used数组**
+```go
+var (
+ res [][]int
+ path []int
+ used []bool
+)
+func combinationSum2(candidates []int, target int) [][]int {
+ res, path = make([][]int, 0), make([]int, 0, len(candidates))
+ used = make([]bool, len(candidates))
+ sort.Ints(candidates) // 排序,为剪枝做准备
+ dfs(candidates, 0, target)
+ return res
+}
+
+func dfs(candidates []int, start int, target int) {
+ if target == 0 { // target 不断减小,如果为0说明达到了目标值
+ tmp := make([]int, len(path))
+ copy(tmp, path)
+ res = append(res, tmp)
+ return
+ }
+ for i := start; i < len(candidates); i++ {
+ if candidates[i] > target { // 剪枝,提前返回
+ break
+ }
+ // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
+ // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
+ if i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false {
+ continue
+ }
+ path = append(path, candidates[i])
+ used[i] = true
+ dfs(candidates, i+1, target - candidates[i])
+ used[i] = false
+ path = path[:len(path) - 1]
+ }
+}
+```
+**不使用used数组**
+```go
+var (
+ res [][]int
+ path []int
+)
+func combinationSum2(candidates []int, target int) [][]int {
+ res, path = make([][]int, 0), make([]int, 0, len(candidates))
+ sort.Ints(candidates) // 排序,为剪枝做准备
+ dfs(candidates, 0, target)
+ return res
+}
+
+func dfs(candidates []int, start int, target int) {
+ if target == 0 { // target 不断减小,如果为0说明达到了目标值
+ tmp := make([]int, len(path))
+ copy(tmp, path)
+ res = append(res, tmp)
+ return
+ }
+ for i := start; i < len(candidates); i++ {
+ if candidates[i] > target { // 剪枝,提前返回
+ break
+ }
+ // i != start 限制了这不对深度遍历到达的此值去重
+ if i != start && candidates[i] == candidates[i-1] { // 去重
+ continue
+ }
+ path = append(path, candidates[i])
+ dfs(candidates, i+1, target - candidates[i])
+ path = path[:len(path) - 1]
+ }
+}
+```
+### JavaScript
+
+```js
+/**
+ * @param {number[]} candidates
+ * @param {number} target
+ * @return {number[][]}
+ */
+var combinationSum2 = function(candidates, target) {
+ const res = []; path = [], len = candidates.length;
+ candidates.sort((a,b)=>a-b);
+ backtracking(0, 0);
+ return res;
+ function backtracking(sum, i) {
+ if (sum === target) {
+ res.push(Array.from(path));
+ return;
+ }
+ for(let j = i; j < len; j++) {
+ const n = candidates[j];
+ if(j > i && candidates[j] === candidates[j-1]){
+ //若当前元素和前一个元素相等
+ //则本次循环结束,防止出现重复组合
+ continue;
+ }
+ //如果当前元素值大于目标值-总和的值
+ //由于数组已排序,那么该元素之后的元素必定不满足条件
+ //直接终止当前层的递归
+ if(n > target - sum) break;
+ path.push(n);
+ sum += n;
+ backtracking(sum, j + 1);
+ path.pop();
+ sum -= n;
+ }
+ }
+};
+```
+**使用used去重**
+
+```js
+var combinationSum2 = function(candidates, target) {
+ let res = [];
+ let path = [];
+ let total = 0;
+ const len = candidates.length;
+ candidates.sort((a, b) => a - b);
+ let used = new Array(len).fill(false);
+ const backtracking = (startIndex) => {
+ if (total === target) {
+ res.push([...path]);
+ return;
+ }
+ for(let i = startIndex; i < len && total < target; i++) {
+ const cur = candidates[i];
+ if (cur > target - total || (i > 0 && cur === candidates[i - 1] && !used[i - 1])) continue;
+ path.push(cur);
+ total += cur;
+ used[i] = true;
+ backtracking(i + 1);
+ path.pop();
+ total -= cur;
+ used[i] = false;
+ }
+ }
+ backtracking(0);
+ return res;
+};
+```
+
+### TypeScript
+
+```typescript
+function combinationSum2(candidates: number[], target: number): number[][] {
+ candidates.sort((a, b) => a - b);
+ const resArr: number[][] = [];
+ function backTracking(
+ candidates: number[], target: number,
+ curSum: number, startIndex: number, route: number[]
+ ) {
+ if (curSum > target) return;
+ if (curSum === target) {
+ resArr.push(route.slice());
+ return;
+ }
+ for (let i = startIndex, length = candidates.length; i < length; i++) {
+ if (i > startIndex && candidates[i] === candidates[i - 1]) {
+ continue;
+ }
+ let tempVal: number = candidates[i];
+ route.push(tempVal);
+ backTracking(candidates, target, curSum + tempVal, i + 1, route);
+ route.pop();
+
+ }
+ }
+ backTracking(candidates, target, 0, 0, []);
+ return resArr;
+};
+```
+
+### Rust
+
+```Rust
+impl Solution {
+ pub fn backtracking(result: &mut Vec
> result = new ArrayList<>();// 存放符合条件结果的集合
+ LinkedList
> permute(int[] nums) {
+ if (nums.length == 0){
+ return result;
+ }
+ used = new boolean[nums.length];
+ permuteHelper(nums);
+ return result;
+ }
+
+ private void permuteHelper(int[] nums){
+ if (path.size() == nums.length){
+ result.add(new ArrayList<>(path));
+ return;
+ }
+ for (int i = 0; i < nums.length; i++){
+ if (used[i]){
+ continue;
+ }
+ used[i] = true;
+ path.add(nums[i]);
+ permuteHelper(nums);
+ path.removeLast();
+ used[i] = false;
+ }
+ }
+}
+```
+
+```java
+// 解法2:通过判断path中是否存在数字,排除已经选择的数字
+class Solution {
+ List
> result = new ArrayList<>();
+ LinkedList
> permute(int[] nums) {
+ if (nums.length == 0) return result;
+ backtrack(nums, path);
+ return result;
+ }
+ public void backtrack(int[] nums, LinkedList