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 e5f4f66593..d9f060b7c2 100644 --- a/README.md +++ b/README.md @@ -1,451 +1,478 @@ -目录: -================= -* [算法面试思维导图](#算法面试思维导图) -* [算法文章精选](#算法文章精选) -* [LeetCode 刷题攻略](#LeetCode-刷题攻略) -* [算法模板](#算法模板) -* [LeetCode 最强题解](#LeetCode-最强题解) -* [关于作者](#关于作者) +# 代码随想录 · LeetCode-Master -# 算法面试思维导图 +
+ 🌍 海外英文版 · + 🌍🇸 英文仓库 · + 🇨🇳 国内在线阅读 · + 🇨 Gitee 同步 +
- + -# 算法文章精选 +> 一套 **循序渐进**、**少走弯路** 的刷题计划。 +> 题目已按知识脉络与难度 **排好顺序**,每题配 **图文题解 + 视频讲解**。 +> 适合从零到进阶、系统化掌握数据结构与算法。 + +--- + +## 🔗 快速入口 + +- 📘 **出版书籍**:[《代码随想录》](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) +- 📢 **转载须知**:全部为原创,引用请标注来源;恶意搬运将依法维权。 + +--- + +## 📚 为什么选这套刷题路线? + +- **不再海选题目**:README 就是刷题路线,**按顺序刷**即可。 +- **全链路学习体验**:每个专题含「理论基础 → 实战题目 → 总结复盘」。 +- **经典高频必会**:题目均为**高频面试题**与**典型考点**。 +- **多语言覆盖**:除 C++ 主线,还有社区贡献的多语言实现。 + +
+
+
+
+
-
-# 我的公众号
-
-更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:「简历模板」「java」「C++」「python」等关键字就可以获得我多年整理出来的学习资料。
-
-**每天8:35准时为你推送一篇经典面试题目,帮你梳理算法知识体系,轻松学习算法!**,并且公众号里有大量学习资源,也有我自己的学习心得和方法总结,更有很多志同道合的好伙伴在这里打卡学习,来看看就你知道了,相信一定会有所收获!
-
-
-
-
+ * [关于时间复杂度,你不知道的都在这里!](./problems/前序/时间复杂度.md)
+ * [O(n)的算法居然超时了,此时的n究竟是多大?](./problems/前序/算法超时.md)
+ * [通过一道面试题目,讲一讲递归算法的时间复杂度!](./problems/前序/递归算法的时间复杂度.md)
+ * [关于空间复杂度,可能有几个疑问?](./problems/前序/空间复杂度.md)
+ * [递归算法的时间与空间复杂度分析!](./problems/前序/递归算法的时间与空间复杂度分析.md)
+ * [刷了这么多题,你了解自己代码的内存消耗么?](./problems/前序/内存消耗.md)
+
+
+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)
+
+背包问题系列:
+
+
+
+
+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)
+
+打家劫舍系列:
+
+29. [动态规划:198.打家劫舍](./problems/0198.打家劫舍.md)
+30. [动态规划:213.打家劫舍II](./problems/0213.打家劫舍II.md)
+31. [动态规划:337.打家劫舍III](./problems/0337.打家劫舍III.md)
+
+股票系列:
+
+
+
+
+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)
+
+子序列系列:
+
+
+
+
+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)
+
+
+
+
图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。
回溯三部曲:
-* 确定回溯函数参数
+* 确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
-注意这个index可不是 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的startIndex了。
+注意这个index可不是 [77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)中的startIndex了。
这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
代码如下:
-```
+```cpp
vector
+
* fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
-
-
-* fast和slow同时移动,之道fast指向末尾,如题:
-
+
+* fast和slow同时移动,直到fast指向末尾,如题:
+
+//图片中有错别词:应该将“只到”改为“直到”
* 删除slow指向的下一个节点,如图:
-
+
此时不难写出如下C++代码:
-```
+```CPP
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
@@ -42,8 +78,402 @@ public:
fast = fast->next;
slow = slow->next;
}
- slow->next = slow->next->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://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)。
+对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)。
接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序**
-初始时,cur指向虚拟头结点,然后进行如下三步:
+初始时,cur指向虚拟头结点,然后进行如下三步:
-
+
操作之后,链表如下:
-
-
+
看这个可能就更直观一些了:
-
+
-对应的C++代码实现如下:
+对应的C++代码实现如下: (注释中详细和如上图中的三步做对应)
-```
+```CPP
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
- dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
+ dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next; // 记录临时节点
@@ -45,26 +61,467 @@ public:
cur = cur->next->next; // cur移动两位,准备下一轮交换
}
- return dummyHead->next;
+ ListNode* result = dummyHead->next;
+ delete dummyHead;
+ return result;
}
};
```
-时间复杂度:O(n)
-空间复杂度:O(1)
-## 拓展
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
+
+## 拓展
-**这里还是说一下,大家不必太在意leetcode上执行用时,打败多少多少用户,这个就是一个玩具,非常不准确。**
+**这里还是说一下,大家不必太在意力扣上执行用时,打败多少多少用户,这个统计不准确的。**
-做题的时候自己能分析出来时间复杂度就可以了,至于leetcode上执行用时,大概看一下就行。
+做题的时候自己能分析出来时间复杂度就可以了,至于力扣上执行用时,大概看一下就行。
上面的代码我第一次提交执行用时8ms,打败6.5%的用户,差点吓到我了。
-心想应该没有更好的方法了吧,也就O(n)的时间复杂度,重复提交几次,这样了:
+心想应该没有更好的方法了吧,也就 $O(n)$ 的时间复杂度,重复提交几次,这样了:
+
+
+
+力扣上的统计如果两份代码是 100ms 和 300ms的耗时,其实是需要注意的。
+
+如果一个是 4ms 一个是 12ms,看上去好像是一个打败了80%,一个打败了20%,其实是没有差别的。 只不过是力扣上统计的误差而已。
-
-所以,不必过于在意leetcode上这个统计。
+## 其他语言版本
+### 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
+
很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。
-# 暴力解法C++代码
+代码如下:
-```
+```CPP
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
@@ -57,7 +61,7 @@ public:
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
- i--; // 因为下表i以后的数值都向前移动了一位,所以i也向前移动一位
+ i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
@@ -67,50 +71,449 @@ public:
};
```
-# 双指针法
-
-双指针法(快慢指针法): **通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
+* 时间复杂度: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++代码:
-```
+```CPP
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector
-
-动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说道。
-
-可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,会发现不匹配,此时就要从头匹配了。
-
-但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。
-
-此时就要问了**前缀表是如何记录的呢?**
-
-首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,在重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
-
-那么什么是前缀表:**记录下表i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
-
-# 为什么一定要用前缀表
-
-这就是前缀表那为啥就能告诉我们 上次匹配的位置,并跳过去呢?
-
-回顾一下,刚刚匹配的过程在下表5的地方遇到不匹配,模式串是指向f,如图:
-
-
-
-然后就找到了下表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在字符串匹配中极大的提高的搜索的效率。**
-
-为了和[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)字符串命名统一,方便大家理解,以下文章统称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指向后缀终止位置(与j同理)。
-
-然后还要对next数组进行初始化赋值,如下:
-
-```
-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开始,代码如下:
-
-```
-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])。
-
-所以,处理前后缀不相同的情况代码如下:
-
-```
-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数组的函数代码如下:
-
-```
-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开始,遍历文本串,代码如下:
-
-```
-for (int i = 0; i < s.size(); i++)
-```
-
-接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 经行比较。
-
-如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。
-
-代码如下:
-
-```
-while(j >= 0 && s[i] != t[j + 1]) {
- j = next[j];
-}
-```
-
-如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:
-
-```
-if (s[i] == t[j + 1]) {
- j++; // i的增加在for循环里
-}
-```
-
-如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
-
-本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
-
-代码如下:
-
-```
-if (j == (t.size() - 1) ) {
- return (i - t.size() + 1);
-}
-```
-
-那么使用next数组,用模式串匹配文本串的整体代码如下:
-
-```
-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);
- }
-}
-```
-
-此时所有逻辑的代码都已经写出来了,本题整体代码如下:
-
-# 前缀表统一减一 C++代码实现
-
-```
-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;
- }
- int next[needle.size()];
- getNext(next, needle);
- int j = -1; // // 因为next数组里记录的起始位置为-1
- for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
- while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
- j = next[j]; // j 寻找之前匹配的位置
- }
- if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
- j++; // i的增加在for循环里
- }
- if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
- return (i - needle.size() + 1);
- }
- }
- return -1;
- }
-};
-
-```
-
-# 前缀表(不减一)C++实现
-
-那么前缀表就不减一了,也不右移的,到底行不行呢?行!
-
-我之前说过,这仅仅是KMP算法实现上的问题,如果就直接使用前缀表可以换一种回退方式,找j=next[j-1] 来进行回退。
-
-主要就是j=next[x]这一步最为关键!
-
-我给出的getNext的实现为:(前缀表统一减一)
-
-```
-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]
- }
-}
-
-```
-此时如果输入的模式串为aabaaf,对应的next为-1 0 -1 0 1 -1。
-
-这里j和next[0]初始化为-1,整个next数组是以 前缀表减一之后的效果来构建的。
-
-那么前缀表不减一来构建next数组,代码如下:
-
-```
- 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作为数组下表的操作
- j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
- }
- if (s[i] == s[j]) {
- j++;
- }
- next[i] = j;
- }
- }
-
-```
-
-此时如果输入的模式串为aabaaf,对应的next为 0 1 0 1 2 0,(其实这就是前缀表的数值了)。
-
-那么用这样的next数组也可以用来做匹配,代码要有所改动。
-
-实现代码如下:
-
-```
-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];
- }
- if (s[i] == s[j]) {
- j++;
- }
- next[i] = j;
- }
- }
- int strStr(string haystack, string needle) {
- if (needle.size() == 0) {
- return 0;
- }
- 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];
- }
- if (haystack[i] == needle[j]) {
- j++;
- }
- if (j == needle.size() ) {
- return (i - needle.size() + 1);
- }
- }
- return -1;
- }
-};
-```
-
-# 总结
-
-我们介绍了什么是KMP,KMP可以解决什么问题,然后分析KMP算法里的next数组,知道了next数组就是前缀表,再分析为什么要是前缀表而不是什么其他表。
-
-接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不同意减一得到的next数组仅仅是kmp的实现方式的不同。
-
-其中还分析了KMP算法的时间复杂度,并且和暴力方法做了对比。
-
-然后先用前缀表统一减一得到的next数组,求得文本串s里是否出现过模式串t,并给出了具体分析代码。
-
-又给出了直接用前缀表作为next数组,来做匹配的实现代码。
-
-可以说把KMP的每一个细微的细节都扣了出来,毫无遮掩的展示给大家了!
-
-
-
-
-> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
-
diff --git "a/problems/0028.\345\256\236\347\216\260strStr.md" "b/problems/0028.\345\256\236\347\216\260strStr.md"
new file mode 100755
index 0000000000..ef8a6c58e6
--- /dev/null
+++ "b/problems/0028.\345\256\236\347\216\260strStr.md"
@@ -0,0 +1,1520 @@
+* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
+* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
+* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
+
+
+> 在一个串中查找是否出现过另一个串,这是KMP的看家本领。
+
+# 28. 实现 strStr()
+
+[力扣题目链接](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/)
+
+实现 strStr() 函数。
+
+给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
+
+示例 1:
+输入: haystack = "hello", needle = "ll"
+输出: 2
+
+示例 2:
+输入: haystack = "aaaaa", needle = "bba"
+输出: -1
+
+说明:
+当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
+对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
+
+## 算法公开课
+
+本题是KMP 经典题目。以下文字如果看不进去,可以看[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html),相信结合视频再看本篇题解,更有助于大家对本题的理解。
+
+* [帮你把KMP算法学个通透!B站(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd/)
+* [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx)
+
+
+## 思路
+
+KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。**
+
+本篇将以如下顺序来讲解KMP,
+
+
+* 什么是KMP
+* KMP有什么用
+* 什么是前缀表
+* 为什么一定要用前缀表
+* 如何计算前缀表
+* 前缀表与next数组
+* 使用next数组来匹配
+* 时间复杂度分析
+* 构造next数组
+* 使用next数组来做匹配
+* 前缀表统一减一 C++代码实现
+* 前缀表(不减一)C++实现
+* 总结
+
+
+读完本篇可以顺便把leetcode上28.实现strStr()题目做了。
+
+
+### 什么是KMP
+
+说到KMP,先说一下KMP这个名字是怎么来的,为什么叫做KMP呢。
+
+因为是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP
+
+### KMP有什么用
+
+KMP主要应用在字符串匹配上。
+
+KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。**
+
+所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。
+
+其实KMP的代码不好理解,一些同学甚至直接把KMP代码的模板背下来。
+
+没有彻底搞懂,懵懵懂懂就把代码背下来太容易忘了。
+
+不仅面试的时候可能写不出来,如果面试官问:**next数组里的数字表示的是什么,为什么这么表示?**
+
+估计大多数候选人都是懵逼的。
+
+下面Carl就带大家把KMP的精髓,next数组弄清楚。
+
+### 什么是前缀表
+
+写过KMP的同学,一定都写过next数组,那么这个next数组究竟是个啥呢?
+
+next数组就是一个前缀表(prefix table)。
+
+前缀表有什么作用呢?
+
+**前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。**
+
+为了清楚地了解前缀表的来历,我们来举一个例子:
+
+要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
+
+请记住文本串和模式串的作用,对于理解下文很重要,要不然容易看懵。所以说三遍:
+
+要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
+
+要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
+
+要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
+
+如动画所示:
+
+
+
+动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说到。
+
+可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。
+
+但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。
+
+此时就要问了**前缀表是如何记录的呢?**
+
+首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
+
+那么什么是前缀表:**记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。**
+
+### 最长公共前后缀
+
+文章中字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**。
+
+**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。
+
+**正确理解什么是前缀什么是后缀很重要**!
+
+那么网上清一色都说 “kmp 最长公共前后缀” 又是什么回事呢?
+
+我查了一遍 算法导论 和 算法4里KMP的章节,都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 更准确一些。
+
+**因为前缀表要求的就是相同前后缀的长度。**
+
+而最长公共前后缀里面的“公共”,更像是说前缀和后缀公共的长度。这其实并不是前缀表所需要的。
+
+所以字符串a的最长相等前后缀为0。
+字符串aa的最长相等前后缀为1。
+字符串aaa的最长相等前后缀为2。
+等等.....。
+
+
+### 为什么一定要用前缀表
+
+这就是前缀表,那为啥就能告诉我们 上次匹配的位置,并跳过去呢?
+
+回顾一下,刚刚匹配的过程在下标5的地方遇到不匹配,模式串是指向f,如图:
+
+
+
+然后就找到了下标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
+
* 目标值在数组所有元素之前
* 目标值等于数组中某一个元素
@@ -43,65 +52,66 @@ https://leetcode-cn.com/problems/search-insert-position/
接下来我将从暴力的解法和二分法来讲解此题,也借此好好讲一讲二分查找法。
-## 暴力解法
+### 暴力解法
暴力解题 不一定时间消耗就非常高,关键看实现的方式,就像是二分查找时间消耗不一定就很低,是一样的。
-## 暴力解法C++代码
+C++代码
-```
+```CPP
class Solution {
public:
int searchInsert(vector
+
-## 二分法
+### 二分法
-既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
+既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
-
+
+
大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。
以后大家**只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。**
-同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下表可能不是唯一的。
+同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
大体讲解一下二分法的思路,这里来举一个例子,例如在这个数组中,使用二分法寻找元素为5的位置,并返回其下标。
-
+
二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。
相信很多同学对二分查找法中边界条件处理不好。
-例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢?
+例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢?
-这里弄不清楚主要是因为**对区间的定义没有想清楚,这就是不变量**。
+这里弄不清楚主要是因为**对区间的定义没有想清楚,这就是不变量**。
要在二分查找的过程中,保持不变量,这也就是**循环不变量** (感兴趣的同学可以查一查)。
-## 二分法第一种写法
+### 二分法第一种写法
以这道题目来举例,以下的代码中定义 target 是在一个在左闭右闭的区间里,**也就是[left, right] (这个很重要)**。
@@ -109,13 +119,13 @@ public:
**大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1**。
-```
+```CPP
class Solution {
public:
int searchInsert(vector
+
-## 二分法第二种写法
+### 二分法第二种写法
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) 。
@@ -151,7 +162,7 @@ public:
**大家要仔细看注释,思考为什么要写while (left < right), 为什么要写right = middle**。
-```
+```CPP
class Solution {
public:
int searchInsert(vector
+一个数独。
-可以看出我们需要的是一个二维的递归(也就是两个for循环,递归遍历每一个棋盘的位置)
+
-这道题目和之前递归的方式都不一样,这里相当于两层递归,之前的都是一层递归。
+答案被标成红色。
-# C++代码
+提示:
+* 给定的数独序列只包含数字 1-9 和字符 '.' 。
+* 你可以假设给定的数独只有唯一解。
+* 给定数独永远是 9x9 形式的。
+
+## 算法公开课
+
+**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[回溯算法二维递归?解数独不过如此!| LeetCode:37. 解数独](https://www.bilibili.com/video/BV1TW4y1471V/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
+
+## 思路
+
+棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是**二维递归**。
+
+怎么做二维递归呢?
+
+大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html),[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html),[78.子集(子集问题)](https://programmercarl.com/0078.子集.html),[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后(N皇后问题)](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。
+
+**如果以上这几道题目没有做过的话,不建议上来就做这道题哈!**
+
+[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。
+
+本题就不一样了,**本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深**。
+
+因为这个树形结构太大了,我抽取一部分,如图所示:
+
+
+
+
+### 回溯三部曲
+
+* 递归函数以及参数
+
+**递归函数的返回值需要是bool类型,为什么呢?**
+
+因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
+
+代码如下:
+
+```cpp
+bool backtracking(vector
-
+
注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
-而在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) 中都可以知道要递归K层,因为要取k个元素的组合。
+而在[77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层,因为要取k个元素的组合。
-## 回溯三部曲
+### 回溯三部曲
-* 递归函数参数
+* 递归函数参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)
@@ -57,31 +67,31 @@ candidates 中的数字可以无限制重复被选取。
**本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?**
-我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。
+我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[77.组合](https://programmercarl.com/0077.组合.html),[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)。
-如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
+如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[17.电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
-**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍**。
+**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我在讲解排列的时候会重点介绍**。
代码如下:
-```
+```CPP
vector
+
从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
sum等于target的时候,需要收集结果,代码如下:
-```
+```CPP
if (sum > target) {
return;
}
@@ -91,15 +101,15 @@ if (sum == target) {
}
```
-* 单层搜索的逻辑
+* 单层搜索的逻辑
单层for循环依然是从startIndex开始,搜索candidates集合。
-**注意本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的一个区别是:本题元素为可重复选取的**。
+**注意本题和[77.组合](https://programmercarl.com/0077.组合.html)、[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的一个区别是:本题元素为可重复选取的**。
如何重复选取呢,看代码,注释部分:
-```
+```CPP
for (int i = startIndex; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
@@ -109,9 +119,9 @@ for (int i = startIndex; i < candidates.size(); i++) {
}
```
-按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的模板,不难写出如下C++完整代码:
+按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的模板,不难写出如下C++完整代码:
-```
+```CPP
// 版本一
class Solution {
private:
@@ -144,11 +154,11 @@ public:
};
```
-## 剪枝优化
+### 剪枝优化
在这个树形结构中:
-
+
以及上面的版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。
@@ -160,18 +170,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++)
```
整体代码如下:(注意注释的部分)
-```
+```CPP
class Solution {
private:
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
+
可以看到图中,每个节点相对于 [39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组,这个used数组下面会重点介绍。
-## 回溯三部曲
+### 回溯三部曲
-* **递归函数参数**
+* **递归函数参数**
-与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
+与[39.组合总和](https://programmercarl.com/0039.组合总和.html)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
代码如下:
-```
+```CPP
vector
+
我在图中将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]使用过
+可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。
+
+而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:
+
+
+
+
**这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!**
+
那么单层搜索的逻辑代码如下:
-```
+```CPP
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
- // used[i - 1] == true,说明同一树支candidates[i - 1]使用过
+ // 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) {
@@ -143,11 +165,10 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
**注意sum + candidates[i] <= target为剪枝操作,在[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)有讲解过!**
-## C++代码
回溯三部曲分析完了,整体C++代码如下:
-```
+```CPP
class Solution {
private:
vector> 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
+按照行来计算如图:
+
-
+按照列来计算如图:
+
-一些同学在实现暴力解法的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
+一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。
@@ -29,7 +61,7 @@
这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:
-
+
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
@@ -43,10 +75,11 @@
此时求出了列4的雨水体积。
-一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
+一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
-```
+
+```CPP
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue;
@@ -55,7 +88,7 @@ for (int i = 0; i < height.size(); i++) {
在for循环中求左右两边最高柱子,代码如下:
-```
+```CPP
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
@@ -68,14 +101,14 @@ for (int l = i - 1; l >= 0; l--) {
最后,计算该列的雨水高度,代码如下:
-```
+```CPP
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中
```
整体代码如下:
-```
+```CPP
class Solution {
public:
int trap(vector
+
知道这一点,后面的就可以理解了。
2. 使用单调栈内元素的顺序
-从大到小还是从小打到呢?
+从大到小还是从小到大呢?
-要从栈底到栈头(元素从栈头弹出)是从大到小的顺序。
+从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
如图:
-
+
+关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。
-3. 遇到相同高度的柱子怎么办。
+3. 遇到相同高度的柱子怎么办。
-遇到相同的元素,更新栈内下表,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
+遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
-因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
+**因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度**。
如图所示:
-
-
-
+
4. 栈里要保存什么数值
-是用单调栈,其实是通过 长 * 宽 来计算雨水面积的。
+使用单调栈,也是通过 长 * 宽 来计算雨水面积的。
-长就是通过柱子的高度来计算,宽是通过柱子之间的下表来计算,
+长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,
-那么栈里有没有必要存一个pair
+
-取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下表记为mid,对应的高度为height[mid](就是图中的高度1)。
+取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid](就是图中的高度1)。
-栈顶元素st.top(),就是凹槽的左边位置,下表为st.top(),对应的高度为height[st.top()](就是图中的高度2)。
+此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()](就是图中的高度2)。
-当前遍历的元素i,就是凹槽右边的位置,下表为i,对应的高度为height[i](就是图中的高度3)。
+当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i](就是图中的高度3)。
-那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
+此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!**
-雨水的宽度是 凹槽右边的下表 - 凹槽左边的下表 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;`
+那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
+
+雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;`
当前凹槽雨水的体积就是:`h * w`。
求当前凹槽雨水的体积代码如下:
-```
+```CPP
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续跟新栈顶元素
int mid = st.top();
st.pop();
@@ -278,7 +315,7 @@ while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,
关键部分讲完了,整体代码如下:
-```
+```CPP
class Solution {
public:
int trap(vector
+
+
+**图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)**
### 方法一
-这里还是有个特殊情况需要考虑,如果当前可移动距离的终点就是是集合终点,那么就不用增加步数了,因为不能再往后走了。
+从图中可以看出来,就是移动下标达到了当前覆盖的最远距离下标时,步数就要加一,来增加覆盖距离。最后的步数就是最少步数。
-详情可看代码(详细注释)
+这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
-```
+- 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
+- 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
+
+C++代码如下:(详细注释)
+
+```CPP
// 版本一
class Solution {
public:
int jump(vector> 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