Showing preview only (3,572K chars total). Download the full file or copy to clipboard to get everything.
Repository: youngyangyang04/leetcode-master
Branch: master
Commit: 4a463af36eb8
Files: 317
Total size: 3.4 MB
Directory structure:
gitextract_fqpf49kp/
├── .gitignore
├── README.md
└── problems/
├── 0001.两数之和.md
├── 0005.最长回文子串.md
├── 0015.三数之和.md
├── 0017.电话号码的字母组合.md
├── 0018.四数之和.md
├── 0019.删除链表的倒数第N个节点.md
├── 0020.有效的括号.md
├── 0024.两两交换链表中的节点.md
├── 0027.移除元素.md
├── 0028.实现strStr.md
├── 0031.下一个排列.md
├── 0034.在排序数组中查找元素的第一个和最后一个位置.md
├── 0035.搜索插入位置.md
├── 0037.解数独.md
├── 0039.组合总和.md
├── 0040.组合总和II.md
├── 0042.接雨水.md
├── 0045.跳跃游戏II.md
├── 0046.全排列.md
├── 0047.全排列II.md
├── 0051.N皇后.md
├── 0052.N皇后II.md
├── 0053.最大子序和.md
├── 0053.最大子序和(动态规划).md
├── 0054.螺旋矩阵.md
├── 0055.跳跃游戏.md
├── 0056.合并区间.md
├── 0059.螺旋矩阵II.md
├── 0062.不同路径.md
├── 0063.不同路径II.md
├── 0070.爬楼梯.md
├── 0070.爬楼梯完全背包版本.md
├── 0072.编辑距离.md
├── 0077.组合.md
├── 0077.组合优化.md
├── 0078.子集.md
├── 0084.柱状图中最大的矩形.md
├── 0090.子集II.md
├── 0093.复原IP地址.md
├── 0096.不同的二叉搜索树.md
├── 0098.验证二叉搜索树.md
├── 0100.相同的树.md
├── 0101.对称二叉树.md
├── 0102.二叉树的层序遍历.md
├── 0104.二叉树的最大深度.md
├── 0106.从中序与后序遍历序列构造二叉树.md
├── 0108.将有序数组转换为二叉搜索树.md
├── 0110.平衡二叉树.md
├── 0111.二叉树的最小深度.md
├── 0112.路径总和.md
├── 0115.不同的子序列.md
├── 0116.填充每个节点的下一个右侧节点指针.md
├── 0121.买卖股票的最佳时机.md
├── 0122.买卖股票的最佳时机II.md
├── 0122.买卖股票的最佳时机II(动态规划).md
├── 0123.买卖股票的最佳时机III.md
├── 0127.单词接龙.md
├── 0129.求根到叶子节点数字之和.md
├── 0130.被围绕的区域.md
├── 0131.分割回文串.md
├── 0132.分割回文串II.md
├── 0134.加油站.md
├── 0135.分发糖果.md
├── 0139.单词拆分.md
├── 0141.环形链表.md
├── 0142.环形链表II.md
├── 0143.重排链表.md
├── 0150.逆波兰表达式求值.md
├── 0151.翻转字符串里的单词.md
├── 0160.相交链表.md
├── 0188.买卖股票的最佳时机IV.md
├── 0189.旋转数组.md
├── 0198.打家劫舍.md
├── 0200.岛屿数量.广搜版.md
├── 0200.岛屿数量.深搜版.md
├── 0202.快乐数.md
├── 0203.移除链表元素.md
├── 0205.同构字符串.md
├── 0206.翻转链表.md
├── 0207.课程表.md
├── 0209.长度最小的子数组.md
├── 0210.课程表II.md
├── 0213.打家劫舍II.md
├── 0216.组合总和III.md
├── 0222.完全二叉树的节点个数.md
├── 0225.用队列实现栈.md
├── 0226.翻转二叉树.md
├── 0232.用栈实现队列.md
├── 0234.回文链表.md
├── 0235.二叉搜索树的最近公共祖先.md
├── 0236.二叉树的最近公共祖先.md
├── 0239.滑动窗口最大值.md
├── 0242.有效的字母异位词.md
├── 0257.二叉树的所有路径.md
├── 0279.完全平方数.md
├── 0283.移动零.md
├── 0300.最长上升子序列.md
├── 0309.最佳买卖股票时机含冷冻期.md
├── 0322.零钱兑换.md
├── 0332.重新安排行程.md
├── 0337.打家劫舍III.md
├── 0343.整数拆分.md
├── 0344.反转字符串.md
├── 0347.前K个高频元素.md
├── 0349.两个数组的交集.md
├── 0376.摆动序列.md
├── 0377.组合总和Ⅳ.md
├── 0383.赎金信.md
├── 0392.判断子序列.md
├── 0404.左叶子之和.md
├── 0406.根据身高重建队列.md
├── 0416.分割等和子集.md
├── 0417.太平洋大西洋水流问题.md
├── 0435.无重叠区间.md
├── 0450.删除二叉搜索树中的节点.md
├── 0452.用最少数量的箭引爆气球.md
├── 0454.四数相加II.md
├── 0455.分发饼干.md
├── 0459.重复的子字符串.md
├── 0463.岛屿的周长.md
├── 0474.一和零.md
├── 0491.递增子序列.md
├── 0494.目标和.md
├── 0496.下一个更大元素I.md
├── 0501.二叉搜索树中的众数.md
├── 0503.下一个更大元素II.md
├── 0509.斐波那契数.md
├── 0513.找树左下角的值.md
├── 0516.最长回文子序列.md
├── 0518.零钱兑换II.md
├── 0530.二叉搜索树的最小绝对差.md
├── 0538.把二叉搜索树转换为累加树.md
├── 0541.反转字符串II.md
├── 0583.两个字符串的删除操作.md
├── 0617.合并二叉树.md
├── 0647.回文子串.md
├── 0649.Dota2参议院.md
├── 0654.最大二叉树.md
├── 0657.机器人能否返回原点.md
├── 0669.修剪二叉搜索树.md
├── 0673.最长递增子序列的个数.md
├── 0674.最长连续递增序列.md
├── 0684.冗余连接.md
├── 0685.冗余连接II.md
├── 0695.岛屿的最大面积.md
├── 0700.二叉搜索树中的搜索.md
├── 0701.二叉搜索树中的插入操作.md
├── 0704.二分查找.md
├── 0707.设计链表.md
├── 0714.买卖股票的最佳时机含手续费.md
├── 0714.买卖股票的最佳时机含手续费(动态规划).md
├── 0718.最长重复子数组.md
├── 0724.寻找数组的中心索引.md
├── 0738.单调递增的数字.md
├── 0739.每日温度.md
├── 0743.网络延迟时间.md
├── 0746.使用最小花费爬楼梯.md
├── 0763.划分字母区间.md
├── 0787.K站中转内最便宜的航班.md
├── 0797.所有可能的路径.md
├── 0827.最大人工岛.md
├── 0841.钥匙和房间.md
├── 0844.比较含退格的字符串.md
├── 0860.柠檬水找零.md
├── 0922.按奇偶排序数组II.md
├── 0925.长按键入.md
├── 0941.有效的山脉数组.md
├── 0968.监控二叉树.md
├── 0977.有序数组的平方.md
├── 1002.查找常用字符.md
├── 1005.K次取反后最大化的数组和.md
├── 1020.飞地的数量.md
├── 1035.不相交的线.md
├── 1047.删除字符串中的所有相邻重复项.md
├── 1049.最后一块石头的重量II.md
├── 1143.最长公共子序列.md
├── 1207.独一无二的出现次数.md
├── 1221.分割平衡字符串.md
├── 1254.统计封闭岛屿的数目.md
├── 1334.阈值距离内邻居最少的城市.md
├── 1356.根据数字二进制下1的数目排序.md
├── 1365.有多少小于当前数字的数字.md
├── 1382.将二叉搜索树变平衡.md
├── 1791.找出星型图的中心节点.md
├── 1971.寻找图中是否存在路径.md
├── O(n)的算法居然超时了,此时的n究竟是多大?.md
├── images/
│ └── test
├── kamacoder/
│ ├── 0044.开发商购买土地.md
│ ├── 0047.参会dijkstra堆.md
│ ├── 0047.参会dijkstra朴素.md
│ ├── 0053.寻宝-Kruskal.md
│ ├── 0053.寻宝-prim.md
│ ├── 0054.替换数字.md
│ ├── 0055.右旋字符串.md
│ ├── 0058.区间和.md
│ ├── 0094.城市间货物运输I-SPFA.md
│ ├── 0094.城市间货物运输I.md
│ ├── 0095.城市间货物运输II.md
│ ├── 0096.城市间货物运输III.md
│ ├── 0097.小明逛公园.md
│ ├── 0098.所有可达路径.md
│ ├── 0099.岛屿的数量广搜.md
│ ├── 0099.岛屿的数量深搜.md
│ ├── 0100.岛屿的最大面积.md
│ ├── 0101.孤岛的总面积.md
│ ├── 0102.沉没孤岛.md
│ ├── 0103.水流问题.md
│ ├── 0104.建造最大岛屿.md
│ ├── 0105.有向图的完全可达性.md
│ ├── 0106.岛屿的周长.md
│ ├── 0107.寻找存在的路径.md
│ ├── 0108.冗余连接.md
│ ├── 0109.冗余连接II.md
│ ├── 0110.字符串接龙.md
│ ├── 0117.软件构建.md
│ ├── 0126.骑士的攻击astar.md
│ ├── 图论为什么用ACM模式.md
│ ├── 图论并查集理论基础.md
│ ├── 图论广搜理论基础.md
│ ├── 图论总结篇.md
│ ├── 图论深搜理论基础.md
│ ├── 图论理论基础.md
│ └── 最短路问题总结篇.md
├── qita/
│ ├── acm.md
│ ├── acm_backup.md
│ ├── algo_pdf.md
│ ├── ewaishuoming.md
│ ├── gitserver.md
│ ├── gongkaike.md
│ ├── join.md
│ ├── language.md
│ ├── publish.md
│ ├── say_feel.md
│ ├── server.md
│ ├── tulunfabu.md
│ ├── tulunshuoming.md
│ └── update.md
├── toolgithub.sh
├── 为了绝杀编辑距离,卡尔做了三步铺垫.md
├── 二叉树中递归带着回溯.md
├── 二叉树总结篇.md
├── 二叉树理论基础.md
├── 二叉树的统一迭代法.md
├── 二叉树的迭代遍历.md
├── 二叉树的递归遍历.md
├── 前序/
│ ├── ACM模式.md
│ ├── ACM模式如何构建二叉树.md
│ ├── BAT级别技术面试流程和注意事项都在这里了.md
│ ├── Java处理输入输出.md
│ ├── gitserver.md
│ ├── kvstore.md
│ ├── server.md
│ ├── vim.md
│ ├── 代码风格.md
│ ├── 内存消耗.md
│ ├── 刷力扣用不用库函数.md
│ ├── 力扣上的代码在本地编译运行.md
│ ├── 时间复杂度.md
│ ├── 程序员写文档工具.md
│ ├── 程序员简历.md
│ ├── 空间复杂度.md
│ ├── 算法超时.md
│ ├── 编程素养部分的吹毛求疵.md
│ ├── 递归算法的时间与空间复杂度分析.md
│ └── 递归算法的时间复杂度.md
├── 剑指Offer05.替换空格.md
├── 剑指Offer58-II.左旋转字符串.md
├── 动态规划-股票问题总结篇.md
├── 动态规划总结篇.md
├── 动态规划理论基础.md
├── 双指针总结.md
├── 周总结/
│ ├── 20200927二叉树周末总结.md
│ ├── 20201003二叉树周末总结.md
│ ├── 20201010二叉树周末总结.md
│ ├── 20201017二叉树周末总结.md
│ ├── 20201030回溯周末总结.md
│ ├── 20201107回溯周末总结.md
│ ├── 20201112回溯周末总结.md
│ ├── 20201126贪心周末总结.md
│ ├── 20201203贪心周末总结.md
│ ├── 20201210复杂度分析周末总结.md
│ ├── 20201217贪心周末总结.md
│ ├── 20201224贪心周末总结.md
│ ├── 20210107动规周末总结.md
│ ├── 20210114动规周末总结.md
│ ├── 20210121动规周末总结.md
│ ├── 20210128动规周末总结.md
│ ├── 20210204动规周末总结.md
│ ├── 20210225动规周末总结.md
│ ├── 20210304动规周末总结.md
│ └── 二叉树阶段总结系列一.md
├── 哈希表总结.md
├── 哈希表理论基础.md
├── 回溯总结.md
├── 回溯算法去重问题的另一种写法.md
├── 回溯算法理论基础.md
├── 字符串总结.md
├── 数组总结篇.md
├── 数组理论基础.md
├── 栈与队列总结.md
├── 栈与队列理论基础.md
├── 根据身高重建队列(vector原理讲解).md
├── 算法模板.md
├── 背包总结篇.md
├── 背包理论基础01背包-1.md
├── 背包理论基础01背包-2.md
├── 背包问题完全背包一维.md
├── 背包问题理论基础多重背包.md
├── 背包问题理论基础完全背包.md
├── 贪心算法总结篇.md
├── 贪心算法理论基础.md
├── 递归算法的时间与空间复杂度分析.md
├── 链表总结篇.md
├── 链表理论基础.md
└── 面试题02.07.链表相交.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea/
.DS_Store
.vscode
.temp
.cache
*.iml
__pycache__
================================================
FILE: README.md
================================================
# 代码随想录 · LeetCode-Master
<p align="center">
<a href="https://keetcoder.com/">🌍 海外英文版</a> ·
<a href="https://github.com/youngyangyang04/keetcoder">🌍🇸 英文仓库</a> ·
<a href="https://programmercarl.com/">🇨🇳 国内在线阅读</a> ·
<a href="https://gitee.com/programmercarl/leetcode-master">🇨 Gitee 同步</a>
</p>
<p align="center">
<a href="https://github.com/youngyangyang04/leetcode-master/stargazers"><img alt="stars" src="https://img.shields.io/github/stars/youngyangyang04/leetcode-master?style=flat&label=Stars"></a>
<a href="https://github.com/youngyangyang04/leetcode-master/network/members"><img alt="forks" src="https://img.shields.io/github/forks/youngyangyang04/leetcode-master?style=flat&label=Forks"></a>
<a href="https://github.com/youngyangyang04/leetcode-master/issues"><img alt="issues" src="https://img.shields.io/github/issues/youngyangyang04/leetcode-master?style=flat&label=Issues"></a>
<a href="https://github.com/youngyangyang04/leetcode-master/graphs/contributors"><img alt="contributors" src="https://img.shields.io/github/contributors/youngyangyang04/leetcode-master?style=flat&label=Contributors"></a>
</p>
> 一套 **循序渐进**、**少走弯路** 的刷题计划。
> 题目已按知识脉络与难度 **排好顺序**,每题配 **图文题解 + 视频讲解**。
> 适合从零到进阶、系统化掌握数据结构与算法。
---
## 🔗 快速入口
- 📘 **出版书籍**:[《代码随想录》](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++ 主线,还有社区贡献的多语言实现。
<p align="center">
<a href="https://programmercarl.com/xunlian/damoxing.html" target="_blank">
<img src="./pics/damoxing.jpg" width="800" />
</a>
</p>
---
## 🚀 如何使用本攻略
1. **从头开始**:按模块顺序「数组 → 链表 → 哈希表 → … → 图论」。
2. **带着问题学**:每个模块先看「理论基础」,再刷对应题单。
3. **及时复盘**:刷完一个模块,阅读「总结篇」,形成**知识闭环**。
4. **语言不设限**:题解以 C++ 讲解为主,配多语言代码,思路通用。
> **建议**:新手先刷「数组/链表/哈希/字符串」,再进阶到「二叉树/回溯/贪心/动态规划/图论」。
---
## 🧭 刷题总目录(可折叠)
> 已根据学习曲线优化排序;下方仅展示每章前若干题目,完整清单请展开查看。
<details>
<summary><b>前序 · 打基础</b></summary>
* [做项目(多个C++、Java、Go、前端、测开项目)](https://programmercarl.com/other/kstar.html)
* 编程语言
* [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP)
* [编程语言基础课](https://kamacoder.com/courseshop.php)
* [23种设计模式](https://github.com/youngyangyang04/kama-DesignPattern)
* [大厂算法笔试题](https://kamacoder.com/company.php)
* 工具
* [一站式vim配置](https://github.com/youngyangyang04/PowerVim)
* [保姆级Git入门教程,万字详解](https://mp.weixin.qq.com/s/Q_O0ey4C9tryPZaZeJocbA)
* [程序员应该用什么用具来写文档?](./problems/前序/程序员写文档工具.md)
* 求职
* [ACM模式练习网站,卡码网](https://kamacoder.com/)
* [程序员的简历应该这么写!!(附简历模板)](./problems/前序/程序员简历.md)
* [【专业技能】应该这样写!](https://programmercarl.com/other/jianlizhuanye.html)
* [【项目经历】应该这样写!](https://programmercarl.com/other/jianlixiangmu.html)
* [BAT级别技术面试流程和注意事项都在这里了](./problems/前序/BAT级别技术面试流程和注意事项都在这里了.md)
* 算法性能分析
* [关于时间复杂度,你不知道的都在这里!](./problems/前序/时间复杂度.md)
* [O(n)的算法居然超时了,此时的n究竟是多大?](./problems/前序/算法超时.md)
* [通过一道面试题目,讲一讲递归算法的时间复杂度!](./problems/前序/递归算法的时间复杂度.md)
* [关于空间复杂度,可能有几个疑问?](./problems/前序/空间复杂度.md)
* [递归算法的时间与空间复杂度分析!](./problems/前序/递归算法的时间与空间复杂度分析.md)
* [刷了这么多题,你了解自己代码的内存消耗么?](./problems/前序/内存消耗.md)
</details>
<details>
<summary><b>数组</b></summary>
1. [数组过于简单,但你该了解这些!](./problems/数组理论基础.md)
2. [数组:704.二分查找](./problems/0704.二分查找.md)
3. [数组:27.移除元素](./problems/0027.移除元素.md)
4. [数组:977.有序数组的平方](./problems/0977.有序数组的平方.md)
5. [数组:209.长度最小的子数组](./problems/0209.长度最小的子数组.md)
6. [数组:区间和](./problems/kamacoder/0058.区间和.md)
7. [数组:开发商购买土地](./problems/kamacoder/0044.开发商购买土地.md)
8. [数组:59.螺旋矩阵II](./problems/0059.螺旋矩阵II.md)
9. [数组:总结篇](./problems/数组总结篇.md)
</details>
<details>
<summary><b>链表</b></summary>
1. [关于链表,你该了解这些!](./problems/链表理论基础.md)
2. [链表:203.移除链表元素](./problems/0203.移除链表元素.md)
3. [链表:707.设计链表](./problems/0707.设计链表.md)
4. [链表:206.翻转链表](./problems/0206.翻转链表.md)
5. [链表:24.两两交换链表中的节点](./problems/0024.两两交换链表中的节点.md)
6. [链表:19.删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md)
7. [链表:链表相交](./problems/面试题02.07.链表相交.md)
8. [链表:142.环形链表](./problems/0142.环形链表II.md)
9. [链表:总结篇!](./problems/链表总结篇.md)
</details>
<details>
<summary><b>哈希表</b></summary>
1. [关于哈希表,你该了解这些!](./problems/哈希表理论基础.md)
2. [哈希表:242.有效的字母异位词](./problems/0242.有效的字母异位词.md)
3. [哈希表:1002.查找常用字符](./problems/1002.查找常用字符.md)
4. [哈希表:349.两个数组的交集](./problems/0349.两个数组的交集.md)
5. [哈希表:202.快乐数](./problems/0202.快乐数.md)
6. [哈希表:1.两数之和](./problems/0001.两数之和.md)
7. [哈希表:454.四数相加II](./problems/0454.四数相加II.md)
8. [哈希表:383.赎金信](./problems/0383.赎金信.md)
9. [哈希表:15.三数之和](./problems/0015.三数之和.md)
10. [双指针法:18.四数之和](./problems/0018.四数之和.md)
11. [哈希表:总结篇!](./problems/哈希表总结.md)
</details>
<details>
<summary><b>字符串</b></summary>
1. [字符串:344.反转字符串](./problems/0344.反转字符串.md)
2. [字符串:541.反转字符串II](./problems/0541.反转字符串II.md)
3. [字符串:替换数字](./problems/kamacoder/0054.替换数字.md)
4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md)
5. [字符串:右旋字符串](./problems/kamacoder/0055.右旋字符串.md)
6. [帮你把KMP算法学个通透](./problems/0028.实现strStr.md)
8. [字符串:459.重复的子字符串](./problems/0459.重复的子字符串.md)
9. [字符串:总结篇!](./problems/字符串总结.md)
</details>
<details>
<summary><b>双指针法</b></summary>
双指针法基本都是应用在数组,字符串与链表的题目上
1. [数组:27.移除元素](./problems/0027.移除元素.md)
2. [字符串:344.反转字符串](./problems/0344.反转字符串.md)
3. [字符串:替换数字](./problems/kamacoder/0054.替换数字.md)
4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md)
5. [链表:206.翻转链表](./problems/0206.翻转链表.md)
6. [链表:19.删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md)
7. [链表:链表相交](./problems/面试题02.07.链表相交.md)
8. [链表:142.环形链表](./problems/0142.环形链表II.md)
9. [双指针:15.三数之和](./problems/0015.三数之和.md)
10. [双指针:18.四数之和](./problems/0018.四数之和.md)
11. [双指针:总结篇!](./problems/双指针总结.md)
</details>
<details>
<summary><b>栈与队列</b></summary>
1. [栈与队列:理论基础](./problems/栈与队列理论基础.md)
2. [栈与队列:232.用栈实现队列](./problems/0232.用栈实现队列.md)
3. [栈与队列:225.用队列实现栈](./problems/0225.用队列实现栈.md)
4. [栈与队列:20.有效的括号](./problems/0020.有效的括号.md)
5. [栈与队列:1047.删除字符串中的所有相邻重复项](./problems/1047.删除字符串中的所有相邻重复项.md)
6. [栈与队列:150.逆波兰表达式求值](./problems/0150.逆波兰表达式求值.md)
7. [栈与队列:239.滑动窗口最大值](./problems/0239.滑动窗口最大值.md)
8. [栈与队列:347.前K个高频元素](./problems/0347.前K个高频元素.md)
9. [栈与队列:总结篇!](./problems/栈与队列总结.md)
</details>
<details>
<summary><b>二叉树</b></summary>
题目分类大纲如下:
<img src='https://file1.kamacoder.com/i/algo/20240424172231.png' width=600 alt='二叉树大纲'> </img></div>
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)
</details>
<details>
<summary><b>回溯算法</b></summary>
<img src='https://file1.kamacoder.com/i/algo/20240424172311.png' width=600 alt='回溯算法大纲'> </img></div>
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)
</details>
<details>
<summary><b>贪心算法</b></summary>
<img src='https://file1.kamacoder.com/i/algo/20210917104315.png' width=600 alt='贪心算法大纲'> </img></div>
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)
</details>
<details>
<summary><b>动态规划</b></summary>
动态规划专题已经开始啦,来不及解释了,小伙伴们上车别掉队!
<img src='https://file1.kamacoder.com/i/algo/动态规划-总结大纲1.jpg' width=500> </img></div>
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)
背包问题系列:
<img src='https://file1.kamacoder.com/i/algo/动态规划-背包问题总结.png' width=500 alt='背包问题大纲'> </img></div>
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)
股票系列:
<img src='https://file1.kamacoder.com/i/algo/股票问题总结.jpg' width=500 alt='股票问题总结'> </img></div>
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)
子序列系列:
<img src='https://file1.kamacoder.com/i/algo/动态规划-子序列问题总结.jpg' width=500 alt=''> </img></div>
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)
</details>
<details>
<summary><b>单调栈</b></summary>
1. [单调栈:739.每日温度](./problems/0739.每日温度.md)
2. [单调栈:496.下一个更大元素I](./problems/0496.下一个更大元素I.md)
3. [单调栈:503.下一个更大元素II](./problems/0503.下一个更大元素II.md)
4. [单调栈:42.接雨水](./problems/0042.接雨水.md)
5. [单调栈:84.柱状图中最大的矩形](./problems/0084.柱状图中最大的矩形.md)
</details>
<details>
<summary><b>图论</b></summary>
**[图论正式发布](./problems/qita/tulunfabu.md)**
1. [图论:理论基础](./problems/kamacoder/图论理论基础.md)
2. [图论:深度优先搜索理论基础](./problems/kamacoder/图论深搜理论基础.md)
3. [图论:所有可达路径](./problems/kamacoder/0098.所有可达路径.md)
4. [图论:广度优先搜索理论基础](./problems/kamacoder/图论广搜理论基础.md)
5. [图论:岛屿数量.深搜版](./problems/kamacoder/0099.岛屿的数量深搜.md)
6. [图论:岛屿数量.广搜版](./problems/kamacoder/0099.岛屿的数量广搜.md)
7. [图论:岛屿的最大面积](./problems/kamacoder/0100.岛屿的最大面积.md)
8. [图论:孤岛的总面积](./problems/kamacoder/0101.孤岛的总面积.md)
9. [图论:沉没孤岛](./problems/kamacoder/0102.沉没孤岛.md)
10. [图论:水流问题](./problems/kamacoder/0103.水流问题.md)
11. [图论:建造最大岛屿](./problems/kamacoder/0104.建造最大岛屿.md)
12. [图论:岛屿的周长](./problems/kamacoder/0106.岛屿的周长.md)
13. [图论:字符串接龙](./problems/kamacoder/0110.字符串接龙.md)
14. [图论:有向图的完全可达性](./problems/kamacoder/0105.有向图的完全可达性.md)
15. [图论:并查集理论基础](./problems/kamacoder/图论并查集理论基础.md)
16. [图论:寻找存在的路径](./problems/kamacoder/0107.寻找存在的路径.md)
17. [图论:冗余连接](./problems/kamacoder/0108.冗余连接.md)
18. [图论:冗余连接II](./problems/kamacoder/0109.冗余连接II.md)
19. [图论:最小生成树之prim](./problems/kamacoder/0053.寻宝-prim.md)
20. [图论:最小生成树之kruskal](./problems/kamacoder/0053.寻宝-Kruskal.md)
21. [图论:拓扑排序](./problems/kamacoder/0117.软件构建.md)
22. [图论:dijkstra(朴素版)](./problems/kamacoder/0047.参会dijkstra朴素.md)
23. [图论:dijkstra(堆优化版)](./problems/kamacoder/0047.参会dijkstra堆.md)
24. [图论:Bellman_ford 算法](./problems/kamacoder/0094.城市间货物运输I.md)
25. [图论:Bellman_ford 队列优化算法(又名SPFA)](./problems/kamacoder/0094.城市间货物运输I-SPFA.md)
26. [图论:Bellman_ford之判断负权回路](./problems/kamacoder/0095.城市间货物运输II.md)
27. [图论:Bellman_ford之单源有限最短路](./problems/kamacoder/0096.城市间货物运输III.md)
28. [图论:Floyd 算法](./problems/kamacoder/0097.小明逛公园.md)
29. [图论:A * 算法](./problems/kamacoder/0126.骑士的攻击astar.md)
30. [图论:最短路算法总结篇](./problems/kamacoder/最短路问题总结篇.md)
31. [图论:图论总结篇](./problems/kamacoder/图论总结篇.md)
</details>
---
## 🧩 算法模板
- [各类基础算法模板(持续更新)](https://github.com/youngyangyang04/leetcode/blob/master/problems/算法模板.md)
---
## 🙌 参与贡献
- 欢迎提交 **题解修订 / 多语言实现 / 文档勘误 / 新增练习**
- 请先阅读:[如何提交与协作](https://www.programmercarl.com/qita/join.html)
- 致谢所有贡献者 → [Contributors](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)
---
## ⭐ Star 趋势
[](https://star-history.com/#youngyangyang04/leetcode-master&Date)
---
## 👨💻 关于作者
大家好,我是 **程序员 Carl**,哈工大师兄,先后在腾讯、百度从事后端与底层技术研发,著有《代码随想录》。
---
## 📥 PDF 下载与学习群
添加下方企业微信,自动获取 **PDF 精讲**,并可选择加入刷题群:
> 备注格式
> - **在职**:姓名-城市-岗位
> - **学生**:姓名-学校-年级(**无备注不通过**)
<p align="center">
<img src="https://file1.kamacoder.com/i/algo/shuati20250519.jpg" width="200" height="200" />
</p>
---
## 📜 版权说明
- 本仓库所有内容均为原创,引用需 **注明出处与链接**。
- 严禁恶意搬运与洗稿,侵权必究。
---
================================================
FILE: problems/0001.两数之和.md
================================================
* [做项目(多个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)
# 1. 两数之和
[力扣题目链接](https://leetcode.cn/problems/two-sum/)
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
**示例:**
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[梦开始的地方,Leetcode:1.两数之和](https://www.bilibili.com/video/BV1aT41177mK),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。
建议大家做这道题目之前,先做一下这两道
* [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html)
* [349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)
[242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)这道题目是通过set作为哈希表来解决哈希问题。
首先我再强调一下 **什么时候使用哈希法**,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
那么我们就应该想到使用哈希法了。
因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,**需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适**。
再来看一下使用数组和set来做哈希法的局限。
* 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
* set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
C++中map,有三种类型:
|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
|---|---| --- |---| --- | --- | ---|
|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(log n)|O(log n) |
|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(log n) |O(log n) |
|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)|
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。
同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://www.programmercarl.com/哈希表理论基础.html)。
**这道题目中并不需要key有序,选择std::unordered_map 效率更高!** 使用其他语言的录友注意了解一下自己所用语言的数据结构就行。
接下来需要明确两点:
* **map用来做什么**
* **map中key和value分别表示什么**
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
接下来是map中key和value分别表示什么。
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
过程如下:


C++代码:
```CPP
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int,int> map;
for(int i = 0; i < nums.size(); i++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 总结
本题其实有四个重点:
* 为什么会想到用哈希表
* 哈希表为什么用map
* 本题map是用来存什么的
* map中的key和value用来存什么的
把这四点想清楚了,本题才算是理解透彻了。
很多录友把这道题目 通过了,但都没想清楚map是用来做什么的,以至于对代码的理解其实是 一知半解的。
## 其他语言版本
### Java:
```java
//使用哈希表
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i]; // 遍历当前元素,并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i); // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}
```
```java
//使用哈希表方法2
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> indexMap = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int balance = target - nums[i]; // 记录当前的目标值的余数
if(indexMap.containsKey(balance)){ // 查找当前的map中是否有满足要求的值
return new int []{i, indexMap.get(balance)}; // 如果有,返回目标值
} else{
indexMap.put(nums[i], i); // 如果没有,把访问过的元素和下标加入map中
}
}
return null;
}
```
```java
//使用双指针
public int[] twoSum(int[] nums, int target) {
int m=0,n=0,k,board=0;
int[] res=new int[2];
int[] tmp1=new int[nums.length];
//备份原本下标的nums数组
System.arraycopy(nums,0,tmp1,0,nums.length);
//将nums排序
Arrays.sort(nums);
//双指针
for(int i=0,j=nums.length-1;i<j;){
if(nums[i]+nums[j]<target)
i++;
else if(nums[i]+nums[j]>target)
j--;
else if(nums[i]+nums[j]==target){
m=i;
n=j;
break;
}
}
//找到nums[m]在tmp1数组中的下标
for(k=0;k<nums.length;k++){
if(tmp1[k]==nums[m]){
res[0]=k;
break;
}
}
//找到nums[n]在tmp1数组中的下标
for(int i=0;i<nums.length;i++){
if(tmp1[i]==nums[n]&&i!=k)
res[1]=i;
}
return res;
}
```
### Python:
(版本一) 使用字典
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 如果没找到匹配对,就把访问过的元素和下标加入到map中
return []
```
(版本二)使用集合
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建一个集合来存储我们目前看到的数字
seen = set()
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [nums.index(complement), i]
seen.add(num)
```
(版本三)使用双指针
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对输入列表进行排序
nums_sorted = sorted(nums)
# 使用双指针
left = 0
right = len(nums_sorted) - 1
while left < right:
current_sum = nums_sorted[left] + nums_sorted[right]
if current_sum == target:
# 如果和等于目标数,则返回两个数的下标
left_index = nums.index(nums_sorted[left])
right_index = nums.index(nums_sorted[right])
if left_index == right_index:
right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
return [left_index, right_index]
elif current_sum < target:
# 如果总和小于目标,则将左侧指针向右移动
left += 1
else:
# 如果总和大于目标值,则将右指针向左移动
right -= 1
```
(版本四)暴力法
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i,j]
```
### Go:
```go
// 暴力解法
func twoSum(nums []int, target int) []int {
for k1, _ := range nums {
for k2 := k1 + 1; k2 < len(nums); k2++ {
if target == nums[k1] + nums[k2] {
return []int{k1, k2}
}
}
}
return []int{}
}
```
```go
func twoSum(nums []int, target int) []int {
m := make(map[int]int)
for i, num := range nums {
complement := target - num
if index, found := m[complement]; found {
return []int{index, i}
}
m[num] = i
}
return nil // 返回空数组 nil 代替空切片
}
```
### Rust:
```rust
use std::collections::HashMap;
impl Solution {
pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
let mut map = HashMap::with_capacity(nums.len());
for i in 0..nums.len() {
if let Some(k) = map.get(&(target - nums[i])) {
if *k != i {
return vec![*k as i32, i as i32];
}
}
map.insert(nums[i], i);
}
panic!("not found")
}
}
```
```rust
use std::collections::HashMap;
impl Solution {
pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
let mut hm: HashMap<i32, i32> = HashMap::new();
for i in 0..nums.len() {
let j = target - nums[i];
if hm.contains_key(&j) {
return vec![*hm.get(&j).unwrap(), i as i32]
} else {
hm.insert(nums[i], i as i32);
}
}
vec![-1, -1]
}
}
```
### JavaScript:
```javascript
var twoSum = function (nums, target) {
let hash = {};
for (let i = 0; i < nums.length; i++) { // 遍历当前元素,并在map中寻找是否有匹配的key
if (hash[target - nums[i]] !== undefined) {
return [i, hash[target - nums[i]]];
}
hash[nums[i]] = i; // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return [];
};
```
### TypeScript:
```typescript
function twoSum(nums: number[], target: number): number[] {
let helperMap: Map<number, number> = new Map();
let index: number | undefined;
let resArr: number[] = [];
for (let i = 0, length = nums.length; i < length; i++) {
index = helperMap.get(target - nums[i]);
if (index !== undefined) {
resArr = [i, index];
break;
}
helperMap.set(nums[i], i);
}
return resArr;
};
```
### PhP:
```php
function twoSum(array $nums, int $target): array
{
$map = [];
foreach($nums as $i => $num) {
if (isset($map[$target - $num])) {
return [
$i,
$map[$target - $num]
];
} else {
$map[$num] = $i;
}
}
return [];
}
```
### Swift:
```swift
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
// 值: 下标
var map = [Int: Int]()
for (i, e) in nums.enumerated() {
if let v = map[target - e] {
return [v, i]
} else {
map[e] = i
}
}
return []
}
```
### Scala:
```scala
object Solution {
// 导入包
import scala.collection.mutable
def twoSum(nums: Array[Int], target: Int): Array[Int] = {
// key存储值,value存储下标
val map = new mutable.HashMap[Int, Int]()
for (i <- nums.indices) {
val tmp = target - nums(i) // 计算差值
// 如果这个差值存在于map,则说明找到了结果
if (map.contains(tmp)) {
return Array(map.get(tmp).get, i)
}
// 如果不包含把当前值与其下标放到map
map.put(nums(i), i)
}
// 如果没有找到直接返回一个空的数组,return关键字可以省略
new Array[Int](2)
}
}
```
### C#:
```csharp
public class Solution {
public int[] TwoSum(int[] nums, int target) {
Dictionary<int ,int> dic= new Dictionary<int,int>();
for(int i=0;i<nums.Length;i++){
int imp= target-nums[i];
if(dic.ContainsKey(imp)&&dic[imp]!=i){
return new int[]{i, dic[imp]};
}
if(!dic.ContainsKey(nums[i])){
dic.Add(nums[i],i);
}
}
return new int[]{0, 0};
}
}
```
### Dart:
```dart
import 'dart:collection';
List<int> twoSum(List<int> nums, int target) {
HashMap<int, int> hashMap = HashMap();
for (int i = 0; i < nums.length; i++) {
int rest = target - nums[i];
if (hashMap.containsKey(rest)) {
return [hashMap[rest]!, i];
}
hashMap.addEntries({nums[i]: i}.entries);
}
return [];
}
```
### C:
```c
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
// leetcode 支持 ut_hash 函式庫
typedef struct {
int key;
int value;
UT_hash_handle hh; // make this structure hashable
} map;
map* hashMap = NULL;
void hashMapAdd(int key, int value){
map* s;
// key already in the hash?
HASH_FIND_INT(hashMap, &key, s);
if(s == NULL){
s = (map*)malloc(sizeof(map));
s -> key = key;
HASH_ADD_INT(hashMap, key, s);
}
s -> value = value;
}
map* hashMapFind(int key){
map* s;
// *s: output pointer
HASH_FIND_INT(hashMap, &key, s);
return s;
}
void hashMapCleanup(){
map* cur, *tmp;
HASH_ITER(hh, hashMap, cur, tmp){
HASH_DEL(hashMap, cur);
free(cur);
}
}
void hashPrint(){
map* s;
for(s = hashMap; s != NULL; s=(map*)(s -> hh.next)){
printf("key %d, value %d\n", s -> key, s -> value);
}
}
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
int i, *ans;
// hash find result
map* hashMapRes;
hashMap = NULL;
ans = malloc(sizeof(int) * 2);
for(i = 0; i < numsSize; i++){
// key 代表 nums[i] 的值,value 代表所在 index;
hashMapAdd(nums[i], i);
}
hashPrint();
for(i = 0; i < numsSize; i++){
hashMapRes = hashMapFind(target - nums[i]);
if(hashMapRes && hashMapRes -> value != i){
ans[0] = i;
ans[1] = hashMapRes -> value ;
*returnSize = 2;
return ans;
}
}
hashMapCleanup();
return NULL;
}
```
================================================
FILE: problems/0005.最长回文子串.md
================================================
* [做项目(多个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)
# 5.最长回文子串
[力扣题目链接](https://leetcode.cn/problems/longest-palindromic-substring/)
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
* 输入:s = "babad"
* 输出:"bab"
* 解释:"aba" 同样是符合题意的答案。
示例 2:
* 输入:s = "cbbd"
* 输出:"bb"
示例 3:
* 输入:s = "a"
* 输出:"a"
示例 4:
* 输入:s = "ac"
* 输出:"a"
## 思路
本题和[647.回文子串](https://programmercarl.com/0647.回文子串.html) 差不多是一样的,但647.回文子串更基本一点,建议可以先做647.回文子串
### 暴力解法
两层for循环,遍历区间起始位置和终止位置,然后判断这个区间是不是回文。
时间复杂度:O(n^3)
### 动态规划
动规五部曲:
1. 确定dp数组(dp table)以及下标的含义
布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
2. 确定递推公式
在确定递推公式时,就要分析如下几种情况。
整体上是两种,就是s[i]与s[j]相等,s[i]与s[j]不相等这两种。
当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。
当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
* 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
* 情况二:下标i 与 j相差为1,例如aa,也是文子串
* 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
以上三种情况分析完了,那么递归公式如下:
```CPP
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
}
}
```
注意这里我没有列出当s[i]与s[j]不相等的时候,因为在下面dp[i][j]初始化的时候,就初始为false。
在得到[i,j]区间是否是回文子串的时候,直接保存最长回文子串的左边界和右边界,代码如下:
```CPP
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
}
}
if (dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1;
left = i;
right = j;
}
```
3. dp数组如何初始化
dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹配上了。
所以dp[i][j]初始化为false。
4. 确定遍历顺序
遍历顺序可有有点讲究了。
首先从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。
dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图:

如果这矩阵是从上到下,从左到右遍历,那么会用到没有计算过的dp[i + 1][j - 1],也就是根据不确定是不是回文的区间[i+1,j-1],来判断了[i,j]是不是回文,那结果一定是不对的。
**所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的**。
有的代码实现是优先遍历列,然后遍历行,其实也是一个道理,都是为了保证dp[i + 1][j - 1]都是经过计算的。
代码如下:
```CPP
for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
}
}
if (dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1;
left = i;
right = j;
}
}
}
```
5. 举例推导dp数组
举例,输入:"aaa",dp[i][j]状态如下:

**注意因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分**。
以上分析完毕,C++代码如下:
```CPP
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
int maxlenth = 0;
int left = 0;
int right = 0;
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
}
}
if (dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1;
left = i;
right = j;
}
}
}
return s.substr(left, right - left + 1);
}
};
```
以上代码是为了凸显情况一二三,当然是可以简洁一下的,如下:
```CPP
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
int maxlenth = 0;
int left = 0;
int right = 0;
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) {
dp[i][j] = true;
}
if (dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1;
left = i;
right = j;
}
}
}
return s.substr(left, maxlenth);
}
};
```
* 时间复杂度:O(n^2)
* 空间复杂度:O(n^2)
### 双指针
动态规划的空间复杂度是偏高的,我们再看一下双指针法。
首先确定回文串,就是找中心然后想两边扩散看是不是对称的就可以了。
**在遍历中心点的时候,要注意中心点有两种情况**。
一个元素可以作为中心点,两个元素也可以作为中心点。
那么有的同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到。
所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。
**这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算**,代码如下:
```CPP
class Solution {
public:
int left = 0;
int right = 0;
int maxLength = 0;
string longestPalindrome(string s) {
int result = 0;
for (int i = 0; i < s.size(); i++) {
extend(s, i, i, s.size()); // 以i为中心
extend(s, i, i + 1, s.size()); // 以i和i+1为中心
}
return s.substr(left, maxLength);
}
void extend(const string& s, int i, int j, int n) {
while (i >= 0 && j < n && s[i] == s[j]) {
if (j - i + 1 > maxLength) {
left = i;
right = j;
maxLength = j - i + 1;
}
i--;
j++;
}
}
};
```
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
### Manacher 算法
Manacher 算法的关键在于高效利用回文的对称性,通过插入分隔符和维护中心、边界等信息,在线性时间内找到最长回文子串。这种方法避免了重复计算,是处理回文问题的最优解。
```c++
//Manacher 算法
class Solution {
public:
string longestPalindrome(string s) {
// 预处理字符串,在每个字符之间插入 '#'
string t = "#";
for (char c : s) {
t += c; // 添加字符
t += '#';// 添加分隔符
}
int n = t.size();// 新字符串的长度
vector<int> p(n, 0);// p[i] 表示以 t[i] 为中心的回文半径
int center = 0, right = 0;// 当前回文的中心和右边界
// 遍历预处理后的字符串
for (int i = 0; i < n; i++) {
// 如果当前索引在右边界内,利用对称性初始化 p[i]
if (i < right) {
p[i] = min(right - i, p[2 * center - i]);
}
// 尝试扩展回文
while (i - p[i] - 1 >= 0 && i + p[i] + 1 < n && t[i - p[i] - 1] == t[i + p[i] + 1]) {
p[i]++;// 增加回文半径
}
// 如果当前回文扩展超出右边界,更新中心和右边界
if (i + p[i] > right) {
center = i;// 更新中心
right = i + p[i];// 更新右边界
}
}
// 找到最大回文半径和对应的中心
int maxLen = 0, centerIndex = 0;
for (int i = 0; i < n; i++) {
if (p[i] > maxLen) {
maxLen = p[i];// 更新最大回文长度
centerIndex = i;// 更新中心索引
}
}
// 计算原字符串中回文子串的起始位置并返回
return s.substr((centerIndex - maxLen) / 2, maxLen);
}
};
```
* 时间复杂度:O(n)
* 空间复杂度:O(n)
## 其他语言版本
### Java:
```java
// 双指针 动态规划
class Solution {
public String longestPalindrome(String s) {
if (s.length() == 0 || s.length() == 1) return s;
int length = 1;
int index = 0;
boolean[][] palindrome = new boolean[s.length()][s.length()];
for (int i = 0; i < s.length(); i++) {
palindrome[i][i] = true;
}
for (int L = 2; L <= s.length(); L++) {
for (int i = 0; i < s.length(); i++) {
int j = i + L - 1;
if (j >= s.length()) break;
if (s.charAt(i) != s.charAt(j)) {
palindrome[i][j] = false;
} else {
if (j - i < 3) {
palindrome[i][j] = true;
} else {
palindrome[i][j] = palindrome[i + 1][j - 1];
}
}
if (palindrome[i][j] && j - i + 1 > length) {
length = j - i + 1;
index = i;
}
}
}
return s.substring(index, index + length);
}
}
```
```java
// 双指针 中心扩散法
class Solution {
public String longestPalindrome(String s) {
String s1 = "";
String s2 = "";
String res = "";
for (int i = 0; i < s.length(); i++) {
// 分两种情况:即一个元素作为中心点,两个元素作为中心点
s1 = extend(s, i, i); // 情况1
res = s1.length() > res.length() ? s1 : res;
s2 = extend(s, i, i + 1); // 情况2
res = s2.length() > res.length() ? s2 : res;
}
return res; // 返回最长的
}
public String extend(String s, int start, int end){
String tmp = "";
while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)){
tmp = s.substring(start, end + 1); // Java中substring是左闭右开的,所以要+1
// 向两边扩散
start--;
end++;
}
return tmp;
}
}
```
### Python:
```python
class Solution:
def longestPalindrome(self, s: str) -> str:
dp = [[False] * len(s) for _ in range(len(s))]
maxlenth = 0
left = 0
right = 0
for i in range(len(s) - 1, -1, -1):
for j in range(i, len(s)):
if s[j] == s[i]:
if j - i <= 1 or dp[i + 1][j - 1]:
dp[i][j] = True
if dp[i][j] and j - i + 1 > maxlenth:
maxlenth = j - i + 1
left = i
right = j
return s[left:right + 1]
```
双指针:
```python
class Solution:
def longestPalindrome(self, s: str) -> str:
def find_point(i, j, s):
while i >= 0 and j < len(s) and s[i] == s[j]:
i -= 1
j += 1
return i + 1, j
def compare(start, end, left, right):
if right - left > end - start:
return left, right
else:
return start, end
start = 0
end = 0
for i in range(len(s)):
left, right = find_point(i, i, s)
start, end = compare(start, end, left, right)
left, right = find_point(i, i + 1, s)
start, end = compare(start, end, left, right)
return s[start:end]
```
### Go:
```go
func longestPalindrome(s string) string {
maxLen := 0
left := 0
length := 0
dp := make([][]bool, len(s))
for i := 0; i < len(s); i++ {
dp[i] = make([]bool,len(s))
}
for i := len(s)-1; i >= 0; i-- {
for j := i; j < len(s); j++ {
if s[i] == s[j]{
if j-i <= 1{ // 情况一和情况二
length = j-i
dp[i][j]=true
}else if dp[i+1][j-1]{ // 情况三
length = j-i
dp[i][j] = true
}
}
}
if length > maxLen {
maxLen = length
left = i
}
}
return s[left: left+maxLen+1]
}
```
### JavaScript:
```js
//动态规划解法
var longestPalindrome = function(s) {
const len = s.length;
// 布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false
let dp = new Array(len).fill(false).map(() => new Array(len).fill(false));
// left起始位置 maxlenth回文串长度
let left = 0, maxlenth = 0;
for(let i = len - 1; i >= 0; i--){
for(let j = i; j < len; j++){
// 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串 j - i == 0
// 情况二:下标i 与 j相差为1,例如aa,也是文子串 j - i == 1
// 情况一和情况二 可以合并为 j - i <= 1
// 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]===true
if(s[i] === s[j] && (j - i <= 1 || dp[i + 1][j - 1])){
dp[i][j] = true;
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
if(dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1; // 回文串长度
left = i; // 起始位置
}
}
}
return s.substr(left, maxlenth); // 找到子串
};
//双指针
var longestPalindrome = function(s) {
let left = 0, right = 0, maxLength = 0;
const extend = (s, i, j, n) => {// s为字符串 i,j为双指针 n为字符串长度
while(i >= 0 && j < n && s[i] === s[j]){
if(j - i + 1 > maxLength){
left = i; // 更新开始位置
right = j; // 更新结尾位置
maxLength = j - i + 1; // 更新子串最大长度
}
// 指针移动
i--;
j++;
}
}
for(let i = 0; i < s.length; i++){
extend(s, i, i, s.length); // 以i为中心
extend(s, i, i + 1, s.length); // 以i和i+1为中心
}
return s.substr(left, maxLength);
};
//Manacher算法
var longestPalindrome = function(s) {
const len = s.length;
if(len < 2) return s;
let maxLength = 1, index = 0;
//Manacher算法,利用回文对称的性质,根据i在上一个回文中心的臂长里的位置去判断i的回文性
//需要知道上一个回文中心,以及其臂长
let center = 0;
//注意这里使用了maxRight的而不是真实的臂长length,因为之后需要判断i在臂长的什么位置
//如果这里臂长用了length,之后还要 计算i - center 去和 length比较,太繁琐
let maxRight = 0;
//考虑到回文串的长度是偶数的情况,所以这里预处理一下字符串,每个字符间插入特殊字符,把可能性都化为奇数
//这个处理把回文串长度的可能性都化为了奇数
//#c#b#b#a#
//#c#b#a#b#d#
let ss = "";
for(let i = 0; i < s.length; i++){
ss += "#"+s[i];
}
ss += "#";
//需要维护一个每个位置臂长的信息数组positionLength
const pl = new Array(ss.length).fill(0);
//这里需要注意参考的是i关于center对称的点i'的回文性
//i' = 2*center - i;
//所以列下情况:
//1.i>maxRight,找不到i',无法参考,自己算自己的
//2.i<=maxRight:
//2.1 i<maxRight-pl[i'],pl[i']的臂长没有超过center的臂长,根据对称性,pl[i] = pl[i']
//2.2 i=maxRight-pl[i'],pl[i']的臂长刚好等于center的臂长,根据对称性,pl[i] >= pl[i‘],大多少需要尝试扩散
//2.3 i>maxRight-pl[i'],pl[i']的臂长超过了center的臂长,根据对称性,i中心扩散到MaxRight处,
// s[2*i-maxRight] !== s[MaxRight]必不相等,所以pl[i] = maxRight-i;
//总结就是pl[i] = Math.min(maxRight-i,pl[i']);提示i<maxRight-pl[i'] 也可写成 pl[i']<maxRight-i
//0没有意义,从1开始计算
for(let i = 1; i < ss.length; i++){
if(i <= maxRight){//可以参考之前的
pl[i] = Math.min(maxRight - i, pl[2 * center - i]);
//尝试中心扩散
}
//注意到i<maxRight时都要尝试中心扩散,所以写else完全无意义,把中心扩散的代码写在下面
// else{//i不在之前回文中心的臂长范围里,之前的信息就完全无法参考,只能从i中心扩散把,然后去维护maxRight和center的定义
//尝试中心扩散
//这里不要动center和maxRight
// center = i;
// maxRight = pl[i] + i + 1;
let right = pl[i] + i + 1;
let left = i - pl[i] - 1;
while (left >= 0 && right<ss.length && ss[left] === ss[right]) {
right++;
left--;
pl[i]++;
}
// }
if(pl[i] + i > maxRight){
center = i;
maxRight = pl[i] + i;
}
if (pl[i] * 2 + 1 > maxLength){
maxLength = pl[i]*2+1;
index = i - pl[i];
}
}
return ss.substr(index, maxLength).replace(/#/g,"");
};
```
### C:
动态规划:
```c
//初始化dp数组,全部初始为false
bool **initDP(int strLen) {
bool **dp = (bool **)malloc(sizeof(bool *) * strLen);
int i, j;
for(i = 0; i < strLen; ++i) {
dp[i] = (bool *)malloc(sizeof(bool) * strLen);
for(j = 0; j < strLen; ++j)
dp[i][j] = false;
}
return dp;
}
char * longestPalindrome(char * s){
//求出字符串长度
int strLen = strlen(s);
//初始化dp数组,元素初始化为false
bool **dp = initDP(strLen);
int maxLength = 0, left = 0, right = 0;
//从下到上,从左到右遍历
int i, j;
for(i = strLen - 1; i >= 0; --i) {
for(j = i; j < strLen; ++j) {
//若当前i与j所指字符一样
if(s[i] == s[j]) {
//若i、j指向相邻字符或同一字符,则为回文字符串
if(j - i <= 1)
dp[i][j] = true;
//若i+1与j-1所指字符串为回文字符串,则i、j所指字符串为回文字符串
else if(dp[i + 1][j - 1])
dp[i][j] = true;
}
//若新的字符串的长度大于之前的最大长度,进行更新
if(dp[i][j] && j - i + 1 > maxLength) {
maxLength = j - i + 1;
left = i;
right = j;
}
}
}
//复制回文字符串,并返回
char *ret = (char*)malloc(sizeof(char) * (maxLength + 1));
memcpy(ret, s + left, maxLength);
ret[maxLength] = 0;
return ret;
}
```
双指针:
```c
int left, maxLength;
void extend(char *str, int i, int j, int size) {
while(i >= 0 && j < size && str[i] == str[j]) {
//若当前子字符串长度大于最长的字符串长度,进行更新
if(j - i + 1 > maxLength) {
maxLength = j - i + 1;
left = i;
}
//左指针左移,右指针右移。扩大搜索范围
++j, --i;
}
}
char * longestPalindrome(char * s){
left = right = maxLength = 0;
int size = strlen(s);
int i;
for(i = 0; i < size; ++i) {
//长度为单数的子字符串
extend(s, i, i, size);
//长度为双数的子字符串
extend(s, i, i + 1, size);
}
//复制子字符串
char *subStr = (char *)malloc(sizeof(char) * (maxLength + 1));
memcpy(subStr, s + left, maxLength);
subStr[maxLength] = 0;
return subStr;
}
```
### C#:
動態規則:
```csharp
public class Solution {
public string LongestPalindrome(string s) {
bool[,] dp = new bool[s.Length, s.Length];
int maxlenth = 0;
int left = 0;
int right = 0;
for(int i = s.Length-1 ; i>=0; i--){
for(int j = i; j <s.Length;j++){
if(s[i] == s[j]){
if(j - i <= 1){ // 情况一和情况二
dp[i, j] = true;
}else if( dp[i+1, j-1] ){ // 情况三
dp[i, j] = true;
}
}
if(dp[i, j] && j-i+1 > maxlenth){
maxlenth = j-i+1;
left = i;
right = j;
}
}
}
return s.Substring(left, maxlenth);
}
}
```
雙指針:
```csharp
public class Solution {
int maxlenth = 0;
int left = 0;
int right = 0;
public string LongestPalindrome(string s) {
int result = 0;
for (int i = 0; i < s.Length; i++) {
extend(s, i, i, s.Length); // 以i為中心
extend(s, i, i + 1, s.Length); // 以i和i+1為中心
}
return s.Substring(left, maxlenth);
}
private void extend(string s, int i, int j, int n) {
while (i >= 0 && j < n && s[i] == s[j]) {
if (j - i + 1 > maxlenth) {
left = i;
right = j;
maxlenth = j - i + 1;
}
i--;
j++;
}
}
}
```
================================================
FILE: problems/0015.三数之和.md
================================================
* [做项目(多个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)
# 第15题. 三数之和
[力扣题目链接](https://leetcode.cn/problems/3sum/)
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
**注意:** 答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[梦破碎的地方!| LeetCode:15.三数之和](https://www.bilibili.com/video/BV1GW4y127qo),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
**注意[0, 0, 0, 0] 这组数据**
## 思路
### 哈希解法
两层for循环就可以确定 两个数值,可以使用哈希法来确定 第三个数 0-(a+b) 或者 0 - (a + c) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
大家可以尝试使用哈希法写一写,就知道其困难的程度了。
哈希法C++代码:
```CPP
class Solution {
public:
// 在一个数组中找到3个数形成的三元组,它们的和为0,不能重复使用(三数下标互不相同),且三元组不能重复。
// b(存储)== 0-(a+c)(检索)
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
// 如果a是正数,a<b<c,不可能形成和为0的三元组
if (nums[i] > 0)
break;
// [a, a, ...] 如果本轮a和上轮a相同,那么找到的b,c也是相同的,所以去重a
if (i > 0 && nums[i] == nums[i - 1])
continue;
// 这个set的作用是存储b
unordered_set<int> set;
for (int k = i + 1; k < nums.size(); k++) {
// 去重b=c时的b和c
if (k > i + 2 && nums[k] == nums[k - 1] && nums[k - 1] == nums[k - 2])
continue;
// a+b+c=0 <=> b=0-(a+c)
int target = 0 - (nums[i] + nums[k]);
if (set.find(target) != set.end()) {
result.push_back({nums[i], target, nums[k]}); // nums[k]成为c
set.erase(target);
}
else {
set.insert(nums[k]); // nums[k]成为b
}
}
}
return result;
}
};
```
* 时间复杂度: O(n^2)
* 空间复杂度: O(n),额外的 set 开销
### 双指针
**其实这道题目使用哈希法并不十分合适**,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。
而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。
接下来我来介绍另一个解法:双指针法,**这道题目使用双指针法 要比哈希法高效一些**,那么来讲解一下具体实现的思路。
动画效果如下:

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)。
C++代码代码如下:
```CPP
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
*/
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
```
* 时间复杂度: O(n^2)
* 空间复杂度: O(1)
### 去重逻辑的思考
#### a的去重
说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]
a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。
但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。
有同学可能想,这不都一样吗。
其实不一样!
都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。
如果我们的写法是 这样:
```C++
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
```
那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
**我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!**
所以这里是有两个重复的维度。
那么应该这么写:
```C++
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
```
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。
这是一个非常细节的思考过程。
#### b与c的去重
很多同学写本题的时候,去重的逻辑多加了 对right 和left 的去重:(代码中注释部分)
```C++
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
// 去重 right
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
// 去重 left
while (left < right && nums[left] == nums[left - 1]) left++;
} else {
}
}
```
但细想一下,这种去重其实对提升程序运行效率是没有帮助的。
拿right去重为例,即使不加这个去重逻辑,依然根据 `while (right > left) ` 和 `if (nums[i] + nums[left] + nums[right] > 0)` 去完成right-- 的操作。
多加了 ` while (left < right && nums[right] == nums[right + 1]) right--;` 这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。
最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。
所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。
## 思考题
既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://programmercarl.com/0001.两数之和.html),可不可以使用双指针法呢?
如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!**
两数之和 就不能使用双指针法,因为[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。
如果[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是数值的话,就可以使用双指针法了。
## 其他语言版本
### Java:
(版本一) 双指针
```Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
```
(版本二) 使用哈希集合
```Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 如果第一个元素大于零,不可能凑成三元组
if (nums[i] > 0) {
return result;
}
// 三元组元素a去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
HashSet<Integer> set = new HashSet<>();
for (int j = i + 1; j < nums.length; j++) {
// 三元组元素b去重
if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]) {
continue;
}
int c = -nums[i] - nums[j];
if (set.contains(c)) {
result.add(Arrays.asList(nums[i], nums[j], c));
set.remove(c); // 三元组元素c去重
} else {
set.add(nums[j]);
}
}
}
return result;
}
}
```
### Python:
(版本一) 双指针
```Python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
```
(版本二) 使用字典
```python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[j], c = -(a + b)
for i in range(len(nums)):
# 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]: #三元组元素a去重
continue
d = {}
for j in range(i + 1, len(nums)):
if j > i + 2 and nums[j] == nums[j-1] == nums[j-2]: # 三元组元素b去重
continue
c = 0 - (nums[i] + nums[j])
if c in d:
result.append([nums[i], nums[j], c])
d.pop(c) # 三元组元素c去重
else:
d[nums[j]] = j
return result
```
### Go:
(版本一) 双指针
```Go
func threeSum(nums []int) [][]int {
sort.Ints(nums)
res := [][]int{}
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for i := 0; i < len(nums)-2; i++ {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
n1 := nums[i]
if n1 > 0 {
break
}
// 去重a
if i > 0 && n1 == nums[i-1] {
continue
}
l, r := i+1, len(nums)-1
for l < r {
n2, n3 := nums[l], nums[r]
if n1+n2+n3 == 0 {
res = append(res, []int{n1, n2, n3})
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
for l < r && nums[l] == n2 {
l++
}
for l < r && nums[r] == n3 {
r--
}
} else if n1+n2+n3 < 0 {
l++
} else {
r--
}
}
}
return res
}
```
(版本二) 哈希解法
```Go
func threeSum(nums []int) [][]int {
res := make([][]int, 0)
sort.Ints(nums)
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for i := 0; i < len(nums); i++ {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0 {
break
}
// 三元组元素a去重
if i > 0 && nums[i] == nums[i-1] {
continue
}
set := make(map[int]struct{})
for j := i + 1; j < len(nums); j++ {
// 三元组元素b去重
if j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2] {
continue
}
c := -nums[i] - nums[j]
if _, ok := set[c]; ok {
res = append(res, []int{nums[i], nums[j], c})
// 三元组元素c去重
delete(set, c)
} else {
set[nums[j]] = struct{}{}
}
}
}
return res
}
```
### JavaScript:
```js
var threeSum = function(nums) {
const res = [], len = nums.length
// 将数组排序
nums.sort((a, b) => a - b)
for (let i = 0; i < len; i++) {
let l = i + 1, r = len - 1, iNum = nums[i]
// 数组排过序,如果第一个数大于0直接返回res
if (iNum > 0) return res
// 去重
if (iNum == nums[i - 1]) continue
while(l < r) {
let lNum = nums[l], rNum = nums[r], threeSum = iNum + lNum + rNum
// 三数之和小于0,则左指针向右移动
if (threeSum < 0) l++
else if (threeSum > 0) r--
else {
res.push([iNum, lNum, rNum])
// 去重
while(l < r && nums[l] == nums[l + 1]){
l++
}
while(l < r && nums[r] == nums[r - 1]) {
r--
}
l++
r--
}
}
}
return res
};
```
解法二:nSum通用解法。递归
```js
/**
* nsum通用解法,支持2sum,3sum,4sum...等等
* 时间复杂度分析:
* 1. n = 2时,时间复杂度O(NlogN),排序所消耗的时间。、
* 2. n > 2时,时间复杂度为O(N^n-1),即N的n-1次方,至少是2次方,此时可省略排序所消耗的时间。举例:3sum为O(n^2),4sum为O(n^3)
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
// nsum通用解法核心方法
function nSumTarget(nums, n, start, target) {
// 前提:nums要先排序好
let res = [];
if (n === 2) {
res = towSumTarget(nums, start, target);
} else {
for (let i = start; i < nums.length; i++) {
// 递归求(n - 1)sum
let subRes = nSumTarget(
nums,
n - 1,
i + 1,
target - nums[i]
);
for (let j = 0; j < subRes.length; j++) {
res.push([nums[i], ...subRes[j]]);
}
// 跳过相同元素
while (nums[i] === nums[i + 1]) i++;
}
}
return res;
}
function towSumTarget(nums, start, target) {
// 前提:nums要先排序好
let res = [];
let len = nums.length;
let left = start;
let right = len - 1;
while (left < right) {
let sum = nums[left] + nums[right];
if (sum < target) {
while (nums[left] === nums[left + 1]) left++;
left++;
} else if (sum > target) {
while (nums[right] === nums[right - 1]) right--;
right--;
} else {
// 相等
res.push([nums[left], nums[right]]);
// 跳过相同元素
while (nums[left] === nums[left + 1]) left++;
while (nums[right] === nums[right - 1]) right--;
left++;
right--;
}
}
return res;
}
nums.sort((a, b) => a - b);
// n = 3,此时求3sum之和
return nSumTarget(nums, 3, 0, 0);
};
```
### TypeScript:
```typescript
function threeSum(nums: number[]): number[][] {
nums.sort((a, b) => a - b);
let length = nums.length;
let left: number = 0,
right: number = length - 1;
let resArr: number[][] = [];
for (let i = 0; i < length; i++) {
if (nums[i]>0) {
return resArr; //nums经过排序后,只要nums[i]>0, 此后的nums[i] + nums[left] + nums[right]均大于0,可以提前终止循环。
}
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
left = i + 1;
right = length - 1;
while (left < right) {
let total: number = nums[i] + nums[left] + nums[right];
if (total === 0) {
resArr.push([nums[i], nums[left], nums[right]]);
left++;
right--;
while (nums[right] === nums[right + 1]) {
right--;
}
while (nums[left] === nums[left - 1]) {
left++;
}
} else if (total < 0) {
left++;
} else {
right--;
}
}
}
return resArr;
};
```
### Ruby:
```ruby
def is_valid(strs)
symbol_map = {')' => '(', '}' => '{', ']' => '['}
stack = []
strs.size.times {|i|
c = strs[i]
if symbol_map.has_key?(c)
top_e = stack.shift
return false if symbol_map[c] != top_e
else
stack.unshift(c)
end
}
stack.empty?
end
```
### PHP:
```php
class Solution {
/**
* @param Integer[] $nums
* @return Integer[][]
*/
function threeSum($nums) {
$res = [];
sort($nums);
for ($i = 0; $i < count($nums); $i++) {
if ($nums[$i] > 0) {
return $res;
}
if ($i > 0 && $nums[$i] == $nums[$i - 1]) {
continue;
}
$left = $i + 1;
$right = count($nums) - 1;
while ($left < $right) {
$sum = $nums[$i] + $nums[$left] + $nums[$right];
if ($sum < 0) {
$left++;
}
else if ($sum > 0) {
$right--;
}
else {
$res[] = [$nums[$i], $nums[$left], $nums[$right]];
while ($left < $right && $nums[$left] == $nums[$left + 1]) $left++;
while ($left < $right && $nums[$right] == $nums[$right - 1]) $right--;
$left++;
$right--;
}
}
}
return $res;
}
}
```
### Swift:
```swift
// 双指针法
func threeSum(_ nums: [Int]) -> [[Int]] {
var res = [[Int]]()
var sorted = nums
sorted.sort()
for i in 0 ..< sorted.count {
if sorted[i] > 0 {
return res
}
if i > 0 && sorted[i] == sorted[i - 1] {
continue
}
var left = i + 1
var right = sorted.count - 1
while left < right {
let sum = sorted[i] + sorted[left] + sorted[right]
if sum < 0 {
left += 1
} else if sum > 0 {
right -= 1
} else {
res.append([sorted[i], sorted[left], sorted[right]])
while left < right && sorted[left] == sorted[left + 1] {
left += 1
}
while left < right && sorted[right] == sorted[right - 1] {
right -= 1
}
left += 1
right -= 1
}
}
}
return res
}
```
### Rust:
```Rust
// 哈希解法
use std::collections::HashSet;
impl Solution {
pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut result: Vec<Vec<i32>> = Vec::new();
let mut nums = nums;
nums.sort();
let len = nums.len();
for i in 0..len {
if nums[i] > 0 { break; }
if i > 0 && nums[i] == nums[i - 1] { continue; }
let mut set = HashSet::new();
for j in (i + 1)..len {
if j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2] { continue; }
let c = 0 - (nums[i] + nums[j]);
if set.contains(&c) {
result.push(vec![nums[i], nums[j], c]);
set.remove(&c);
} else { set.insert(nums[j]); }
}
}
result
}
}
```
```Rust
// 双指针法
use std::cmp::Ordering;
impl Solution {
pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut result: Vec<Vec<i32>> = Vec::new();
let mut nums = nums;
nums.sort();
let len = nums.len();
for i in 0..len {
if nums[i] > 0 { return result; }
if i > 0 && nums[i] == nums[i - 1] { continue; }
let (mut left, mut right) = (i + 1, len - 1);
while left < right {
match (nums[i] + nums[left] + nums[right]).cmp(&0){
Ordering::Equal =>{
result.push(vec![nums[i], nums[left], nums[right]]);
left +=1;
right -=1;
while left < right && nums[left] == nums[left - 1]{
left += 1;
}
while left < right && nums[right] == nums[right+1]{
right -= 1;
}
}
Ordering::Greater => right -= 1,
Ordering::Less => left += 1,
}
}
}
result
}
}
```
### C:
```C
//qsort辅助cmp函数
int cmp(const void* ptr1, const void* ptr2) {
return *((int*)ptr1) > *((int*)ptr2);
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
//开辟ans数组空间
int **ans = (int**)malloc(sizeof(int*) * 18000);
int ansTop = 0;
//若传入nums数组大小小于3,则需要返回数组大小为0
if(numsSize < 3) {
*returnSize = 0;
return ans;
}
//对nums数组进行排序
qsort(nums, numsSize, sizeof(int), cmp);
int i;
//用for循环遍历数组,结束条件为i < numsSize - 2(因为要预留左右指针的位置)
for(i = 0; i < numsSize - 2; i++) {
//若当前i指向元素>0,则代表left和right以及i的和大于0。直接break
if(nums[i] > 0)
break;
//去重:i > 0 && nums[i] == nums[i-1]
if(i > 0 && nums[i] == nums[i-1])
continue;
//定义左指针和右指针
int left = i + 1;
int right = numsSize - 1;
//当右指针比左指针大时进行循环
while(right > left) {
//求出三数之和
int sum = nums[right] + nums[left] + nums[i];
//若和小于0,则左指针+1(因为左指针右边的数比当前所指元素大)
if(sum < 0)
left++;
//若和大于0,则将右指针-1
else if(sum > 0)
right--;
//若和等于0
else {
//开辟一个大小为3的数组空间,存入nums[i], nums[left]和nums[right]
int* arr = (int*)malloc(sizeof(int) * 3);
arr[0] = nums[i];
arr[1] = nums[left];
arr[2] = nums[right];
//将开辟数组存入ans中
ans[ansTop++] = arr;
//去重
while(right > left && nums[right] == nums[right - 1])
right--;
while(left < right && nums[left] == nums[left + 1])
left++;
//更新左右指针
left++;
right--;
}
}
}
//设定返回的数组大小
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int z;
for(z = 0; z < ansTop; z++) {
(*returnColumnSizes)[z] = 3;
}
return ans;
}
```
### C#:
```csharp
public class Solution
{
public IList<IList<int>> ThreeSum(int[] nums)
{
var result = new List<IList<int>>();
Array.Sort(nums);
for (int i = 0; i < nums.Length - 2; i++)
{
int n1 = nums[i];
if (n1 > 0)
break;
if (i > 0 && n1 == nums[i - 1])
continue;
int left = i + 1;
int right = nums.Length - 1;
while (left < right)
{
int n2 = nums[left];
int n3 = nums[right];
int sum = n1 + n2 + n3;
if (sum > 0)
{
right--;
}
else if (sum < 0)
{
left++;
}
else
{
result.Add(new List<int> { n1, n2, n3 });
while (left < right && nums[left] == n2)
{
left++;
}
while (left < right && nums[right] == n3)
{
right--;
}
}
}
}
return result;
}
}
```
### Scala:
```scala
object Solution {
// 导包
import scala.collection.mutable.ListBuffer
import scala.util.control.Breaks.{break, breakable}
def threeSum(nums: Array[Int]): List[List[Int]] = {
// 定义结果集,最后需要转换为List
val res = ListBuffer[List[Int]]()
val nums_tmp = nums.sorted // 对nums进行排序
for (i <- nums_tmp.indices) {
// 如果要排的第一个数字大于0,直接返回结果
if (nums_tmp(i) > 0) {
return res.toList
}
// 如果i大于0并且和前一个数字重复,则跳过本次循环,相当于continue
breakable {
if (i > 0 && nums_tmp(i) == nums_tmp(i - 1)) {
break
} else {
var left = i + 1
var right = nums_tmp.length - 1
while (left < right) {
var sum = nums_tmp(i) + nums_tmp(left) + nums_tmp(right) // 求三数之和
if (sum < 0) left += 1
else if (sum > 0) right -= 1
else {
res += List(nums_tmp(i), nums_tmp(left), nums_tmp(right)) // 如果等于0 添加进结果集
// 为了避免重复,对left和right进行移动
while (left < right && nums_tmp(left) == nums_tmp(left + 1)) left += 1
while (left < right && nums_tmp(right) == nums_tmp(right - 1)) right -= 1
left += 1
right -= 1
}
}
}
}
}
// 最终返回需要转换为List,return关键字可以省略
res.toList
}
}
```
================================================
FILE: problems/0017.电话号码的字母组合.md
================================================
* [做项目(多个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)
# 17.电话号码的字母组合
[力扣题目链接](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:
* 输入:"23"
* 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)::[还得用回溯算法!| LeetCode:17.电话号码的字母组合](https://www.bilibili.com/video/BV1yV4y1V7Ug),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
从示例上来说,输入"23",最直接的想法就是两层for循环遍历了吧,正好把组合的情况都输出了。
如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环.......
大家应该感觉出和[77.组合](https://programmercarl.com/0077.组合.html)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。
理解本题后,要解决如下三个问题:
1. 数字和字母如何映射
2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
3. 输入1 * #按键等等异常情况
### 数字和字母如何映射
可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下:
```cpp
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
```
### 回溯法来解决n个for循环的问题
对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)
例如:输入:"23",抽象为树形结构,如图所示:

图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。
回溯三部曲:
* 确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
注意这个index可不是 [77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)中的startIndex了。
这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
代码如下:
```cpp
vector<string> result;
string s;
void backtracking(const string& digits, int index)
```
* 确定终止条件
例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。
然后收集结果,结束本层递归。
代码如下:
```cpp
if (index == digits.size()) {
result.push_back(s);
return;
}
```
* 确定单层遍历逻辑
首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。
然后for循环来处理这个字符集,代码如下:
```CPP
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = letterMap[digit]; // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
```
**注意这里for循环,可不像是在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的**。
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)都是求同一个集合中的组合!**
注意:输入1 * #按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
**但是要知道会有这些异常,如果是现场面试中,一定要考虑到!**
关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中的回溯法模板,不难写出如下C++代码:
```CPP
// 版本一
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
public:
vector<string> result;
string s;
void backtracking(const string& digits, int index) {
if (index == digits.size()) {
result.push_back(s);
return;
}
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = letterMap[digit]; // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
}
vector<string> letterCombinations(string digits) {
s.clear();
result.clear();
if (digits.size() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}
};
```
* 时间复杂度: O(3^m * 4^n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
* 空间复杂度: O(3^m * 4^n)
一些写法,是把回溯的过程放在递归函数里了,例如如下代码,我可以写成这样:(注意注释中不一样的地方)
```CPP
// 版本二
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
public:
vector<string> result;
void getCombinations(const string& digits, int index, const string& s) { // 注意参数的不同
if (index == digits.size()) {
result.push_back(s);
return;
}
int digit = digits[index] - '0';
string letters = letterMap[digit];
for (int i = 0; i < letters.size(); i++) {
getCombinations(digits, index + 1, s + letters[i]); // 注意这里的不同
}
}
vector<string> letterCombinations(string digits) {
result.clear();
if (digits.size() == 0) {
return result;
}
getCombinations(digits, 0, "");
return result;
}
};
```
我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)这篇文章中也深度分析了,回溯隐藏在了哪里。
所以大家可以按照版本一来写就可以了。
## 总结
本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。
其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。
## 其他语言版本
### Java
```Java
class Solution {
//设置全局列表存储最后的结果
List<String> list = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
//迭代处理
backTracking(digits, numString, 0);
return list;
}
//每次迭代获取一个字符串,所以会涉及大量的字符串拼接,所以这里选择更为高效的 StringBuilder
StringBuilder temp = new StringBuilder();
//比如digits如果为"23",num 为0,则str表示2对应的 abc
public void backTracking(String digits, String[] numString, int num) {
//遍历全部一次记录一次得到的字符串
if (num == digits.length()) {
list.add(temp.toString());
return;
}
//str 表示当前num对应的字符串
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i));
//递归,处理下一层
backTracking(digits, numString, num + 1);
//剔除末尾的继续尝试
temp.deleteCharAt(temp.length() - 1);
}
}
}
```
### Python
回溯
```python
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
self.result = []
self.s = ""
def backtracking(self, digits, index):
if index == len(digits):
self.result.append(self.s)
return
digit = int(digits[index]) # 将索引处的数字转换为整数
letters = self.letterMap[digit] # 获取对应的字符集
for i in range(len(letters)):
self.s += letters[i] # 处理字符
self.backtracking(digits, index + 1) # 递归调用,注意索引加1,处理下一个数字
self.s = self.s[:-1] # 回溯,删除最后添加的字符
def letterCombinations(self, digits):
if len(digits) == 0:
return self.result
self.backtracking(digits, 0)
return self.result
```
回溯精简(版本一)
```python
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
self.result = []
def getCombinations(self, digits, index, s):
if index == len(digits):
self.result.append(s)
return
digit = int(digits[index])
letters = self.letterMap[digit]
for letter in letters:
self.getCombinations(digits, index + 1, s + letter)
def letterCombinations(self, digits):
if len(digits) == 0:
return self.result
self.getCombinations(digits, 0, "")
return self.result
```
回溯精简(版本二)
```python
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
def getCombinations(self, digits, index, s, result):
if index == len(digits):
result.append(s)
return
digit = int(digits[index])
letters = self.letterMap[digit]
for letter in letters:
self.getCombinations(digits, index + 1, s + letter, result)
def letterCombinations(self, digits):
result = []
if len(digits) == 0:
return result
self.getCombinations(digits, 0, "", result)
return result
```
回溯优化使用列表
```python
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
def getCombinations(self, digits, index, path, result):
if index == len(digits):
result.append(''.join(path))
return
digit = int(digits[index])
letters = self.letterMap[digit]
for letter in letters:
path.append(letter)
self.getCombinations(digits, index + 1, path, result)
path.pop()
def letterCombinations(self, digits):
result = []
if len(digits) == 0:
return result
self.getCombinations(digits, 0, [], result)
return result
```
### Go
主要在于递归中传递下一个数字
```go
var (
m []string
path []byte
res []string
)
func letterCombinations(digits string) []string {
m = []string{"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
path, res = make([]byte, 0), make([]string, 0)
if digits == "" {
return res
}
dfs(digits, 0)
return res
}
func dfs(digits string, start int) {
if len(path) == len(digits) { //终止条件,字符串长度等于digits的长度
tmp := string(path)
res = append(res, tmp)
return
}
digit := int(digits[start] - '0') // 将index指向的数字转为int(确定下一个数字)
str := m[digit-2] // 取数字对应的字符集(注意和map中的对应)
for j := 0; j < len(str); j++ {
path = append(path, str[j])
dfs(digits, start+1)
path = path[:len(path)-1]
}
}
```
### JavaScript
```js
var letterCombinations = function(digits) {
const k = digits.length;
const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
if(!k) return [];
if(k === 1) return map[digits].split("");
const res = [], path = [];
backtracking(digits, k, 0);
return res;
function backtracking(n, k, a) {
if(path.length === k) {
res.push(path.join(""));
return;
}
for(const v of map[n[a]]) {
path.push(v);
backtracking(n, k, a + 1);
path.pop();
}
}
};
```
### TypeScript
```typescript
function letterCombinations(digits: string): string[] {
if (digits === '') return [];
const strMap: { [index: string]: string[] } = {
1: [],
2: ['a', 'b', 'c'],
3: ['d', 'e', 'f'],
4: ['g', 'h', 'i'],
5: ['j', 'k', 'l'],
6: ['m', 'n', 'o'],
7: ['p', 'q', 'r', 's'],
8: ['t', 'u', 'v'],
9: ['w', 'x', 'y', 'z'],
}
const resArr: string[] = [];
function backTracking(digits: string, curIndex: number, route: string[]): void {
if (curIndex === digits.length) {
resArr.push(route.join(''));
return;
}
let tempArr: string[] = strMap[digits[curIndex]];
for (let i = 0, length = tempArr.length; i < length; i++) {
route.push(tempArr[i]);
backTracking(digits, curIndex + 1, route);
route.pop();
}
}
backTracking(digits, 0, []);
return resArr;
};
```
### Rust
```Rust
const map: [&str; 10] = [
"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz",
];
impl Solution {
fn back_trace(result: &mut Vec<String>, s: &mut String, digits: &String, index: usize) {
let len = digits.len();
if len == index {
result.push(s.to_string());
return;
}
let digit = (digits.as_bytes()[index] - b'0') as usize;
for i in map[digit].chars() {
s.push(i);
Self::back_trace(result, s, digits, index + 1);
s.pop();
}
}
pub fn letter_combinations(digits: String) -> Vec<String> {
if digits.is_empty() {
return vec![];
}
let mut res = vec![];
let mut s = String::new();
Self::back_trace(&mut res, &mut s, &digits, 0);
res
}
}
```
### C
```c
char* path;
int pathTop;
char** result;
int resultTop;
char* letterMap[10] = {"", //0
"", //1
"abc", //2
"def", //3
"ghi", //4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz", //9
};
void backTracking(char* digits, int index) {
//若当前下标等于digits数组长度
if(index == strlen(digits)) {
//复制digits数组,因为最后要多存储一个0,所以数组长度要+1
char* tempString = (char*)malloc(sizeof(char) * strlen(digits) + 1);
int j;
for(j = 0; j < strlen(digits); j++) {
tempString[j] = path[j];
}
//char数组最后要以0结尾
tempString[strlen(digits)] = 0;
result[resultTop++] = tempString;
return ;
}
//将字符数字转换为真的数字
int digit = digits[index] - '0';
//找到letterMap中对应的字符串
char* letters = letterMap[digit];
int i;
for(i = 0; i < strlen(letters); i++) {
path[pathTop++] = letters[i];
//递归,处理下一层数字
backTracking(digits, index+1);
pathTop--;
}
}
char ** letterCombinations(char * digits, int* returnSize){
//初始化path和result
path = (char*)malloc(sizeof(char) * strlen(digits));
result = (char**)malloc(sizeof(char*) * 300);
*returnSize = 0;
//若digits数组中元素个数为0,返回空集
if(strlen(digits) == 0)
return result;
pathTop = resultTop = 0;
backTracking(digits, 0);
*returnSize = resultTop;
return result;
}
```
### Swift
```swift
func letterCombinations(_ digits: String) -> [String] {
// 按键与字母串映射
let letterMap = [
"",
"", "abc", "def",
"ghi", "jkl", "mno",
"pqrs", "tuv", "wxyz"
]
// 把输入的按键字符串转成Int数组
let baseCode = ("0" as Character).asciiValue!
let digits = digits.map { c in
guard let code = c.asciiValue else { return -1 }
return Int(code - baseCode)
}.filter { $0 >= 0 && $0 <= 9 }
guard !digits.isEmpty else { return [] }
var result = [String]()
var s = ""
func backtracking(index: Int) {
// 结束条件:收集结果
if index == digits.count {
result.append(s)
return
}
// 遍历当前按键对应的字母串
let letters = letterMap[digits[index]]
for letter in letters {
s.append(letter) // 处理
backtracking(index: index + 1) // 递归,记得+1
s.removeLast() // 回溯
}
}
backtracking(index: 0)
return result
}
```
### Scala
```scala
object Solution {
import scala.collection.mutable
def letterCombinations(digits: String): List[String] = {
var result = mutable.ListBuffer[String]()
if(digits == "") return result.toList // 如果参数为空,返回空结果集的List形式
var path = mutable.ListBuffer[Char]()
// 数字和字符的映射关系
val map = Array[String]("", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz")
def backtracking(index: Int): Unit = {
if (index == digits.size) {
result.append(path.mkString) // mkString语法:将数组类型直接转换为字符串
return
}
var digit = digits(index) - '0' // 这里使用toInt会报错!必须 -'0'
for (i <- 0 until map(digit).size) {
path.append(map(digit)(i))
backtracking(index + 1)
path = path.take(path.size - 1)
}
}
backtracking(0)
result.toList
}
}
```
### Ruby
```ruby
def letter_combinations(digits)
letter_map = {
2 => ['a','b','c'],
3 => ['d','e','f'],
4 => ['g','h','i'],
5 => ['j','k','l'],
6 => ['m','n','o'],
7 => ['p','q','r','s'],
8 => ['t','u','v'],
9 => ['w','x','y','z']
}
result = []
path = []
return result if digits.size == 0
backtracking(result, letter_map, digits.split(''), path, 0)
result
end
def backtracking(result, letter_map, digits, path, index)
if path.size == digits.size
result << path.join('')
return
end
hash[digits[index].to_i].each do |chr|
path << chr
#index + 1代表处理下一个数字
backtracking(result, letter_map, digits, path, index + 1)
#回溯,撤销处理过的数字
path.pop
end
end
```
### C#
```csharp
public class Solution
{
public IList<string> res = new List<string>();
public string s;
public string[] letterMap = new string[10] { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };
public IList<string> LetterCombinations(string digits)
{
if (digits.Length == 0)
return res;
BackTracking(digits, 0);
return res;
}
public void BackTracking(string digits, int index)
{
if (index == digits.Length)
{
res.Add(s);
return;
}
int digit = digits[index] - '0';
string letters = letterMap[digit];
for (int i = 0; i < letters.Length; i++)
{
s += letters[i];
BackTracking(digits, index + 1);
s = s.Substring(0, s.Length - 1);
}
}
}
```
================================================
FILE: problems/0018.四数之和.md
================================================
* [做项目(多个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)
> 一样的道理,能解决四数之和
> 那么五数之和、六数之和、N数之和呢?
# 第18题. 四数之和
[力扣题目链接](https://leetcode.cn/problems/4sum/)
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
**注意:**
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[难在去重和剪枝!| LeetCode:18. 四数之和](https://www.bilibili.com/video/BV1DS4y147US),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]`,`target`是`-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[k] > target && (nums[k] >=0 || target >= 0)`就可以了。
[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
那么一样的道理,五数之和、六数之和等等都采用这种解法。
对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
之前我们讲过哈希表的经典题目:[454.四数相加II](https://programmercarl.com/0454.四数相加II.html),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。
而[454.四数相加II](https://programmercarl.com/0454.四数相加II.html)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少!
我们来回顾一下,几道题目使用了双指针法。
双指针法将时间复杂度:O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下:
* [27.移除元素](https://programmercarl.com/0027.移除元素.html)
* [15.三数之和](https://programmercarl.com/0015.三数之和.html)
* [18.四数之和](https://programmercarl.com/0018.四数之和.html)
链表相关双指针题目:
* [206.反转链表](https://programmercarl.com/0206.翻转链表.html)
* [19.删除链表的倒数第N个节点](https://programmercarl.com/0019.删除链表的倒数第N个节点.html)
* [面试题 02.07. 链表相交](https://programmercarl.com/面试题02.07.链表相交.html)
* [142题.环形链表II](https://programmercarl.com/0142.环形链表II.html)
双指针法在字符串题目中还有很多应用,后面还会介绍到。
C++代码
```CPP
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break; // 这里使用break,统一通过最后的return返回
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
// 2级剪枝处理
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
right--;
// nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
} else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) {
left++;
} else {
result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
}
return result;
}
};
```
* 时间复杂度: O(n^3)
* 空间复杂度: O(1)
## 补充
二级剪枝的部分:
```C++
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
```
可以优化为:
```C++
if (nums[k] + nums[i] > target && nums[i] >= 0) {
break;
}
```
因为只要 nums[k] + nums[i] > target,那么 nums[i] 后面的数都是正数的话,就一定 不符合条件了。
不过这种剪枝 其实有点 小绕,大家能够理解 文章给的完整代码的剪枝 就够了。
## 其他语言版本
### C:
```C
/* qsort */
static int cmp(const void* arg1, const void* arg2) {
int a = *(int *)arg1;
int b = *(int *)arg2;
return (a > b);
}
int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes) {
/* 对nums数组进行排序 */
qsort(nums, numsSize, sizeof(int), cmp);
int **res = (int **)malloc(sizeof(int *) * 40000);
int index = 0;
/* k */
for (int k = 0; k < numsSize - 3; k++) { /* 第一级 */
/* k剪枝 */
if ((nums[k] > target) && (nums[k] >= 0)) {
break;
}
/* k去重 */
if ((k > 0) && (nums[k] == nums[k - 1])) {
continue;
}
/* i */
for (int i = k + 1; i < numsSize - 2; i++) { /* 第二级 */
/* i剪枝 */
if ((nums[k] + nums[i] > target) && (nums[i] >= 0)) {
break;
}
/* i去重 */
if ((i > (k + 1)) && (nums[i] == nums[i - 1])) {
continue;
}
/* left and right */
int left = i + 1;
int right = numsSize - 1;
while (left < right) {
/* 防止大数溢出 */
long long val = (long long)nums[k] + nums[i] + nums[left] + nums[right];
if (val > target) {
right--;
} else if (val < target) {
left++;
} else {
int *res_tmp = (int *)malloc(sizeof(int) * 4);
res_tmp[0] = nums[k];
res_tmp[1] = nums[i];
res_tmp[2] = nums[left];
res_tmp[3] = nums[right];
res[index++] = res_tmp;
/* right去重 */
while ((right > left) && (nums[right] == nums[right - 1])) {
right--;
}
/* left去重 */
while ((left < right) && (nums[left] == nums[left + 1])) {
left++;
}
/* 更新right与left */
left++, right--;
}
}
}
}
/* 返回值处理 */
*returnSize = index;
int *column = (int *)malloc(sizeof(int) * index);
for (int i = 0; i < index; i++) {
column[i] = 4;
}
*returnColumnSizes = column;
return res;
}
```
### Java:
```Java
import java.util.*;
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums); // 排序数组
List<List<Integer>> result = new ArrayList<>(); // 结果集
for (int k = 0; k < nums.length; k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break; // 此处的break可以等价于return result;
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.length; i++) {
// 第二级剪枝
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break; // 注意是break到上一级for循环,如果直接return result;会有遗漏
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
}
return result;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 0, -1, 0, -2, 2};
int target = 0;
List<List<Integer>> results = solution.fourSum(nums, target);
for (List<Integer> result : results) {
System.out.println(result);
}
}
}
```
### Python:
(版本一) 双指针
```python
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
result = []
for i in range(n):
if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
break
if i > 0 and nums[i] == nums[i-1]:# 去重
continue
for j in range(i+1, n):
if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
break
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
left, right = j+1, n-1
while left < right:
s = nums[i] + nums[j] + nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result
```
(版本二) 使用字典
```python
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
# 创建一个字典来存储输入列表中每个数字的频率
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
# 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
ans = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
val = target - (nums[i] + nums[j] + nums[k])
if val in freq:
# 确保没有重复
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if freq[val] > count:
ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
return [list(x) for x in ans]
```
### Go:
```go
func fourSum(nums []int, target int) [][]int {
if len(nums) < 4 {
return nil
}
sort.Ints(nums)
var res [][]int
for i := 0; i < len(nums)-3; i++ {
n1 := nums[i]
// if n1 > target { // 不能这样写,因为可能是负数
// break
// }
if i > 0 && n1 == nums[i-1] { // 对nums[i]去重
continue
}
for j := i + 1; j < len(nums)-2; j++ {
n2 := nums[j]
if j > i+1 && n2 == nums[j-1] { // 对nums[j]去重
continue
}
l := j + 1
r := len(nums) - 1
for l < r {
n3 := nums[l]
n4 := nums[r]
sum := n1 + n2 + n3 + n4
if sum < target {
l++
} else if sum > target {
r--
} else {
res = append(res, []int{n1, n2, n3, n4})
for l < r && n3 == nums[l+1] { // 去重
l++
}
for l < r && n4 == nums[r-1] { // 去重
r--
}
// 找到答案时,双指针同时靠近
r--
l++
}
}
}
}
return res
}
```
### JavaScript:
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function(nums, target) {
const len = nums.length;
if(len < 4) return [];
nums.sort((a, b) => a - b);
const res = [];
for(let i = 0; i < len - 3; i++) {
// 去重i
if(i > 0 && nums[i] === nums[i - 1]) continue;
for(let j = i + 1; j < len - 2; j++) {
// 去重j
if(j > i + 1 && nums[j] === nums[j - 1]) continue;
let l = j + 1, r = len - 1;
while(l < r) {
const sum = nums[i] + nums[j] + nums[l] + nums[r];
if(sum < target) { l++; continue}
if(sum > target) { r--; continue}
res.push([nums[i], nums[j], nums[l], nums[r]]);
// 对nums[left]和nums[right]去重
while(l < r && nums[l] === nums[++l]);
while(l < r && nums[r] === nums[--r]);
}
}
}
return res;
};
```
### TypeScript:
```typescript
function fourSum(nums: number[], target: number): number[][] {
nums.sort((a, b) => a - b);
let first: number = 0,
second: number,
third: number,
fourth: number;
let length: number = nums.length;
let resArr: number[][] = [];
for (; first < length; first++) {
if (first > 0 && nums[first] === nums[first - 1]) {
continue;
}
for (second = first + 1; second < length; second++) {
if ((second - first) > 1 && nums[second] === nums[second - 1]) {
continue;
}
third = second + 1;
fourth = length - 1;
while (third < fourth) {
let total: number = nums[first] + nums[second] + nums[third] + nums[fourth];
if (total === target) {
resArr.push([nums[first], nums[second], nums[third], nums[fourth]]);
third++;
fourth--;
while (nums[third] === nums[third - 1]) third++;
while (nums[fourth] === nums[fourth + 1]) fourth--;
} else if (total < target) {
third++;
} else {
fourth--;
}
}
}
}
return resArr;
};
```
### PHP:
```php
class Solution {
/**
* @param Integer[] $nums
* @param Integer $target
* @return Integer[][]
*/
function fourSum($nums, $target) {
$res = [];
sort($nums);
for ($i = 0; $i < count($nums); $i++) {
if ($i > 0 && $nums[$i] == $nums[$i - 1]) {
continue;
}
for ($j = $i + 1; $j < count($nums); $j++) {
if ($j > $i + 1 && $nums[$j] == $nums[$j - 1]) {
continue;
}
$left = $j + 1;
$right = count($nums) - 1;
while ($left < $right) {
$sum = $nums[$i] + $nums[$j] + $nums[$left] + $nums[$right];
if ($sum < $target) {
$left++;
}
else if ($sum > $target) {
$right--;
}
else {
$res[] = [$nums[$i], $nums[$j], $nums[$left], $nums[$right]];
while ($left < $right && $nums[$left] == $nums[$left+1]) $left++;
while ($left < $right && $nums[$right] == $nums[$right-1]) $right--;
$left++;
$right--;
}
}
}
}
return $res;
}
}
```
### Swift:
```swift
func fourSum(_ nums: [Int], _ target: Int) -> [[Int]] {
var res = [[Int]]()
var sorted = nums
sorted.sort()
for k in 0 ..< sorted.count {
// 这种剪枝不行,target可能是负数
// if sorted[k] > target {
// return res
// }
// 去重
if k > 0 && sorted[k] == sorted[k - 1] {
continue
}
let target2 = target - sorted[k]
for i in (k + 1) ..< sorted.count {
if i > (k + 1) && sorted[i] == sorted[i - 1] {
continue
}
var left = i + 1
var right = sorted.count - 1
while left < right {
let sum = sorted[i] + sorted[left] + sorted[right]
if sum < target2 {
left += 1
} else if sum > target2 {
right -= 1
} else {
res.append([sorted[k], sorted[i], sorted[left], sorted[right]])
while left < right && sorted[left] == sorted[left + 1] {
left += 1
}
while left < right && sorted[right] == sorted[right - 1] {
right -= 1
}
// 找到答案 双指针同时收缩
left += 1
right -= 1
}
}
}
}
return res
}
```
### C#:
```csharp
public class Solution
{
public IList<IList<int>> FourSum(int[] nums, int target)
{
var result = new List<IList<int>>();
Array.Sort(nums);
for (int i = 0; i < nums.Length - 3; i++)
{
int n1 = nums[i];
if (i > 0 && n1 == nums[i - 1])
continue;
for (int j = i + 1; j < nums.Length - 2; j++)
{
int n2 = nums[j];
if (j > i + 1 && n2 == nums[j - 1])
continue;
int left = j + 1;
int right = nums.Length - 1;
while (left < right)
{
int n3 = nums[left];
int n4 = nums[right];
int sum = n1 + n2 + n3 + n4;
if (sum > target)
{
right--;
}
else if (sum < target)
{
left++;
}
else
{
result.Add(new List<int> { n1, n2, n3, n4 });
while (left < right && nums[left] == n3)
{
left++;
}
while (left < right && nums[right] == n4)
{
right--;
}
}
}
}
}
return result;
}
}
```
### Rust:
```Rust
use std::cmp::Ordering;
impl Solution {
pub fn four_sum(nums: Vec<i32>, target: i32) -> Vec<Vec<i32>> {
let mut result: Vec<Vec<i32>> = Vec::new();
let mut nums = nums;
nums.sort();
let len = nums.len();
for k in 0..len {
// 剪枝
if nums[k] > target && (nums[k] > 0 || target > 0) { break; }
// 去重
if k > 0 && nums[k] == nums[k - 1] { continue; }
for i in (k + 1)..len {
// 剪枝
if nums[k] + nums[i] > target && (nums[k] + nums[i] >= 0 || target >= 0) { break; }
// 去重
if i > k + 1 && nums[i] == nums[i - 1] { continue; }
let (mut left, mut right) = (i + 1, len - 1);
while left < right {
match (nums[k] + nums[i] + nums[left] + nums[right]).cmp(&target){
Ordering::Equal => {
result.push(vec![nums[k], nums[i], nums[left], nums[right]]);
left += 1;
right -= 1;
while left < right && nums[left] == nums[left - 1]{
left += 1;
}
while left < right && nums[right] == nums[right + 1]{
right -= 1;
}
}
Ordering::Less => {
left +=1;
},
Ordering::Greater => {
right -= 1;
}
}
}
}
}
result
}
}
```
### Scala:
```scala
object Solution {
// 导包
import scala.collection.mutable.ListBuffer
import scala.util.control.Breaks.{break, breakable}
def fourSum(nums: Array[Int], target: Int): List[List[Int]] = {
val res = ListBuffer[List[Int]]()
val nums_tmp = nums.sorted // 先排序
for (i <- nums_tmp.indices) {
breakable {
if (i > 0 && nums_tmp(i) == nums_tmp(i - 1)) {
break // 如果该值和上次的值相同,跳过本次循环,相当于continue
} else {
for (j <- i + 1 until nums_tmp.length) {
breakable {
if (j > i + 1 && nums_tmp(j) == nums_tmp(j - 1)) {
break // 同上
} else {
// 双指针
var (left, right) = (j + 1, nums_tmp.length - 1)
while (left < right) {
var sum = nums_tmp(i) + nums_tmp(j) + nums_tmp(left) + nums_tmp(right)
if (sum == target) {
// 满足要求,直接加入到集合里面去
res += List(nums_tmp(i), nums_tmp(j), nums_tmp(left), nums_tmp(right))
while (left < right && nums_tmp(left) == nums_tmp(left + 1)) left += 1
while (left < right && nums_tmp(right) == nums_tmp(right - 1)) right -= 1
left += 1
right -= 1
} else if (sum < target) left += 1
else right -= 1
}
}
}
}
}
}
}
// 最终返回的res要转换为List,return关键字可以省略
res.toList
}
}
```
### Ruby:
```ruby
def four_sum(nums, target)
#结果集
result = []
nums = nums.sort!
for i in 0..nums.size - 1
return result if i > 0 && nums[i] > target && nums[i] >= 0
#对a进行去重
next if i > 0 && nums[i] == nums[i - 1]
for j in i + 1..nums.size - 1
break if nums[i] + nums[j] > target && nums[i] + nums[j] >= 0
#对b进行去重
next if j > i + 1 && nums[j] == nums[j - 1]
left = j + 1
right = nums.size - 1
while left < right
sum = nums[i] + nums[j] + nums[left] + nums[right]
if sum > target
right -= 1
elsif sum < target
left += 1
else
result << [nums[i], nums[j], nums[left], nums[right]]
#对c进行去重
while left < right && nums[left] == nums[left + 1]
left += 1
end
#对d进行去重
while left < right && nums[right] == nums[right - 1]
right -= 1
end
right -= 1
left += 1
end
end
end
end
return result
end
```
================================================
FILE: problems/0019.删除链表的倒数第N个节点.md
================================================
* [做项目(多个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)
# 19.删除链表的倒数第N个节点
[力扣题目链接](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)::[链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点](https://www.bilibili.com/video/BV1vW4y1U7Gf),相信结合视频再看本篇题解,更有助于大家对链表的理解。**
## 思路
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
思路是这样的,但要注意一些细节。
分为如下几步:
* 首先这里我推荐大家使用虚拟头结点,这样方便处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)
* 定义fast指针和slow指针,初始值为虚拟头结点,如图:
<img src='https://file1.kamacoder.com/i/algo/19.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9.png' width=600> </img></div>
* fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
<img src='https://file1.kamacoder.com/i/algo/19.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B91.png' width=600> </img></div>
* fast和slow同时移动,直到fast指向末尾,如题:
<img src='https://file1.kamacoder.com/i/algo/19.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B92.png' width=600> </img></div>
//图片中有错别词:应该将“只到”改为“直到”
* 删除slow指向的下一个节点,如图:
<img src='https://file1.kamacoder.com/i/algo/19.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B93.png' width=600> </img></div>
此时不难写出如下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<Box<ListNode>>, mut n: i32) -> Option<Box<ListNode>> {
let mut dummy_head = Box::new(ListNode::new(0));
dummy_head.next = head;
let mut fast = &dummy_head.clone();
let mut slow = &mut dummy_head;
while n > 0 {
fast = fast.next.as_ref().unwrap();
n -= 1;
}
while fast.next.is_some() {
fast = fast.next.as_ref().unwrap();
slow = slow.next.as_mut().unwrap();
}
slow.next = slow.next.as_mut().unwrap().next.take();
dummy_head.next
}
}
```
### C:
```c
/**c语言单链表的定义
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
//定义虚拟头节点dummy 并初始化使其指向head
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0;
dummy->next = head;
//定义 fast slow 双指针
struct ListNode* fast = head;
struct ListNode* slow = dummy;
for (int i = 0; i < n; ++i) {
fast = fast->next;
}
while (fast) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;//删除倒数第n个节点
head = dummy->next;
free(dummy);//删除虚拟节点dummy
return head;
}
```
### C#:
```csharp
public class Solution {
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode dummpHead = new ListNode(0);
dummpHead.next = head;
var fastNode = dummpHead;
var slowNode = dummpHead;
while(n-- != 0 && fastNode != null)
{
fastNode = fastNode.next;
}
while(fastNode.next != null)
{
fastNode = fastNode.next;
slowNode = slowNode.next;
}
slowNode.next = slowNode.next.next;
return dummpHead.next;
}
}
```
================================================
FILE: problems/0020.有效的括号.md
================================================
* [做项目(多个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)
> 数据结构与算法应用往往隐藏在我们看不到的地方
# 20. 有效的括号
[力扣题目链接](https://leetcode.cn/problems/valid-parentheses/)
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 注意空字符串可被认为是有效字符串。
示例 1:
* 输入: "()"
* 输出: true
示例 2:
* 输入: "()[]{}"
* 输出: true
示例 3:
* 输入: "(]"
* 输出: false
示例 4:
* 输入: "([)]"
* 输出: false
示例 5:
* 输入: "{[]}"
* 输出: true
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[栈的拿手好戏!| LeetCode:20. 有效的括号](https://www.bilibili.com/video/BV1AF411w78g),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
### 题外话
**括号匹配是使用栈解决的经典问题。**
题意其实就像我们在写代码的过程中,要求括号的顺序是一样的,有左括号,相应的位置必须要有右括号。
如果还记得编译原理的话,编译器在 词法分析的过程中处理括号、花括号等这个符号的逻辑,也是使用了栈这种数据结构。
再举个例子,linux系统中,cd这个进入目录的命令我们应该再熟悉不过了。
```
cd a/b/c/../../
```
这个命令最后进入a目录,系统是如何知道进入了a目录呢 ,这就是栈的应用(其实可以出一道相应的面试题了)
所以栈在计算机领域中应用是非常广泛的。
有的同学经常会想学的这些数据结构有什么用,也开发不了什么软件,大多数同学说的软件应该都是可视化的软件例如APP、网站之类的,那都是非常上层的应用了,底层很多功能的实现都是基础的数据结构和算法。
**所以数据结构与算法的应用往往隐藏在我们看不到的地方!**
这里我就不过多展开了,先来看题。
### 进入正题
由于栈结构的特殊性,非常适合做对称匹配类的题目。
首先要弄清楚,字符串里的括号不匹配有几种情况。
**一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越乱。**
建议在写代码之前要分析好有哪几种不匹配的情况,如果不在动手之前分析好,写出的代码也会有很多问题。
先来分析一下 这里有三种不匹配的情况,
1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。

2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。

3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。

我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。
动画如下:

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
分析完之后,代码其实就比较好写了,
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
实现C++代码如下:
```CPP
class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
技巧性的东西没有固定的学习方法,还是要多看多练,自己灵活运用了。
## 其他语言版本
### Java:
```Java
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//遍历结束,如果栈为空,则括号全部匹配
return deque.isEmpty();
}
}
```
```java
// 解法二
// 对应的另一半一定在栈顶
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(char c : s.toCharArray()){
// 有对应的另一半就直接消消乐
if(c == ')' && !stack.isEmpty() && stack.peek() == '(')
stack.pop();
else if(c == '}' && !stack.isEmpty() && stack.peek() == '{')
stack.pop();
else if(c == ']' && !stack.isEmpty() && stack.peek() == '[')
stack.pop();
else
stack.push(c);// 没有匹配的就放进去
}
return stack.isEmpty();
}
}
```
### Python:
```python
# 方法一,仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
```
```python
# 方法二,使用字典
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for item in s:
if item in mapping.keys():
stack.append(mapping[item])
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
```
### Go:
```Go
// 思路: 使用栈来进行括号的匹配
// 时间复杂度 O(n)
// 空间复杂度 O(n)
func isValid(s string) bool {
// 使用切片模拟栈的行为
stack := make([]rune, 0)
// m 用于记录某个右括号对应的左括号
m := make(map[rune]rune)
m[')'] = '('
m[']'] = '['
m['}'] = '{'
// 遍历字符串中的 rune
for _, c := range s {
// 左括号直接入栈
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
} else {
// 如果是右括号,先判断栈内是否还有元素
if len(stack) == 0 {
return false
}
// 再判断栈顶元素是否能够匹配
peek := stack[len(stack)-1]
if peek != m[c] {
return false
}
// 模拟栈顶弹出
stack = stack[:len(stack)-1]
}
}
// 若栈中不再包含元素,则能完全匹配
return len(stack) == 0
}
```
### Ruby:
```ruby
def is_valid(strs)
symbol_map = {')' => '(', '}' => '{', ']' => '['}
stack = []
strs.size.times {|i|
c = strs[i]
if symbol_map.has_key?(c)
top_e = stack.shift
return false if symbol_map[c] != top_e
else
stack.unshift(c)
end
}
stack.empty?
end
```
### JavaScript:
```javascript
var isValid = function (s) {
const stack = [];
for (let i = 0; i < s.length; i++) {
let c = s[i];
switch (c) {
case '(':
stack.push(')');
break;
case '[':
stack.push(']');
break;
case '{':
stack.push('}');
break;
default:
if (c !== stack.pop()) {
return false;
}
}
}
return stack.length === 0;
};
// 简化版本
var isValid = function(s) {
const stack = [],
map = {
"(":")",
"{":"}",
"[":"]"
};
for(const x of s) {
if(x in map) {
stack.push(x);
continue;
};
if(map[stack.pop()] !== x) return false;
}
return !stack.length;
};
```
### TypeScript:
版本一:普通版
```typescript
function isValid(s: string): boolean {
let helperStack: string[] = [];
for (let i = 0, length = s.length; i < length; i++) {
let x: string = s[i];
switch (x) {
case '(':
helperStack.push(')');
break;
case '[':
helperStack.push(']');
break;
case '{':
helperStack.push('}');
break;
default:
if (helperStack.pop() !== x) return false;
break;
}
}
return helperStack.length === 0;
};
```
版本二:优化版
```typescript
function isValid(s: string): boolean {
type BracketMap = {
[index: string]: string;
}
let helperStack: string[] = [];
let bracketMap: BracketMap = {
'(': ')',
'[': ']',
'{': '}'
}
for (let i of s) {
if (bracketMap.hasOwnProperty(i)) {
helperStack.push(bracketMap[i]);
} else if (i !== helperStack.pop()) {
return false;
}
}
return helperStack.length === 0;
};
```
### Swift:
```swift
func isValid(_ s: String) -> Bool {
var stack = [String.Element]()
for ch in s {
if ch == "(" {
stack.append(")")
} else if ch == "{" {
stack.append("}")
} else if ch == "[" {
stack.append("]")
} else {
let top = stack.last
if ch == top {
stack.removeLast()
} else {
return false
}
}
}
return stack.isEmpty
}
```
### C:
```C
//辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回False
int notMatch(char par, char* stack, int stackTop) {
switch(par) {
case ']':
return stack[stackTop - 1] != '[';
case ')':
return stack[stackTop - 1] != '(';
case '}':
return stack[stackTop - 1] != '{';
}
return 0;
}
bool isValid(char * s){
int strLen = strlen(s);
//开辟栈空间
char stack[5000];
int stackTop = 0;
//遍历字符串
int i;
for(i = 0; i < strLen; i++) {
//取出当前下标所对应字符
char tempChar = s[i];
//若当前字符为左括号,则入栈
if(tempChar == '(' || tempChar == '[' || tempChar == '{')
stack[stackTop++] = tempChar;
//若当前字符为右括号,且栈中无元素或右括号与栈顶元素不符,返回False
else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
return 0;
//当前字符与栈顶元素为一对括号,将栈顶元素出栈
else
stackTop--;
}
//若栈中有元素,返回False。若没有元素(stackTop为0),返回True
return !stackTop;
}
```
### C#:
```csharp
public class Solution {
public bool IsValid(string s) {
var len = s.Length;
if(len % 2 == 1) return false; // 字符串长度为单数,直接返回 false
// 初始化栈
var stack = new Stack<char>();
// 遍历字符串
for(int i = 0; i < len; i++){
// 当字符串为左括号时,进栈对应的右括号
if(s[i] == '('){
stack.Push(')');
}else if(s[i] == '['){
stack.Push(']');
}else if(s[i] == '{'){
stack.Push('}');
}
// 当字符串为右括号时,当栈为空(无左括号) 或者 出栈字符不是当前的字符
else if(stack.Count == 0 || stack.Pop() != s[i])
return false;
}
// 如果栈不为空,例如“((()”,右括号少于左括号,返回false
if (stack.Count > 0)
return false;
// 上面的校验都满足,则返回true
else
return true;
}
}
```
### PHP:
```php
// https://www.php.net/manual/zh/class.splstack.php
class Solution
{
function isValid($s){
$stack = new SplStack();
for ($i = 0; $i < strlen($s); $i++) {
if ($s[$i] == "(") {
$stack->push(')');
} else if ($s[$i] == "{") {
$stack->push('}');
} else if ($s[$i] == "[") {
$stack->push(']');
// 2、遍历匹配过程中,发现栈内没有要匹配的字符 return false
// 3、遍历匹配过程中,栈已为空,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
} else if ($stack->isEmpty() || $stack->top() != $s[$i]) {
return false;
} else {//$stack->top() == $s[$i]
$stack->pop();
}
}
// 1、遍历完,但是栈不为空,说明有相应的括号没有被匹配,return false
return $stack->isEmpty();
}
}
```
### Scala:
```scala
object Solution {
import scala.collection.mutable
def isValid(s: String): Boolean = {
if(s.length % 2 != 0) return false // 如果字符串长度是奇数直接返回false
val stack = mutable.Stack[Char]()
// 循环遍历字符串
for (i <- s.indices) {
val c = s(i)
if (c == '(' || c == '[' || c == '{') stack.push(c)
else if(stack.isEmpty) return false // 如果没有(、[、{则直接返回false
// 以下三种情况,不满足则直接返回false
else if(c==')' && stack.pop() != '(') return false
else if(c==']' && stack.pop() != '[') return false
else if(c=='}' && stack.pop() != '{') return false
}
// 如果为空则正确匹配,否则还有余孽就不匹配
stack.isEmpty
}
}
```
### Rust:
```rust
impl Solution {
pub fn is_valid(s: String) -> bool {
if s.len() % 2 == 1 {
return false;
}
let mut stack = vec![];
let mut chars: Vec<char> = s.chars().collect();
while let Some(s) = chars.pop() {
match s {
')' => stack.push('('),
']' => stack.push('['),
'}' => stack.push('{'),
_ => {
if stack.is_empty() || stack.pop().unwrap() != s {
return false;
}
}
}
}
stack.is_empty()
}
}
```
================================================
FILE: problems/0024.两两交换链表中的节点.md
================================================
* [做项目(多个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)
# 24. 两两交换链表中的节点
[力扣题目链接](https://leetcode.cn/problems/swap-nodes-in-pairs/)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
<img src='https://file1.kamacoder.com/i/algo/24.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9-%E9%A2%98%E6%84%8F.jpg' width=600 alt='24.两两交换链表中的节点-题意'> </img></div>
## 算法公开课
**[《代码随想录》算法视频公开课](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<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut dummy_head = Box::new(ListNode::new(0));
dummy_head.next = head;
let mut cur = dummy_head.as_mut();
while let Some(mut node) = cur.next.take() {
if let Some(mut next) = node.next.take() {
node.next = next.next.take();
next.next = Some(node);
cur.next = Some(next);
cur = cur.next.as_mut().unwrap().next.as_mut().unwrap();
} else {
cur.next = Some(node);
cur = cur.next.as_mut().unwrap();
}
}
dummy_head.next
}
}
```
```rust
// 递归
impl Solution {
pub fn swap_pairs(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
if head.is_none() || head.as_ref().unwrap().next.is_none() {
return head;
}
let mut node = head.unwrap();
if let Some(mut next) = node.next.take() {
node.next = Solution::swap_pairs(next.next);
next.next = Some(node);
Some(next)
} else {
Some(node)
}
}
}
```
### C#
```csharp
// 虚拟头结点
public ListNode SwapPairs(ListNode head)
{
var dummyHead = new ListNode();
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null && cur.next.next != null)
{
ListNode tmp1 = cur.next;
ListNode tmp2 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = tmp1;
cur.next.next.next = tmp2;
cur = cur.next.next;
}
return dummyHead.next;
}
```
``` C#
// 递归
public ListNode SwapPairs(ListNode head)
{
if (head == null || head.next == null) return head;
var cur = head.next;
head.next = SwapPairs(head.next.next);
cur.next = head;
return cur;
}
```
================================================
FILE: problems/0027.移除元素.md
================================================
* [做项目(多个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)
# 27. 移除元素
[力扣题目链接](https://leetcode.cn/problems/remove-element/)
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并**原地**修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
**你不需要考虑数组中超出新长度后面的元素。**
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[数组中移除元素并不容易!LeetCode:27. 移除元素](https://www.bilibili.com/video/BV12A4y1Z7LP),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
有的同学可能说了,多余的元素,删掉不就得了。
**要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。**
数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。
### 暴力解法
这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
删除过程如下:

很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。
代码如下:
```CPP
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
```
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
### 双指针法
双指针法(快慢指针法): **通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
定义快慢指针
* 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
* 慢指针:指向更新 新数组下标的位置
很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。
删除过程如下:

很多同学不了解
**双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。**
后续都会一一介绍到,本题代码如下:
```CPP
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
```
注意这些实现方法并没有改变元素的相对位置!
* 时间复杂度:O(n)
* 空间复杂度:O(1)
## 相关题目推荐
* [26.删除排序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)
* [283.移动零](https://leetcode.cn/problems/move-zeroes/)
* [844.比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/)
* [977.有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/)
## 其他语言版本
### Java:
```java
class Solution {
public int removeElement(int[] nums, int val) {
// 暴力法
int n = nums.length;
for (int i = 0; i < n; i++) {
if (nums[i] == val) {
for (int j = i + 1; j < n; j++) {
nums[j - 1] = nums[j];
}
i--;
n--;
}
}
return n;
}
}
```
```java
class Solution {
public int removeElement(int[] nums, int val) {
// 快慢指针
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
```
```java
//相向双指针法
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
while(right >= 0 && nums[right] == val) right--; //将right移到从右数第一个值不为val的位置
while(left <= right) {
if(nums[left] == val) { //left位置的元素需要移除
//将right位置的元素移到left(覆盖),right位置移除
nums[left] = nums[right];
right--;
}
left++;
while(right >= 0 && nums[right] == val) right--;
}
return left;
}
}
```
```java
// 相向双指针法(版本二)
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
if(nums[left] == val){
nums[left] = nums[right];
right--;
}else {
// 这里兼容了right指针指向的值与val相等的情况
left++;
}
}
return left;
}
}
```
### Python:
``` python 3
(版本一)快慢指针法
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
fast = 0 # 快指针
slow = 0 # 慢指针
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
```
``` python 3
(版本二)暴力法
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i, l = 0, len(nums)
while i < l:
if nums[i] == val: # 找到等于目标值的节点
for j in range(i+1, l): # 移除该元素,并将后面元素向前平移
nums[j - 1] = nums[j]
l -= 1
i -= 1
i += 1
return l
```
``` python 3
# 相向双指针法
# 时间复杂度 O(n)
# 空间复杂度 O(1)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left, right = 0, n - 1
while left <= right:
while left <= right and nums[left] != val:
left += 1
while left <= right and nums[right] == val:
right -= 1
if left < right:
nums[left] = nums[right]
left += 1
right -= 1
return left
```
### Go:
```go
// 暴力法
// 时间复杂度 O(n^2)
// 空间复杂度 O(1)
func removeElement(nums []int, val int) int {
size := len(nums)
for i := 0; i < size; i ++ {
if nums[i] == val {
for j := i + 1; j < size; j ++ {
nums[j - 1] = nums[j]
}
i --
size --
}
}
return size
}
```
```go
// 快慢指针法
// 时间复杂度 O(n)
// 空间复杂度 O(1)
func removeElement(nums []int, val int) int {
// 初始化慢指针 slow
slow := 0
// 通过 for 循环移动快指针 fast
// 当 fast 指向的元素等于 val 时,跳过
// 否则,将该元素写入 slow 指向的位置,并将 slow 后移一位
for fast := 0; fast < len(nums); fast++ {
if nums[fast] == val {
continue
}
nums[slow] = nums[fast]
slow++
}
return slow
}
```
```go
//相向双指针法
func removeElement(nums []int, val int) int {
// 有点像二分查找的左闭右闭区间 所以下面是<=
left := 0
right := len(nums) - 1
for left <= right {
// 不断寻找左侧的val和右侧的非val 找到时交换位置 目的是将val全覆盖掉
for left <= right && nums[left] != val {
left++
}
for left <= right && nums[right] == val {
right--
}
//各自找到后开始覆盖 覆盖后继续寻找
if left < right {
nums[left] = nums[right]
left++
right--
}
}
return left
}
```
### JavaScript:
```javascript
//时间复杂度:O(n)
//空间复杂度:O(1)
var removeElement = (nums, val) => {
let k = 0;
for(let i = 0;i < nums.length;i++){
if(nums[i] != val){
nums[k++] = nums[i]
}
}
return k;
};
```
### TypeScript:
```typescript
function removeElement(nums: number[], val: number): number {
let slowIndex: number = 0, fastIndex: number = 0;
while (fastIndex < nums.length) {
if (nums[fastIndex] !== val) {
nums[slowIndex++] = nums[fastIndex];
}
fastIndex++;
}
return slowIndex;
};
```
### Ruby:
```ruby
def remove_element(nums, val)
i = 0
nums.each_index do |j|
if nums[j] != val
nums[i] = nums[j]
i+=1
end
end
i
end
```
### Rust:
```rust
impl Solution {
pub fn remove_element(nums: &mut Vec<i32>, val: i32) -> i32 {
let mut slowIdx = 0;
for pos in (0..nums.len()) {
if nums[pos]!=val {
nums[slowIdx] = nums[pos];
slowIdx += 1;
}
}
return (slowIdx) as i32;
}
}
```
### Swift:
```swift
func removeElement(_ nums: inout [Int], _ val: Int) -> Int {
var slowIndex = 0
for fastIndex in 0..<nums.count {
if val != nums[fastIndex] {
nums[slowIndex] = nums[fastIndex]
slowIndex += 1
}
}
return slowIndex
}
```
### PHP:
```php
class Solution {
/**
* @param Integer[] $nums
* @param Integer $val
* @return Integer
*/
function removeElement(&$nums, $val) {
if (count($nums) == 0) {
return 0;
}
// 快慢指针
$slow = 0;
for ($fast = 0; $fast < count($nums); $fast++) {
if ($nums[$fast] != $val) {
$nums[$slow] = $nums[$fast];
$slow++;
}
}
return $slow;
}
```
### C:
```c
int removeElement(int* nums, int numsSize, int val){
int slow = 0;
for(int fast = 0; fast < numsSize; fast++) {
//若快指针位置的元素不等于要删除的元素
if(nums[fast] != val) {
//将其挪到慢指针指向的位置,慢指针+1
nums[slow++] = nums[fast];
}
}
//最后慢指针的大小就是新的数组的大小
return slow;
}
```
### Kotlin:
```kotlin
fun removeElement(nums: IntArray, `val`: Int): Int {
var slowIndex = 0 // 初始化慢指针
for (fastIndex in nums.indices) {
if (nums[fastIndex] != `val`) nums[slowIndex++] = nums[fastIndex] // 在慢指针所在位置存储未被删除的元素
}
return slowIndex
}
```
### Scala:
```scala
object Solution {
def removeElement(nums: Array[Int], `val`: Int): Int = {
var slow = 0
for (fast <- 0 until nums.length) {
if (`val` != nums(fast)) {
nums(slow) = nums(fast)
slow += 1
}
}
slow
}
}
```
### C#:
```csharp
public class Solution {
public int RemoveElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.Length; fast++) {
if (val != nums[fast]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
}
```
###Dart:
```dart
int removeElement(List<int> nums, int val) {
//相向双指针法
var left = 0;
var right = nums.length - 1;
while (left <= right) {
//寻找左侧的val,将其被右侧非val覆盖
if (nums[left] == val) {
while (nums[right] == val&&left<=right) {
right--;
if (right < 0) {
return 0;
}
}
nums[left] = nums[right--];
} else {
left++;
}
}
//覆盖后可以将0至left部分视为所需部分
return left;
}
```
================================================
FILE: problems/0028.实现strStr.md
================================================
* [做项目(多个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,如图:
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B21.png' width=600 alt='KMP精讲1'> </img></div>
然后就找到了下标2,指向b,继续匹配:如图:
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B22.png' width=600 alt='KMP精讲2'> </img></div>
以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!
**下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。**
所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。
**很多介绍KMP的文章或者视频并没有把为什么要用前缀表?这个问题说清楚,而是直接默认使用前缀表。**
### 如何计算前缀表
接下来就要说一说怎么计算前缀表。
如图:
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B25.png' width=600 alt='KMP精讲5'> </img></div>
长度为前1个字符的子串`a`,最长相同前后缀的长度为0。(注意字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**;**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。)
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B26.png' width=600 alt='KMP精讲6'> </img></div>
长度为前2个字符的子串`aa`,最长相同前后缀的长度为1。
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B27.png' width=600 alt='KMP精讲7'> </img></div>
长度为前3个字符的子串`aab`,最长相同前后缀的长度为0。
以此类推:
长度为前4个字符的子串`aaba`,最长相同前后缀的长度为1。
长度为前5个字符的子串`aabaa`,最长相同前后缀的长度为2。
长度为前6个字符的子串`aabaaf`,最长相同前后缀的长度为0。
那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
<img src='https://file1.kamacoder.com/i/algo/KMP%E7%B2%BE%E8%AE%B28.png' width=600 alt='KMP精讲8'> </img></div>
可以看出模式串与前缀表对应位置的数字表示的就是:**下标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<int> next(needle.size());
getNext(&next[0], 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;
}
};
```
* 时间复杂度: O(n + m)
* 空间复杂度: O(m), 只需要保存字符串needle的前缀表
### 前缀表(不减一)C++实现
那么前缀表就不减一了,也不右移的,到底行不行呢?
**行!**
我之前说过,这仅仅是KMP算法实现上的问题,如果就直接使用前缀表可以换一种回退方式,找j=next[j-1] 来进行回退。
主要就是j=next[x]这一步最为关键!
我给出的getNext的实现为:(前缀表统一减一)
```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]
}
}
```
此时如果输入的模式串为aabaaf,对应的next为-1 0 -1 0 1 -1。
这里j和next[0]初始化为-1,整个next数组是以 前缀表减一之后的效果来构建的。
那么前缀表不减一来构建next数组,代码如下:
```CPP
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数组也可以用来做匹配,代码要有所改动。
实现代码如下:
```CPP
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;
}
vector<int> next(needle.size());
getNext(&next[0], 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;
}
};
```
* 时间复杂度: O(n + m)
* 空间复杂度: O(m)
## 总结
我们介绍了什么是KMP,KMP可以解决什么问题,然后分析KMP算法里的next数组,知道了next数组就是前缀表,再分析为什么要是前缀表而不是什么其他表。
接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不减一得到的next数组仅仅是kmp的实现方式的不同。
其中还分析了KMP算法的时间复杂度,并且和暴力方法做了对比。
然后先用前缀表统一减一得到的next数组,求得文本串s里是否出现过模式串t,并给出了具体分析代码。
又给出了直接用前缀表作为next数组,来做匹配的实现代码。
可以说把KMP的每一个细微的细节都扣了出来,毫无遮掩的展示给大家了!
## 其他语言版本
### Java:
```Java
class Solution {
/**
牺牲空间,换取最直白的暴力法
时间复杂度 O(n * m)
空间 O(n + m)
*/
public int strStr(String haystack, String needle) {
// 获取 haystack 和 needle 的长度
int n = haystack.length(), m = needle.length();
// 将字符串转换为字符数组,方便索引操作
char[] s = haystack.toCharArray(), p = needle.toCharArray();
// 遍历 haystack 字符串
for (int i = 0; i < n - m + 1; i++) {
// 初始化匹配的指针
int a = i, b = 0;
// 循环检查 needle 是否在当前位置开始匹配
while (b < m && s[a] == p[b]) {
// 如果当前字符匹配,则移动指针
a++;
b++;
}
// 如果 b 等于 m,说明 needle 已经完全匹配,返回当前位置 i
if (b == m) return i;
}
// 如果遍历完毕仍未找到匹配的子串,则返回 -1
return -1;
}
}
```
```Java
class Solution {
/**
* 基于窗口滑动的算法
* <p>
* 时间复杂度: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<usize>, s: &Vec<char>) {
let len = s.len();
let mut j = 0;
for i in 1..len {
while j > 0 && s[i] != s[j] {
j = next[j - 1];
}
if s[i] == s[j] {
j += 1;
}
next[i] = j;
}
}
pub fn str_str(haystack: String, needle: String) -> i32 {
let (haystack_len, needle_len) = (haystack.len(), needle.len());
if haystack_len < needle_len { return -1;}
let (haystack, needle) = (haystack.chars().collect::<Vec<char>>(), needle.chars().collect::<Vec<char>>());
let mut next: Vec<usize> = vec![0; haystack_len];
Self::get_next(&mut next, &needle);
let mut j = 0;
for i in 0..haystack_len {
while j > 0 && haystack[i] != needle[j] {
j = next[j - 1];
}
if haystack[i] == needle[j] {
j += 1;
}
if j == needle_len {
return (i - needle_len + 1) as i32;
}
}
return -1;
}
}
```
> 前缀表统一减一
```rust
impl Solution {
pub fn get_next(next_len: usize, s: &Vec<char>) -> Vec<i32> {
let mut next = vec![-1; next_len];
let mut j = -1;
for i in 1..s.len() {
while j >= 0 && s[(j + 1) as usize] != s[i] {
j = next[j as usize];
}
if s[i] == s[(j + 1) as usize] {
j += 1;
}
next[i] = j;
}
next
}
pub fn str_str(haystack: String, needle: String) -> i32 {
if haystack.len() < needle.len() {
return -1;
}
let (haystack_chars, needle_chars) = (
haystack.chars().collect::<Vec<char>>(),
needle.chars().collect::<Vec<char>>(),
);
let mut j = -1;
let next = Self::get_next(needle.len(), &needle_chars);
for (i, v) in haystack_chars.into_iter().enumerate() {
while j >= 0 && v != needle_chars[(j + 1) as usize] {
j = next[j as usize];
}
if v == needle_chars[(j + 1) as usize] {
j += 1;
}
if j == needle_chars.len() as i32 - 1 {
return (i - needle_chars.len() + 1) as i32;
}
}
-1
}
}
```
>前缀表统一不减一
```csharp
public int StrStr(string haystack, string needle)
{
if (string.IsNullOrEmpty(needle))
return 0;
if (needle.Length > haystack.Length || string.IsNullOrEmpty(haystack))
return -1;
return KMP(haystack, needle);
}
public int KMP(string haystack, string needle)
{
int[] next = GetNext(needle);
int i = 0, j = 0;
while (i < haystack.Length)
{
if (haystack[i] == needle[j])
{
i++;
j++;
}
if (j == needle.Length)
return i-j;
else if (i < haystack.Length && haystack[i] != needle[j])
if (j != 0)
{
j = next[j - 1];
}
else
{
i++;
}
}
return -1;
}
public int[] GetNext(string needle)
{
int[] next = new int[needle.Length];
next[0] = 0;
int i = 1, j = 0;
while (i < needle.Length)
{
if (needle[i] == needle[j])
{
next[i++] = ++j;
}
else
{
if (j == 0)
{
next[i++] = 0;
}
else
{
j = next[j - 1];
}
}
}
return next;
}
```
### C:
> 前缀表统一右移和减一
```c
int *build_next(char* needle, int len) {
int *next = (int *)malloc(len * sizeof(int));
assert(next); // 确保分配成功
// 初始化next数组
next[0] = -1; // next[0] 设置为 -1,表示没有有效前缀匹配
if (len <= 1) { // 如果模式串长度小于等于 1,直接返回
return next;
}
next[1] = 0; // next[1] 设置为 0,表示第一个字符没有公共前后缀
// 构建next数组, i 从模式串的第三个字符开始, j 指向当前匹配的最长前缀长度
int i = 2, j = 0;
while (i < len) {
if (needle[i - 1] == needle[j]) {
j++;
next[i] = j;
i++;
} else if (j > 0) {
// 如果不匹配且 j > 0, 回退到次长匹配前缀的长度
j = next[j];
} else {
next[i] = 0;
i++;
}
}
return next;
}
int strStr(char* haystack, char* needle) {
int needle_len = strlen(needle);
int haystack_len = strlen(haystack);
int *next = build_next(needle, needle_len);
int i = 0, j = 0; // i 指向主串的当前起始位置, j 指向模式串的当前匹配位置
while (i <= haystack_len - needle_len) {
if (haystack[i + j] == needle[j]) {
j++;
if (j == needle_len) {
free(next);
next = NULL
return i;
}
} else {
i += j - next[j]; // 调整主串的起始位置
j = j > 0 ? next[j] : 0;
}
}
free(next);
next = NULL;
return -1;
}
```
================================================
FILE: problems/0031.下一个排列.md
================================================
* [做项目(多个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)
# 31.下一个排列
[力扣题目链接](https://leetcode.cn/problems/next-permutation/)
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
* 输入:nums = [1,2,3]
* 输出:[1,3,2]
示例 2:
* 输入:nums = [3,2,1]
* 输出:[1,2,3]
示例 3:
* 输入:nums = [1,1,5]
* 输出:[1,5,1]
示例 4:
* 输入:nums = [1]
* 输出:[1]
## 思路
一些同学可能手动写排列的顺序,都没有写对,那么写程序的话思路一定是有问题的了,我这里以1234为例子,把全排列都列出来。可以参考一下规律所在:
```
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
```
如图:
以求1243为例,流程如图:
<img src='https://file1.kamacoder.com/i/algo/31.下一个排列.png' width=600> </img></div>
对应的C++代码如下:
```CPP
class Solution {
public:
void nextPermutation(vector<int>& nums) {
for (int i = nums.size() - 1; i >= 0; i--) {
for (int j = nums.size() - 1; j > i; j--) {
if (nums[j] > nums[i]) {
swap(nums[j], nums[i]);
reverse(nums.begin() + i + 1, nums.end());
gitextract_fqpf49kp/
├── .gitignore
├── README.md
└── problems/
├── 0001.两数之和.md
├── 0005.最长回文子串.md
├── 0015.三数之和.md
├── 0017.电话号码的字母组合.md
├── 0018.四数之和.md
├── 0019.删除链表的倒数第N个节点.md
├── 0020.有效的括号.md
├── 0024.两两交换链表中的节点.md
├── 0027.移除元素.md
├── 0028.实现strStr.md
├── 0031.下一个排列.md
├── 0034.在排序数组中查找元素的第一个和最后一个位置.md
├── 0035.搜索插入位置.md
├── 0037.解数独.md
├── 0039.组合总和.md
├── 0040.组合总和II.md
├── 0042.接雨水.md
├── 0045.跳跃游戏II.md
├── 0046.全排列.md
├── 0047.全排列II.md
├── 0051.N皇后.md
├── 0052.N皇后II.md
├── 0053.最大子序和.md
├── 0053.最大子序和(动态规划).md
├── 0054.螺旋矩阵.md
├── 0055.跳跃游戏.md
├── 0056.合并区间.md
├── 0059.螺旋矩阵II.md
├── 0062.不同路径.md
├── 0063.不同路径II.md
├── 0070.爬楼梯.md
├── 0070.爬楼梯完全背包版本.md
├── 0072.编辑距离.md
├── 0077.组合.md
├── 0077.组合优化.md
├── 0078.子集.md
├── 0084.柱状图中最大的矩形.md
├── 0090.子集II.md
├── 0093.复原IP地址.md
├── 0096.不同的二叉搜索树.md
├── 0098.验证二叉搜索树.md
├── 0100.相同的树.md
├── 0101.对称二叉树.md
├── 0102.二叉树的层序遍历.md
├── 0104.二叉树的最大深度.md
├── 0106.从中序与后序遍历序列构造二叉树.md
├── 0108.将有序数组转换为二叉搜索树.md
├── 0110.平衡二叉树.md
├── 0111.二叉树的最小深度.md
├── 0112.路径总和.md
├── 0115.不同的子序列.md
├── 0116.填充每个节点的下一个右侧节点指针.md
├── 0121.买卖股票的最佳时机.md
├── 0122.买卖股票的最佳时机II.md
├── 0122.买卖股票的最佳时机II(动态规划).md
├── 0123.买卖股票的最佳时机III.md
├── 0127.单词接龙.md
├── 0129.求根到叶子节点数字之和.md
├── 0130.被围绕的区域.md
├── 0131.分割回文串.md
├── 0132.分割回文串II.md
├── 0134.加油站.md
├── 0135.分发糖果.md
├── 0139.单词拆分.md
├── 0141.环形链表.md
├── 0142.环形链表II.md
├── 0143.重排链表.md
├── 0150.逆波兰表达式求值.md
├── 0151.翻转字符串里的单词.md
├── 0160.相交链表.md
├── 0188.买卖股票的最佳时机IV.md
├── 0189.旋转数组.md
├── 0198.打家劫舍.md
├── 0200.岛屿数量.广搜版.md
├── 0200.岛屿数量.深搜版.md
├── 0202.快乐数.md
├── 0203.移除链表元素.md
├── 0205.同构字符串.md
├── 0206.翻转链表.md
├── 0207.课程表.md
├── 0209.长度最小的子数组.md
├── 0210.课程表II.md
├── 0213.打家劫舍II.md
├── 0216.组合总和III.md
├── 0222.完全二叉树的节点个数.md
├── 0225.用队列实现栈.md
├── 0226.翻转二叉树.md
├── 0232.用栈实现队列.md
├── 0234.回文链表.md
├── 0235.二叉搜索树的最近公共祖先.md
├── 0236.二叉树的最近公共祖先.md
├── 0239.滑动窗口最大值.md
├── 0242.有效的字母异位词.md
├── 0257.二叉树的所有路径.md
├── 0279.完全平方数.md
├── 0283.移动零.md
├── 0300.最长上升子序列.md
├── 0309.最佳买卖股票时机含冷冻期.md
├── 0322.零钱兑换.md
├── 0332.重新安排行程.md
├── 0337.打家劫舍III.md
├── 0343.整数拆分.md
├── 0344.反转字符串.md
├── 0347.前K个高频元素.md
├── 0349.两个数组的交集.md
├── 0376.摆动序列.md
├── 0377.组合总和Ⅳ.md
├── 0383.赎金信.md
├── 0392.判断子序列.md
├── 0404.左叶子之和.md
├── 0406.根据身高重建队列.md
├── 0416.分割等和子集.md
├── 0417.太平洋大西洋水流问题.md
├── 0435.无重叠区间.md
├── 0450.删除二叉搜索树中的节点.md
├── 0452.用最少数量的箭引爆气球.md
├── 0454.四数相加II.md
├── 0455.分发饼干.md
├── 0459.重复的子字符串.md
├── 0463.岛屿的周长.md
├── 0474.一和零.md
├── 0491.递增子序列.md
├── 0494.目标和.md
├── 0496.下一个更大元素I.md
├── 0501.二叉搜索树中的众数.md
├── 0503.下一个更大元素II.md
├── 0509.斐波那契数.md
├── 0513.找树左下角的值.md
├── 0516.最长回文子序列.md
├── 0518.零钱兑换II.md
├── 0530.二叉搜索树的最小绝对差.md
├── 0538.把二叉搜索树转换为累加树.md
├── 0541.反转字符串II.md
├── 0583.两个字符串的删除操作.md
├── 0617.合并二叉树.md
├── 0647.回文子串.md
├── 0649.Dota2参议院.md
├── 0654.最大二叉树.md
├── 0657.机器人能否返回原点.md
├── 0669.修剪二叉搜索树.md
├── 0673.最长递增子序列的个数.md
├── 0674.最长连续递增序列.md
├── 0684.冗余连接.md
├── 0685.冗余连接II.md
├── 0695.岛屿的最大面积.md
├── 0700.二叉搜索树中的搜索.md
├── 0701.二叉搜索树中的插入操作.md
├── 0704.二分查找.md
├── 0707.设计链表.md
├── 0714.买卖股票的最佳时机含手续费.md
├── 0714.买卖股票的最佳时机含手续费(动态规划).md
├── 0718.最长重复子数组.md
├── 0724.寻找数组的中心索引.md
├── 0738.单调递增的数字.md
├── 0739.每日温度.md
├── 0743.网络延迟时间.md
├── 0746.使用最小花费爬楼梯.md
├── 0763.划分字母区间.md
├── 0787.K站中转内最便宜的航班.md
├── 0797.所有可能的路径.md
├── 0827.最大人工岛.md
├── 0841.钥匙和房间.md
├── 0844.比较含退格的字符串.md
├── 0860.柠檬水找零.md
├── 0922.按奇偶排序数组II.md
├── 0925.长按键入.md
├── 0941.有效的山脉数组.md
├── 0968.监控二叉树.md
├── 0977.有序数组的平方.md
├── 1002.查找常用字符.md
├── 1005.K次取反后最大化的数组和.md
├── 1020.飞地的数量.md
├── 1035.不相交的线.md
├── 1047.删除字符串中的所有相邻重复项.md
├── 1049.最后一块石头的重量II.md
├── 1143.最长公共子序列.md
├── 1207.独一无二的出现次数.md
├── 1221.分割平衡字符串.md
├── 1254.统计封闭岛屿的数目.md
├── 1334.阈值距离内邻居最少的城市.md
├── 1356.根据数字二进制下1的数目排序.md
├── 1365.有多少小于当前数字的数字.md
├── 1382.将二叉搜索树变平衡.md
├── 1791.找出星型图的中心节点.md
├── 1971.寻找图中是否存在路径.md
├── O(n)的算法居然超时了,此时的n究竟是多大?.md
├── images/
│ └── test
├── kamacoder/
│ ├── 0044.开发商购买土地.md
│ ├── 0047.参会dijkstra堆.md
│ ├── 0047.参会dijkstra朴素.md
│ ├── 0053.寻宝-Kruskal.md
│ ├── 0053.寻宝-prim.md
│ ├── 0054.替换数字.md
│ ├── 0055.右旋字符串.md
│ ├── 0058.区间和.md
│ ├── 0094.城市间货物运输I-SPFA.md
│ ├── 0094.城市间货物运输I.md
│ ├── 0095.城市间货物运输II.md
│ ├── 0096.城市间货物运输III.md
│ ├── 0097.小明逛公园.md
│ ├── 0098.所有可达路径.md
│ ├── 0099.岛屿的数量广搜.md
│ ├── 0099.岛屿的数量深搜.md
│ ├── 0100.岛屿的最大面积.md
│ ├── 0101.孤岛的总面积.md
│ ├── 0102.沉没孤岛.md
│ ├── 0103.水流问题.md
│ ├── 0104.建造最大岛屿.md
│ ├── 0105.有向图的完全可达性.md
│ ├── 0106.岛屿的周长.md
│ ├── 0107.寻找存在的路径.md
│ ├── 0108.冗余连接.md
│ ├── 0109.冗余连接II.md
│ ├── 0110.字符串接龙.md
│ ├── 0117.软件构建.md
│ ├── 0126.骑士的攻击astar.md
│ ├── 图论为什么用ACM模式.md
│ ├── 图论并查集理论基础.md
│ ├── 图论广搜理论基础.md
│ ├── 图论总结篇.md
│ ├── 图论深搜理论基础.md
│ ├── 图论理论基础.md
│ └── 最短路问题总结篇.md
├── qita/
│ ├── acm.md
│ ├── acm_backup.md
│ ├── algo_pdf.md
│ ├── ewaishuoming.md
│ ├── gitserver.md
│ ├── gongkaike.md
│ ├── join.md
│ ├── language.md
│ ├── publish.md
│ ├── say_feel.md
│ ├── server.md
│ ├── tulunfabu.md
│ ├── tulunshuoming.md
│ └── update.md
├── toolgithub.sh
├── 为了绝杀编辑距离,卡尔做了三步铺垫.md
├── 二叉树中递归带着回溯.md
├── 二叉树总结篇.md
├── 二叉树理论基础.md
├── 二叉树的统一迭代法.md
├── 二叉树的迭代遍历.md
├── 二叉树的递归遍历.md
├── 前序/
│ ├── ACM模式.md
│ ├── ACM模式如何构建二叉树.md
│ ├── BAT级别技术面试流程和注意事项都在这里了.md
│ ├── Java处理输入输出.md
│ ├── gitserver.md
│ ├── kvstore.md
│ ├── server.md
│ ├── vim.md
│ ├── 代码风格.md
│ ├── 内存消耗.md
│ ├── 刷力扣用不用库函数.md
│ ├── 力扣上的代码在本地编译运行.md
│ ├── 时间复杂度.md
│ ├── 程序员写文档工具.md
│ ├── 程序员简历.md
│ ├── 空间复杂度.md
│ ├── 算法超时.md
│ ├── 编程素养部分的吹毛求疵.md
│ ├── 递归算法的时间与空间复杂度分析.md
│ └── 递归算法的时间复杂度.md
├── 剑指Offer05.替换空格.md
├── 剑指Offer58-II.左旋转字符串.md
├── 动态规划-股票问题总结篇.md
├── 动态规划总结篇.md
├── 动态规划理论基础.md
├── 双指针总结.md
├── 周总结/
│ ├── 20200927二叉树周末总结.md
│ ├── 20201003二叉树周末总结.md
│ ├── 20201010二叉树周末总结.md
│ ├── 20201017二叉树周末总结.md
│ ├── 20201030回溯周末总结.md
│ ├── 20201107回溯周末总结.md
│ ├── 20201112回溯周末总结.md
│ ├── 20201126贪心周末总结.md
│ ├── 20201203贪心周末总结.md
│ ├── 20201210复杂度分析周末总结.md
│ ├── 20201217贪心周末总结.md
│ ├── 20201224贪心周末总结.md
│ ├── 20210107动规周末总结.md
│ ├── 20210114动规周末总结.md
│ ├── 20210121动规周末总结.md
│ ├── 20210128动规周末总结.md
│ ├── 20210204动规周末总结.md
│ ├── 20210225动规周末总结.md
│ ├── 20210304动规周末总结.md
│ └── 二叉树阶段总结系列一.md
├── 哈希表总结.md
├── 哈希表理论基础.md
├── 回溯总结.md
├── 回溯算法去重问题的另一种写法.md
├── 回溯算法理论基础.md
├── 字符串总结.md
├── 数组总结篇.md
├── 数组理论基础.md
├── 栈与队列总结.md
├── 栈与队列理论基础.md
├── 根据身高重建队列(vector原理讲解).md
├── 算法模板.md
├── 背包总结篇.md
├── 背包理论基础01背包-1.md
├── 背包理论基础01背包-2.md
├── 背包问题完全背包一维.md
├── 背包问题理论基础多重背包.md
├── 背包问题理论基础完全背包.md
├── 贪心算法总结篇.md
├── 贪心算法理论基础.md
├── 递归算法的时间与空间复杂度分析.md
├── 链表总结篇.md
├── 链表理论基础.md
└── 面试题02.07.链表相交.md
Condensed preview — 317 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,700K chars).
[
{
"path": ".gitignore",
"chars": 56,
"preview": ".idea/\n.DS_Store\n.vscode\n.temp\n.cache\n*.iml\n__pycache__\n"
},
{
"path": "README.md",
"chars": 17798,
"preview": "\n# 代码随想录 · LeetCode-Master\n\n<p align=\"center\">\n <a href=\"https://keetcoder.com/\">🌍 海外英文版</a> ·\n <a href=\"https://githu"
},
{
"path": "problems/0001.两数之和.md",
"chars": 13116,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0005.最长回文子串.md",
"chars": 18287,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0015.三数之和.md",
"chars": 25419,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0017.电话号码的字母组合.md",
"chars": 18757,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0018.四数之和.md",
"chars": 22959,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0019.删除链表的倒数第N个节点.md",
"chars": 11257,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0020.有效的括号.md",
"chars": 12669,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0024.两两交换链表中的节点.md",
"chars": 13053,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0027.移除元素.md",
"chars": 10862,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0028.实现strStr.md",
"chars": 32807,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0031.下一个排列.md",
"chars": 6045,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0034.在排序数组中查找元素的第一个和最后一个位置.md",
"chars": 23828,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0035.搜索插入位置.md",
"chars": 12074,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0037.解数独.md",
"chars": 23558,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0039.组合总和.md",
"chars": 17097,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0040.组合总和II.md",
"chars": 21914,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0042.接雨水.md",
"chars": 28304,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0045.跳跃游戏II.md",
"chars": 12345,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0046.全排列.md",
"chars": 11884,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0047.全排列II.md",
"chars": 13666,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0051.N皇后.md",
"chars": 21745,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0052.N皇后II.md",
"chars": 7190,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0053.最大子序和.md",
"chars": 10483,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0053.最大子序和(动态规划).md",
"chars": 5024,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0054.螺旋矩阵.md",
"chars": 13153,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0055.跳跃游戏.md",
"chars": 6078,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0056.合并区间.md",
"chars": 10789,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0059.螺旋矩阵II.md",
"chars": 17994,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0062.不同路径.md",
"chars": 12344,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0063.不同路径II.md",
"chars": 18929,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0070.爬楼梯.md",
"chars": 9750,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0070.爬楼梯完全背包版本.md",
"chars": 5077,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0072.编辑距离.md",
"chars": 11196,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0077.组合.md",
"chars": 18790,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0077.组合优化.md",
"chars": 9337,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0078.子集.md",
"chars": 11338,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0084.柱状图中最大的矩形.md",
"chars": 23383,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0090.子集II.md",
"chars": 17188,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0093.复原IP地址.md",
"chars": 23510,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0096.不同的二叉搜索树.md",
"chars": 6800,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0098.验证二叉搜索树.md",
"chars": 17924,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0100.相同的树.md",
"chars": 8706,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0101.对称二叉树.md",
"chars": 25349,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0102.二叉树的层序遍历.md",
"chars": 87212,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0104.二叉树的最大深度.md",
"chars": 25082,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0106.从中序与后序遍历序列构造二叉树.md",
"chars": 41210,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0108.将有序数组转换为二叉搜索树.md",
"chars": 14623,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0110.平衡二叉树.md",
"chars": 23919,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0111.二叉树的最小深度.md",
"chars": 17662,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0112.路径总和.md",
"chars": 42864,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0115.不同的子序列.md",
"chars": 9392,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0116.填充每个节点的下一个右侧节点指针.md",
"chars": 11835,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0121.买卖股票的最佳时机.md",
"chars": 13607,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0122.买卖股票的最佳时机II.md",
"chars": 9283,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0122.买卖股票的最佳时机II(动态规划).md",
"chars": 12031,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0123.买卖股票的最佳时机III.md",
"chars": 14145,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0127.单词接龙.md",
"chars": 11221,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0129.求根到叶子节点数字之和.md",
"chars": 8415,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0130.被围绕的区域.md",
"chars": 23180,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0131.分割回文串.md",
"chars": 25775,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0132.分割回文串II.md",
"chars": 8278,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0134.加油站.md",
"chars": 16183,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0135.分发糖果.md",
"chars": 9247,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0139.单词拆分.md",
"chars": 14696,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0141.环形链表.md",
"chars": 3512,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0142.环形链表II.md",
"chars": 10844,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0143.重排链表.md",
"chars": 16023,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0150.逆波兰表达式求值.md",
"chars": 14284,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0151.翻转字符串里的单词.md",
"chars": 27292,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0160.相交链表.md",
"chars": 286,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0188.买卖股票的最佳时机IV.md",
"chars": 15603,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0189.旋转数组.md",
"chars": 4221,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0198.打家劫舍.md",
"chars": 7755,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0200.岛屿数量.广搜版.md",
"chars": 11527,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0200.岛屿数量.深搜版.md",
"chars": 12499,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0202.快乐数.md",
"chars": 11185,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0203.移除链表元素.md",
"chars": 16857,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0205.同构字符串.md",
"chars": 4323,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0206.翻转链表.md",
"chars": 15149,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0207.课程表.md",
"chars": 1810,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0209.长度最小的子数组.md",
"chars": 12872,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0210.课程表II.md",
"chars": 1669,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0213.打家劫舍II.md",
"chars": 8770,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0216.组合总和III.md",
"chars": 16736,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0222.完全二叉树的节点个数.md",
"chars": 20075,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0225.用队列实现栈.md",
"chars": 26878,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0226.翻转二叉树.md",
"chars": 24836,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0232.用栈实现队列.md",
"chars": 13794,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0234.回文链表.md",
"chars": 9515,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0235.二叉搜索树的最近公共祖先.md",
"chars": 12900,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0236.二叉树的最近公共祖先.md",
"chars": 12033,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0239.滑动窗口最大值.md",
"chars": 22194,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0242.有效的字母异位词.md",
"chars": 9390,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0257.二叉树的所有路径.md",
"chars": 23275,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0279.完全平方数.md",
"chars": 9486,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0283.移动零.md",
"chars": 4008,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0300.最长上升子序列.md",
"chars": 7694,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0309.最佳买卖股票时机含冷冻期.md",
"chars": 16148,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0322.零钱兑换.md",
"chars": 11316,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0332.重新安排行程.md",
"chars": 26056,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0337.打家劫舍III.md",
"chars": 15075,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0343.整数拆分.md",
"chars": 11499,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0344.反转字符串.md",
"chars": 8223,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0347.前K个高频元素.md",
"chars": 15111,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0349.两个数组的交集.md",
"chars": 12249,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0376.摆动序列.md",
"chars": 16951,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0377.组合总和Ⅳ.md",
"chars": 7708,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0383.赎金信.md",
"chars": 11527,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0392.判断子序列.md",
"chars": 10136,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0404.左叶子之和.md",
"chars": 16301,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0406.根据身高重建队列.md",
"chars": 10613,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0416.分割等和子集.md",
"chars": 19010,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0417.太平洋大西洋水流问题.md",
"chars": 25174,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0435.无重叠区间.md",
"chars": 12184,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0450.删除二叉搜索树中的节点.md",
"chars": 21470,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0452.用最少数量的箭引爆气球.md",
"chars": 8645,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0454.四数相加II.md",
"chars": 12928,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0455.分发饼干.md",
"chars": 9250,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0459.重复的子字符串.md",
"chars": 21763,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0463.岛屿的周长.md",
"chars": 10875,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0474.一和零.md",
"chars": 18960,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0491.递增子序列.md",
"chars": 16915,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0494.目标和.md",
"chars": 23745,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0496.下一个更大元素I.md",
"chars": 13633,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0501.二叉搜索树中的众数.md",
"chars": 25681,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0503.下一个更大元素II.md",
"chars": 9231,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0509.斐波那契数.md",
"chars": 7797,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0513.找树左下角的值.md",
"chars": 17350,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0516.最长回文子序列.md",
"chars": 6582,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0518.零钱兑换II.md",
"chars": 13079,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0530.二叉搜索树的最小绝对差.md",
"chars": 15416,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0538.把二叉搜索树转换为累加树.md",
"chars": 11274,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0541.反转字符串II.md",
"chars": 12040,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0583.两个字符串的删除操作.md",
"chars": 12521,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0617.合并二叉树.md",
"chars": 20855,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0647.回文子串.md",
"chars": 14131,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0649.Dota2参议院.md",
"chars": 7043,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0654.最大二叉树.md",
"chars": 15100,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0657.机器人能否返回原点.md",
"chars": 3455,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0669.修剪二叉搜索树.md",
"chars": 13617,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0673.最长递增子序列的个数.md",
"chars": 8199,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0674.最长连续递增序列.md",
"chars": 10231,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0684.冗余连接.md",
"chars": 7242,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0685.冗余连接II.md",
"chars": 14040,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0695.岛屿的最大面积.md",
"chars": 19469,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0700.二叉搜索树中的搜索.md",
"chars": 10977,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0701.二叉搜索树中的插入操作.md",
"chars": 16846,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0704.二分查找.md",
"chars": 19593,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0707.设计链表.md",
"chars": 43724,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0714.买卖股票的最佳时机含手续费.md",
"chars": 8869,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0714.买卖股票的最佳时机含手续费(动态规划).md",
"chars": 8434,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0718.最长重复子数组.md",
"chars": 14620,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0724.寻找数组的中心索引.md",
"chars": 3374,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0738.单调递增的数字.md",
"chars": 10076,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0739.每日温度.md",
"chars": 12458,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0743.网络延迟时间.md",
"chars": 26600,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0746.使用最小花费爬楼梯.md",
"chars": 12229,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0763.划分字母区间.md",
"chars": 11565,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0787.K站中转内最便宜的航班.md",
"chars": 5171,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0797.所有可能的路径.md",
"chars": 6569,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0827.最大人工岛.md",
"chars": 15478,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0841.钥匙和房间.md",
"chars": 11512,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0844.比较含退格的字符串.md",
"chars": 14461,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0860.柠檬水找零.md",
"chars": 9530,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0922.按奇偶排序数组II.md",
"chars": 9661,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0925.长按键入.md",
"chars": 5811,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0941.有效的山脉数组.md",
"chars": 4733,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0968.监控二叉树.md",
"chars": 16718,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/0977.有序数组的平方.md",
"chars": 13237,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1002.查找常用字符.md",
"chars": 14125,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1005.K次取反后最大化的数组和.md",
"chars": 8163,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1020.飞地的数量.md",
"chars": 21220,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1035.不相交的线.md",
"chars": 7315,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1047.删除字符串中的所有相邻重复项.md",
"chars": 11448,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1049.最后一块石头的重量II.md",
"chars": 12649,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1143.最长公共子序列.md",
"chars": 10665,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1207.独一无二的出现次数.md",
"chars": 5857,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1221.分割平衡字符串.md",
"chars": 3110,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1254.统计封闭岛屿的数目.md",
"chars": 3615,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1334.阈值距离内邻居最少的城市.md",
"chars": 1576,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1356.根据数字二进制下1的数目排序.md",
"chars": 4097,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1365.有多少小于当前数字的数字.md",
"chars": 7092,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1382.将二叉搜索树变平衡.md",
"chars": 5560,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1791.找出星型图的中心节点.md",
"chars": 1955,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/1971.寻找图中是否存在路径.md",
"chars": 7182,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/O(n)的算法居然超时了,此时的n究竟是多大?.md",
"chars": 4859,
"preview": "* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)\n* [刷算法(两个月高强度学算法)](https://www.programme"
},
{
"path": "problems/images/test",
"chars": 0,
"preview": ""
},
{
"path": "problems/kamacoder/0044.开发商购买土地.md",
"chars": 12579,
"preview": "\n# 44. 开发商购买土地 \n\n> 本题为代码随想录后续扩充题目,还没有视频讲解,顺便让大家练习一下ACM输入输出模式(笔试面试必备)\n\n[题目链接](https://kamacoder.com/problempage.php?pid=1"
},
{
"path": "problems/kamacoder/0047.参会dijkstra堆.md",
"chars": 19225,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# dijkstra("
},
{
"path": "problems/kamacoder/0047.参会dijkstra朴素.md",
"chars": 20991,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# dijkstra("
},
{
"path": "problems/kamacoder/0053.寻宝-Kruskal.md",
"chars": 14281,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# kruskal算法"
},
{
"path": "problems/kamacoder/0053.寻宝-prim.md",
"chars": 16346,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# prim算法精讲\n"
},
{
"path": "problems/kamacoder/0054.替换数字.md",
"chars": 9663,
"preview": "\n# 替换数字 \n\n[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1064)\n\n给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数"
},
{
"path": "problems/kamacoder/0055.右旋字符串.md",
"chars": 7549,
"preview": "\n\n\n# 右旋字符串 \n\n[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1065)\n\n字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数"
},
{
"path": "problems/kamacoder/0058.区间和.md",
"chars": 6881,
"preview": "\n# 58. 区间和 \n\n> 本题为代码随想录后续扩充题目,还没有视频讲解,顺便让大家练习一下ACM输入输出模式(笔试面试必备)\n\n[题目链接](https://kamacoder.com/problempage.php?pid=1070)"
},
{
"path": "problems/kamacoder/0094.城市间货物运输I-SPFA.md",
"chars": 12466,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# Bellman_f"
},
{
"path": "problems/kamacoder/0094.城市间货物运输I.md",
"chars": 12122,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# Bellman_f"
},
{
"path": "problems/kamacoder/0095.城市间货物运输II.md",
"chars": 13162,
"preview": "\n<p align=\"center\"><strong><a href=\"./qita/join.md\">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>\n\n# bellman_f"
}
]
// ... and 117 more files (download for full content)
About this extraction
This page contains the full source code of the youngyangyang04/leetcode-master GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 317 files (3.4 MB), approximately 895.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.