This commit is contained in:
kok-s0s 2021-09-16 12:32:24 +08:00
commit 52579583b7
266 changed files with 12229 additions and 4022 deletions

View File

@ -1,4 +1,5 @@
## 一些闲话:
👉 推荐 [在线阅读](http://programmercarl.com/) (Github在国内访问经常不稳定)
👉 推荐 [Gitee同步](https://gitee.com/programmercarl/leetcode-master)
> 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者)
> 2. **PDF版本** [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。
@ -87,6 +88,7 @@
* 编程语言
* [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP)
* 项目
* [基于跳表的轻量级KV存储引擎](https://github.com/youngyangyang04/Skiplist-CPP)
* [Nosql数据库注入攻击系统](https://github.com/youngyangyang04/NoSQLAttack)
@ -95,6 +97,7 @@
* [看了这么多代码,谈一谈代码风格!](./problems/前序/代码风格.md)
* [力扣上的代码想在本地编译运行?](./problems/前序/力扣上的代码想在本地编译运行?.md)
* [什么是核心代码模式什么又是ACM模式](./problems/前序/什么是核心代码模式什么又是ACM模式.md)
* 工具
* [一站式vim配置](https://github.com/youngyangyang04/PowerVim)
* [保姆级Git入门教程万字详解](https://mp.weixin.qq.com/s/Q_O0ey4C9tryPZaZeJocbA)
@ -119,27 +122,33 @@
* [递归算法的时间与空间复杂度分析!](./problems/前序/递归算法的时间与空间复杂度分析.md)
* [刷了这么多题,你了解自己代码的内存消耗么?](./problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md)
(持续更新中.....
## 知识星球精选
1. [选择方向的时候,我也迷茫了](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g)
2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg)
3. [关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA)
4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA)
5. [Carl看了上百份简历总结了这些](https://mp.weixin.qq.com/s/sJa87MZD28piCOVMFkIbwQ)
6. [面试中遇到了发散性问题.....](https://mp.weixin.qq.com/s/SSonDxi2pjkSVwHNzZswng)
7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw)
8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg)
9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ)
10. [你的简历里「专业技能」写的够专业么?](https://mp.weixin.qq.com/s/bp6y-e5FVN28H9qc8J9zrg)
11. [对于秋招,实习生也有烦恼....](https://mp.weixin.qq.com/s/ka07IPryFnfmIjByFFcXDg)
12. [华为提前批已经开始了.....](https://mp.weixin.qq.com/s/OC35QDG8pn5OwLpCxieStw)
13. [大厂新人培养体系应该是什么样的?](https://mp.weixin.qq.com/s/WBaPCosOljB5NEkFL2GhOQ)
* [为什么都说客户端会消失](./problems/知识星球精选/客三消.md)
* [博士转计算机如何找工作](./problems/知识星球精选/博士转行计算机.md)
* [不一样的七夕](./problems/知识星球精选/不一样的七夕.md)
* [HR面注意事项](./problems/知识星球精选/HR面注意事项.md)
* [刷题攻略要刷两遍!](./problems/知识星球精选/刷题攻略要刷两遍.md)
* [秋招进行中的迷茫与焦虑......](./problems/知识星球精选/秋招进行中的迷茫与焦虑.md)
* [大厂新人培养体系应该是什么样的?](./problems/知识星球精选/大厂新人培养体系.md)
* [你的简历里「专业技能」写的够专业么?](./problems/知识星球精选/专业技能可以这么写.md)
* [Carl看了上百份简历总结了这些](./problems/知识星球精选/写简历的一些问题.md)
* [备战2022届秋招](./problems/知识星球精选/备战2022届秋招.md)
* [技术不太好,如果选择方向](./problems/知识星球精选/技术不好如何选择技术方向.md)
* [刷题要不要使用库函数](./problems/知识星球精选/刷力扣用不用库函数.md)
* [关于实习的几点问题](./problems/知识星球精选/关于实习大家的疑问.md)
* [面试中遇到了发散性问题,怎么办?](./problems/知识星球精选/面试中发散性问题.md)
* [英语到底重不重要!](./problems/知识星球精选/英语到底重不重要.md)
* [计算机专业要不要读研!](./problems/知识星球精选/要不要考研.md)
* [关于提前批的一些建议](./problems/知识星球精选/关于提前批的一些建议.md)
* [已经在实习的录友要如何准备秋招](./problems/知识星球精选/如何权衡实习与秋招复习.md)
* [华为提前批已经开始了](./problems/知识星球精选/提前批已经开始了.md)
## 杂谈
* [「代码随想录」刷题网站上线](https://mp.weixin.qq.com/s/-6rd_g7LrVD1fuKBYk2tXQ)。
* [LeetCode-Master上榜了](https://mp.weixin.qq.com/s/wZRTrA9Rbvgq1yEkSw4vfQ)
* [上榜之后,都有哪些变化?](https://mp.weixin.qq.com/s/VJBV0qSBthjnbbmW-lctLA)
* [大半年过去了......](https://mp.weixin.qq.com/s/lubfeistPxBLSQIe5XYg5g)
* [一万录友在B站学算法](https://mp.weixin.qq.com/s/Vzq4zkMZY7erKeu0fqGLgw)
@ -241,7 +250,7 @@
15. [二叉树:以为使用了递归,其实还隐藏着回溯](./problems/二叉树中递归带着回溯.md)
16. [二叉树:做了这么多题目了,我的左叶子之和是多少?](./problems/0404.左叶子之和.md)
17. [二叉树:我的左下角的值是多少?](./problems/0513.找树左下角的值.md)
18. [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](./problems/0112.路径总和.md)
18. [二叉树:路径总和](./problems/0112.路径总和.md)
19. [二叉树:构造二叉树登场!](./problems/0106.从中序与后序遍历序列构造二叉树.md)
20. [二叉树:构造一棵最大的二叉树](./problems/0654.最大二叉树.md)
21. [本周小结!(二叉树系列三)](./problems/周总结/20201010二叉树周末总结.md)
@ -409,6 +418,7 @@
2. [单调栈下一个更大元素I](./problems/0496.下一个更大元素I.md)
3. [单调栈下一个更大元素II](./problems/0503.下一个更大元素II.md)
4. [单调栈:接雨水](./problems/0042.接雨水.md)
5. [单调栈:柱状图中最大的矩形](./problems/0084.柱状图中最大的矩形.md)
(持续更新中....
@ -451,7 +461,6 @@
* [24.两两交换链表中的节点](./problems/0024.两两交换链表中的节点.md)
* [234.回文链表](./problems/0234.回文链表.md)
* [143.重排链表](./problems/0143.重排链表.md)【数组】【双向队列】【直接操作链表】
* [234.回文链表](./problems/0234.回文链表.md)
* [141.环形链表](./problems/0141.环形链表.md)
## 哈希表
@ -467,8 +476,14 @@
* [100.相同的树](./problems/0100.相同的树.md) 同101.对称二叉树 一个思路
* [116.填充每个节点的下一个右侧节点指针](./problems/0116.填充每个节点的下一个右侧节点指针.md)
## 回溯算法
* [52.N皇后II](./problems/0052.N皇后II.md)
## 贪心
* [649.Dota2参议院](./problems/0649.Dota2参议院.md) 有难度
* [1221.分割平衡字符](./problems/1221.分割平衡字符串.md) 简单贪心
## 动态规划
* [5.最长回文子串](./problems/0005.最长回文子串.md) 和[647.回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) 差不多是一样的
@ -478,6 +493,7 @@
## 图论
* [463.岛屿的周长](./problems/0463.岛屿的周长.md) (模拟)
* [841.钥匙和房间](./problems/0841.钥匙和房间.md) 【有向图】dfsbfs都可以
* [127.单词接龙](./problems/0127.单词接龙.md) 广搜
## 并查集
* [684.冗余连接](./problems/0684.冗余连接.md) 【并查集基础题目】

View File

@ -9,7 +9,7 @@
## 1. 两数之和
https://leetcode-cn.com/problems/two-sum/
[力扣题目链接](https://leetcode-cn.com/problems/two-sum/)
给定一个整数数组 nums 和一个目标值 target请你在该数组中找出和为目标值的那 两个 整数并返回他们的数组下标。
@ -29,10 +29,10 @@ https://leetcode-cn.com/problems/two-sum/
很明显暴力的解法是两层for循环查找时间复杂度是O(n^2)。
建议大家做这道题目之前,先做一下这两道
* [242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)
* [349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)
* [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html)
* [349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)
[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)这道题目是通过set作为哈希表来解决哈希问题。
[242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)这道题目是通过set作为哈希表来解决哈希问题。
本题呢则要使用map那么来看一下使用数组和set来做哈希法的局限。
@ -51,7 +51,7 @@ C++中map有三种类型
std::unordered_map 底层实现为哈希表std::map 和std::multimap 的底层实现是红黑树。
同理std::map 和std::multimap 的key也是有序的这个问题也经常作为面试题考察对语言容器底层的理解。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。
同理std::map 和std::multimap 的key也是有序的这个问题也经常作为面试题考察对语言容器底层的理解。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://www.programmercarl.com/哈希表理论基础.html)。
**这道题目中并不需要key有序选择std::unordered_map 效率更高!**
@ -62,7 +62,7 @@ std::unordered_map 底层实现为哈希表std::map 和std::multimap 的底
C++代码:
```C++
```CPP
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
@ -110,13 +110,14 @@ Python
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashmap={}
for ind,num in enumerate(nums):
hashmap[num] = ind
for i,num in enumerate(nums):
j = hashmap.get(target - num)
if j is not None and i!=j:
return [i,j]
records = dict()
# 用枚举更方便,就不需要通过索引再去取当前位置的值
for idx, val in enumerate(nums):
if target - val not in records:
records[val] = idx
else:
return [records[target - val], idx] # 如果存在就返回字典记录索引和当前索引
```
@ -205,9 +206,51 @@ function twoSum(array $nums, int $target): array
}
```
Swift
```swift
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var res = [Int]()
var dict = [Int : Int]()
for i in 0 ..< nums.count {
let other = target - nums[i]
if dict.keys.contains(other) {
res.append(i)
res.append(dict[other]!)
return res
}
dict[nums[i]] = i
}
return res
}
```
PHP:
```php
class Solution {
/**
* @param Integer[] $nums
* @param Integer $target
* @return Integer[]
*/
function twoSum($nums, $target) {
if (count($nums) == 0) {
return [];
}
$table = [];
for ($i = 0; $i < count($nums); $i++) {
$temp = $target - $nums[$i];
if (isset($table[$temp])) {
return [$table[$temp], $i];
}
$table[$nums[$i]] = $i;
}
return [];
}
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
# 5.最长回文子串
题目链接:https://leetcode-cn.com/problems/longest-palindromic-substring/
[力扣题目链接](https://leetcode-cn.com/problems/longest-palindromic-substring/)
给你一个字符串 s找到 s 中最长的回文子串。
@ -30,11 +30,11 @@
示例 4
* 输入s = "ac"
* 输出:"a"
 
# 思路
本题和[647.回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) 差不多是一样的但647.回文子串更基本一点建议可以先做647.回文子串
本题和[647.回文子串](https://programmercarl.com/0647.回文子串.html) 差不多是一样的但647.回文子串更基本一点建议可以先做647.回文子串
## 暴力解法
@ -67,7 +67,7 @@
以上三种情况分析完了,那么递归公式如下:
```C++
```CPP
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
@ -81,7 +81,7 @@ if (s[i] == s[j]) {
在得到[i,j]区间是否是回文子串的时候,直接保存最长回文子串的左边界和右边界,代码如下:
```C++
```CPP
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
@ -120,7 +120,7 @@ dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图:
代码如下:
```C++
```CPP
for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j]) {
@ -150,7 +150,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序
以上分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
string longestPalindrome(string s) {
@ -181,7 +181,7 @@ public:
```
以上代码是为了凸显情况一二三,当然是可以简洁一下的,如下:
```C++
```CPP
class Solution {
public:
string longestPalindrome(string s) {
@ -226,7 +226,7 @@ public:
**这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算**,代码如下:
```C++
```CPP
class Solution {
public:
int left = 0;
@ -270,6 +270,23 @@ public:
## 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]
```
## Go
@ -286,6 +303,6 @@ public:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,11 +8,11 @@
> 用哈希表解决了[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),那么三数之和呢?
> 用哈希表解决了[两数之和](https://programmercarl.com/0001.两数之和.html),那么三数之和呢?
# 第15题. 三数之和
https://leetcode-cn.com/problems/3sum/
[力扣题目链接](https://leetcode-cn.com/problems/3sum/)
给你一个包含 n 个整数的数组 nums判断 nums 中是否存在三个元素 abc 使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
@ -37,7 +37,7 @@ https://leetcode-cn.com/problems/3sum/
两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中然后在去去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
把符合条件的三元组放进vector中然后去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
@ -46,7 +46,7 @@ https://leetcode-cn.com/problems/3sum/
大家可以尝试使用哈希法写一写,就知道其困难的程度了。
哈希法C++代码:
```C++
```CPP
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
@ -95,11 +95,11 @@ public:
![15.三数之和](https://code-thinking.cdn.bcebos.com/gifs/15.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.gif)
拿这个nums数组来举例首先将数组排序然后有一层for循环i从下表0的地方开始同时定一个下表left 定义在i+1的位置上定义下表right 在数组结尾的位置上。
拿这个nums数组来举例首先将数组排序然后有一层for循环i从下标0的地方开始同时定一个下标left 定义在i+1的位置上定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0我们这里相当于 a = nums[i] b = nums[left] c = nums[right]。
接下来如何移动left 和right呢 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了因为数组是排序后了所以right下就应该向左移动,这样才能让三数之和小一些。
接下来如何移动left 和right呢 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了因为数组是排序后了所以right下就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了left 就向右移动才能让三数之和大一些直到left与right相遇为止
@ -107,7 +107,7 @@ public:
C++代码代码如下:
```C++
```CPP
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
@ -163,13 +163,13 @@ public:
# 思考题
既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ),可不可以使用双指针法呢?
既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://programmercarl.com/0001.两数之和.html),可不可以使用双指针法呢?
如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!**
两数之和 就不能使用双指针法,因为[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是索引下表 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。
两数之和 就不能使用双指针法,因为[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是索引下标 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。
如果[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是数值的话,就可以使用双指针法了。
如果[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是数值的话,就可以使用双指针法了。
@ -393,10 +393,90 @@ function threeSum(array $nums): array
}
```
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
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 17.电话号码的字母组合
题目链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
[力扣题目链接](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
@ -29,7 +29,7 @@
如果输入"233"呢那么就三层for循环如果"2333"呢就四层for循环.......
大家应该感觉出和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)遇到的一样的问题就是这for循环的层数如何写出来此时又是回溯法登场的时候了。
大家应该感觉出和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)遇到的一样的问题就是这for循环的层数如何写出来此时又是回溯法登场的时候了。
理解本题后,要解决如下三个问题:
@ -58,7 +58,7 @@ const string letterMap[10] = {
## 回溯法来解决n个for循环的问题
对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)
对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)
例如:输入:"23",抽象为树形结构,如图所示:
@ -75,7 +75,7 @@ const string letterMap[10] = {
再来看参数参数指定是有题目中给的string digits然后还要有一个参数就是int型的index。
注意这个index可不是 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的startIndex了。
注意这个index可不是 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中的startIndex了。
这个index是记录遍历第几个数字了就是用来遍历digits的题目中给出数字字符串同时index也表示树的深度。
@ -120,9 +120,9 @@ for (int i = 0; i < letters.size(); i++) {
}
```
**注意这里for循环可不像是在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的**。
**注意这里for循环可不像是在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的**。
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!**
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!**
注意输入1 * #按键等等异常情况
@ -134,7 +134,7 @@ for (int i = 0; i < letters.size(); i++) {
## C++代码
关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的回溯法模板不难写出如下C++代码:
关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中的回溯法模板不难写出如下C++代码:
```c++
@ -224,13 +224,13 @@ public:
};
```
我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)这篇文章中也深度分析了,回溯隐藏在了哪里。
我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)这篇文章中也深度分析了,回溯隐藏在了哪里。
所以大家可以按照版本一来写就可以了。
# 总结
本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。
本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。
其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。
@ -322,20 +322,20 @@ python3
```py
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
self.s = ""
res = []
s = ""
letterMap = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
if len(digits) == 0: return res
def backtrack(digits,index):
if not len(digits): return res
def backtrack(digits,index, s):
if index == len(digits):
return res.append(self.s)
return res.append(s)
digit = int(digits[index]) #将index指向的数字转为int
letters = letterMap[digit] #取数字对应的字符集
for i in range(len(letters)):
self.s += letters[i]
backtrack(digits,index + 1) #递归注意index+1一下层要处理下一个数字
self.s = self.s[:-1] #回溯
backtrack(digits,0)
s += letters[i]
backtrack(digits, index+1, s) #递归注意index+1一下层要处理下一个数字
s = s[:-1] #回溯
backtrack(digits, 0, s)
return res
```
@ -410,10 +410,70 @@ var letterCombinations = function(digits) {
};
```
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;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 第18题. 四数之和
https://leetcode-cn.com/problems/4sum/
[力扣题目链接](https://leetcode-cn.com/problems/4sum/)
题意给定一个包含 n 个整数的数组 nums 和一个目标值 target判断 nums 中是否存在四个元素 abc 和 d 使得 a + b + c + d 的值与 target 相等找出所有满足条件且不重复的四元组。
@ -31,43 +31,43 @@ https://leetcode-cn.com/problems/4sum/
# 思路
四数之和,和[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) 的基础上再套一层for循环。
四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。大家亲自写代码就能感受出来
[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)的双指针解法是一层for循环num[i]为确定值然后循环内有left和right下表作为双指针找到nums[i] + nums[left] + nums[right] == 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://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)双指针法就是将原本暴力O(n^3)的解法降为O(n^2)的解法四数之和的双指针解法就是将原本暴力O(n^4)的解法降为O(n^3)的解法。
对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力O(n^3)的解法降为O(n^2)的解法四数之和的双指针解法就是将原本暴力O(n^4)的解法降为O(n^3)的解法。
之前我们讲过哈希表的经典题目:[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)相对于本题简单很多因为本题是要求在一个集合中找出四个数相加等于target同时四元组不能重复。
之前我们讲过哈希表的经典题目:[454.四数相加II](https://programmercarl.com/0454.四数相加II.html)相对于本题简单很多因为本题是要求在一个集合中找出四个数相加等于target同时四元组不能重复。
而[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)是四个独立的数组只要找到A[i] + B[j] + C[k] + D[l] = 0就可以不用考虑有重复的四个元素相加等于0的情况所以相对于本题还是简单了不少
而[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://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)
* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)
* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
* [27.移除元素](https://programmercarl.com/0027.移除元素.html)
* [15.三数之和](https://programmercarl.com/0015.三数之和.html)
* [18.四数之和](https://programmercarl.com/0018.四数之和.html)
操作链表:
* [206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)
* [19.删除链表的倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg)
* [面试题 02.07. 链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw)
* [142题.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)
* [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++代码
```C++
```CPP
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
@ -167,7 +167,33 @@ class Solution {
Python
```python
# 双指针法
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue
for k in range(i+1, n):
if k > i + 1 and nums[k] == nums[k-1]: continue
p = k + 1
q = n - 1
while p < q:
if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1
elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
else:
res.append([nums[i], nums[k], nums[p], nums[q]])
while p < q and nums[p] == nums[p + 1]: p += 1
while p < q and nums[q] == nums[q - 1]: q -= 1
p += 1
q -= 1
return res
```
```python
# 哈希表法
class Solution(object):
def fourSum(self, nums, target):
"""
@ -201,6 +227,54 @@ class Solution(object):
```
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] {
continue
}
for j := i + 1; j < len(nums)-2; j++ {
n2 := nums[j]
if j > i+1 && n2 == nums[j-1] {
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:
@ -236,9 +310,100 @@ var fourSum = function(nums, target) {
};
```
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
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 19.删除链表的倒数第N个节点
题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
[力扣题目链接](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/)
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
@ -41,7 +41,7 @@
分为如下几步:
* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)
* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)
* 定义fast指针和slow指针初始值为虚拟头结点如图
@ -58,7 +58,7 @@
此时不难写出如下C++代码:
```C++
```CPP
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
@ -184,9 +184,53 @@ var removeNthFromEnd = function(head, n) {
return ret.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
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 20. 有效的括号
https://leetcode-cn.com/problems/valid-parentheses/
[力扣题目链接](https://leetcode-cn.com/problems/valid-parentheses/)
给定一个只包括 '('')''{''}''['']' 的字符串,判断字符串是否有效。
@ -108,7 +108,7 @@ cd a/b/c/../../
实现C++代码如下:
```C++
```CPP
class Solution {
public:
bool isValid(string s) {
@ -162,18 +162,44 @@ class Solution {
Python
```python3
# 方法一,仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = [] # 保存还未匹配的左括号
mapping = {")": "(", "]": "[", "}": "{"}
for i in s:
if i in "([{": # 当前是左括号,则入栈
stack.append(i)
elif stack and stack[-1] == mapping[i]: # 当前是配对的右括号则出栈
stack.pop()
else: # 不是匹配的右括号或者没有左括号与之匹配则返回false
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
return stack == [] # 最后必须正好把左括号匹配完
else:
stack.pop()
return True if not stack else False
```
```python3
# 方法二,使用字典
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
@ -264,4 +290,4 @@ var isValid = function(s) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 24. 两两交换链表中的节点
https://leetcode-cn.com/problems/swap-nodes-in-pairs/
[力扣题目链接](https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
@ -24,7 +24,7 @@ https://leetcode-cn.com/problems/swap-nodes-in-pairs/
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)。
对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)。
接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序**
@ -43,7 +43,7 @@ https://leetcode-cn.com/problems/swap-nodes-in-pairs/
对应的C++代码实现如下: (注释中详细和如上图中的三步做对应)
```C++
```CPP
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
@ -86,6 +86,34 @@ public:
## 其他语言版本
C:
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
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
@ -132,21 +160,29 @@ class Solution {
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: ListNode) -> ListNode:
dummy = ListNode(0) #设置一个虚拟头结点
dummy.next = head
cur = dummy
while cur.next and cur.next.next:
tmp = cur.next #记录临时节点
tmp1 = cur.next.next.next #记录临时节点
res = ListNode(next=head)
pre = res
# 必须有pre的下一个和下下个才能交换否则说明已经交换结束了
while pre.next and pre.next.next:
cur = pre.next
post = pre.next.next
cur.next = cur.next.next #步骤一
cur.next.next = tmp #步骤二
cur.next.next.next = tmp1 #步骤三
cur = cur.next.next #cur移动两位,准备下一轮交换
return dummy.next
# precurpost对应最左中间的最右边的节点
cur.next = post.next
post.next = cur
pre.next = post
pre = pre.next.next
return res.next
```
Go
@ -200,9 +236,51 @@ var swapPairs = function (head) {
};
```
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
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 27. 移除元素
题目地址https://leetcode-cn.com/problems/remove-element/
[力扣题目链接](https://leetcode-cn.com/problems/remove-element/)
给你一个数组 nums 和一个值 val你需要 原地 移除所有数值等于 val 的元素并返回移除后数组的新长度。
@ -34,7 +34,7 @@
**要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。**
数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。
数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。
### 暴力解法
@ -48,7 +48,7 @@
代码如下:
```C++
```CPP
// 时间复杂度O(n^2)
// 空间复杂度O(1)
class Solution {
@ -85,7 +85,7 @@ public:
后序都会一一介绍到,本题代码如下:
```C++
```CPP
// 时间复杂度O(n)
// 空间复杂度O(1)
class Solution {
@ -106,7 +106,7 @@ public:
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
旧文链接:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
旧文链接:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)
## 相关题目推荐
@ -144,15 +144,28 @@ class Solution {
Python
```python
```python3
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i,n = 0,len(nums)
for j in range(n):
if nums[j] != val:
nums[i] = nums[j]
i += 1
return i
"""双指针法
时间复杂度O(n)
空间复杂度O(1)
"""
@classmethod
def removeElement(cls, nums: List[int], val: int) -> int:
fast = slow = 0
while fast < len(nums):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
# 当 fast 指针遇到要删除的元素时停止赋值
# slow 指针停止移动, fast 指针继续前进
fast += 1
return slow
```
@ -201,23 +214,80 @@ end
```
Rust:
```rust
pub fn remove_element(nums: &mut Vec<i32>, val: i32) -> &mut Vec<i32> {
let mut start: usize = 0;
while start < nums.len() {
if nums[start] == val {
nums.remove(start);
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;
}
}
start += 1;
return (slowIdx) as i32;
}
nums
}
fn main() {
let mut nums = vec![5,1,3,5,2,3,4,1];
println!("{:?}",remove_element(&mut nums, 5));
}
```
Swift:
```swift
func removeElement(_ nums: inout [Int], _ val: Int) -> Int {
var slowIndex = 0
for fastIndex in 0..<nums.count {
if val != nums[fastIndex] {
if slowIndex != 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;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 28. 实现 strStr()
https://leetcode-cn.com/problems/implement-strstr/
[力扣题目链接](https://leetcode-cn.com/problems/implement-strstr/)
实现 strStr() 函数。
@ -315,7 +315,7 @@ next[i] = j;
最后整体构建next数组的函数代码如下
```C++
```CPP
void getNext(int* next, const string& s){
    int j = -1;
    next[0] = j;
@ -386,7 +386,7 @@ if (j == (t.size() - 1) ) {
那么使用next数组用模式串匹配文本串的整体代码如下
```C++
```CPP
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
    while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
@ -405,7 +405,7 @@ for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
# 前缀表统一减一 C++代码实现
```C++
```CPP
class Solution {
public:
    void getNext(int* next, const string& s) {
@ -457,7 +457,7 @@ public:
我给出的getNext的实现为前缀表统一减一
```C++
```CPP
void getNext(int* next, const string& s) {
    int j = -1;
    next[0] = j;
@ -479,7 +479,7 @@ void getNext(int* next, const string& s) {
那么前缀表不减一来构建next数组代码如下
```C++
```CPP
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
@ -502,7 +502,7 @@ void getNext(int* next, const string& s) {
实现代码如下:
```C++
```CPP
class Solution {
public:
void getNext(int* next, const string& s) {
@ -902,4 +902,4 @@ var strStr = function (haystack, needle) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 31.下一个排列
链接:https://leetcode-cn.com/problems/next-permutation/
[力扣题目链接](https://leetcode-cn.com/problems/next-permutation/)
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
@ -75,7 +75,7 @@
对应的C++代码如下:
```C++
```CPP
class Solution {
public:
void nextPermutation(vector<int>& nums) {
@ -99,6 +99,24 @@ public:
## Java
```java
class Solution {
public void nextPermutation(int[] nums) {
for (int i = nums.length - 1; i >= 0; i--) {
for (int j = nums.length - 1; j > i; j--) {
if (nums[j] > nums[i]) {
// 交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
// [i + 1, nums.length) 内元素升序排序
Arrays.sort(nums, i + 1, nums.length);
return;
}
}
}
Arrays.sort(nums); // 不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
}
}
```
## Python
@ -120,5 +138,5 @@ public:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -35,8 +35,8 @@
对二分还不了解的同学先做这两题:
* [704.二分查找](https://mp.weixin.qq.com/s/4X-8VRgnYRGd5LYGZ33m4w)
* [35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)
* [704.二分查找](https://programmercarl.com/0704.二分查找.html)
* [35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html)
下面我来把所有情况都讨论一下。
@ -50,21 +50,21 @@
接下来,在去寻找左边界,和右边界了。
采用二分法来寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
采用二分法来寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
**刚刚接触二分搜索的同学不建议上来就像如果用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界**
## 寻找右边界
先来寻找右边界,至于二分查找,如果看过[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)就会知道二分查找中什么时候用while (left <= right)有什么时候用while (left < right)其实只要清楚**循环不变量**很容易区分两种写法
先来寻找右边界,至于二分查找,如果看过[704.二分查找](https://programmercarl.com/0704.二分查找.html)就会知道二分查找中什么时候用while (left <= right)有什么时候用while (left < right)其实只要清楚**循环不变量**很容易区分两种写法
那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭又闭的区间(如果这里有点看不懂了,强烈建议把[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)这篇文章先看了在把「leetcode35.搜索插入位置」做了之后在做这道题目就好很多了)
那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭又闭的区间(如果这里有点看不懂了,强烈建议把[704.二分查找](https://programmercarl.com/0704.二分查找.html)这篇文章先看了704题目做了之后再做这道题目就好很多了)
确定好计算出来的右边界是不包好target的右边界左边界同理。
可以写出如下代码
```C++
```CPP
// 二分查找寻找target的右边界不包括target
// 如果rightBorder为没有被赋值即target在数组范围的左边例如数组[3,3]target为2为了处理情况一
int getRightBorder(vector<int>& nums, int target) {
@ -86,7 +86,7 @@ int getRightBorder(vector<int>& nums, int target) {
## 寻找左边界
```C++
```CPP
// 二分查找寻找target的左边界leftBorder不包括target
// 如果leftBorder没有被赋值即target在数组范围的右边例如数组[3,3],target为4为了处理情况一
int getLeftBorder(vector<int>& nums, int target) {
@ -110,7 +110,7 @@ int getLeftBorder(vector<int>& nums, int target) {
左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了
```C++
```CPP
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
@ -223,7 +223,7 @@ class Solution {
// 解法2
// 1、首先在 nums 数组中二分查找 target
// 2、如果二分查找失败则 binarySearch 返回 -1表明 nums 中没有 target。此时searchRange 直接返回 {-1, -1}
// 3、如果二分查找失败,则 binarySearch 返回 nums 中 为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
// 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution {
public int[] searchRange(int[] nums, int target) {
@ -275,6 +275,117 @@ class Solution {
## Python
```python
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界nums[middle] == target的时候更新left
left = middle + 1
rightBoder = left
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界nums[middle] == target的时候更新right
right = middle - 1;
leftBoder = right;
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
# 情况二
return [-1, -1]
```
```python
# 解法2
# 1、首先在 nums 数组中二分查找 target
# 2、如果二分查找失败则 binarySearch 返回 -1表明 nums 中没有 target。此时searchRange 直接返回 {-1, -1}
# 3、如果二分查找成功则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
index = binarySearch(nums, target)
if index == -1:return [-1, -1] # nums 中不存在 target直接返回 {-1, -1}
# nums 中存在 targe则左右滑动指针来找到符合题意的区间
left, right = index, index
# 向左滑动,找左边界
while left -1 >=0 and nums[left - 1] == target: left -=1
# 向右滑动,找右边界
while right+1 < len(nums) and nums[right + 1] == target: right +=1
return [left, right]
```
```python
# 解法3
# 1、首先在 nums 数组中二分查找得到第一个大于等于 target的下标左边界与第一个大于target的下标右边界
# 2、如果左边界<= 右边界,则返回 [左边界, 右边界]。否则返回[-1, -1]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int, lower:bool) -> int:
left, right = 0, len(nums)-1
ans = len(nums)
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) //2
# lower为True执行前半部分找到第一个大于等于 target的下标 否则找到第一个大于target的下标
if nums[middle] > target or (lower and nums[middle] >= target):
right = middle - 1
ans = middle
else:
left = middle + 1
return ans
leftBorder = binarySearch(nums, target, True) # 搜索左边界
rightBorder = binarySearch(nums, target, False) -1 # 搜索右边界
if leftBorder<= rightBorder and rightBorder< len(nums) and nums[leftBorder] == target and nums[rightBorder] == target:
return [leftBorder, rightBorder]
return [-1, -1]
```
```python
# 解法4
# 1、首先在 nums 数组中二分查找得到第一个大于等于 target的下标leftBorder
# 2、在 nums 数组中二分查找得到第一个大于等于 target+1的下标 减1则得到rightBorder
# 3、如果开始位置在数组的右边或者不存在target则返回[-1, -1] 。否则返回[leftBorder, rightBorder]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) //2
if nums[middle] >= target:
right = middle - 1
else:
left = middle + 1
return left # 若存在target则返回第一个等于target的值
leftBorder = binarySearch(nums, target) # 搜索左边界
rightBorder = binarySearch(nums, target+1) -1 # 搜索右边界
if leftBorder == len(nums) or nums[leftBorder]!= target: # 情况一和情况二
return [-1, -1]
return [leftBorder, rightBorder]
```
## Go
@ -291,5 +402,5 @@ class Solution {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 35.搜索插入位置
题目地址:https://leetcode-cn.com/problems/search-insert-position/
[力扣题目链接](https://leetcode-cn.com/problems/search-insert-position/)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
@ -76,7 +76,7 @@ public:
```
时间复杂度O(n)
间复杂度O(1)
间复杂度O(1)
效率如下:
@ -116,7 +116,7 @@ public:
**大家要仔细看注释思考为什么要写while(left <= right) 为什么要写right = middle - 1**。
```C++
```CPP
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
@ -158,7 +158,7 @@ public:
**大家要仔细看注释思考为什么要写while (left < right) 为什么要写right = middle**。
```C++
```CPP
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
@ -234,29 +234,24 @@ class Solution {
```
Python
```python3
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
else:
return middle
return right + 1
```
Go
JavaScript:
```js
var searchInsert = function (nums, target) {
@ -277,9 +272,45 @@ var searchInsert = function (nums, target) {
};
```
Swift:
```swift
// 暴力法
func searchInsert(_ nums: [Int], _ target: Int) -> Int {
for i in 0..<nums.count {
if nums[i] >= target {
return i
}
}
return nums.count
}
// 二分法
func searchInsert(_ nums: [Int], _ target: Int) -> Int {
var left = 0
var right = nums.count - 1
while left <= right {
let middle = left + ((right - left) >> 1)
if nums[middle] > target {
right = middle - 1
}else if nums[middle] < target {
left = middle + 1
}else if nums[middle] == target {
return middle
}
}
return right + 1
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 37. 解数独
题目地址https://leetcode-cn.com/problems/sudoku-solver/
[力扣题目链接](https://leetcode-cn.com/problems/sudoku-solver/)
编写一个程序,通过填充空格来解决数独问题。
@ -40,11 +40,11 @@
怎么做二维递归呢?
大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[131.分割回文串(分割问题)](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)[78.子集(子集问题)](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)[46.全排列(排列问题)](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及[51.N皇后N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg),其实这些题目都是一维递归。
大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html)[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html)[78.子集(子集问题)](https://programmercarl.com/0078.子集.html)[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后N皇后问题](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。
**如果以上这几道题目没有做过的话,不建议上来就做这道题哈!**
[N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)是因为每一行每一列只放一个皇后只需要一层for循环遍历一行递归来来遍历列然后一行一列确定皇后的唯一位置。
[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后只需要一层for循环遍历一行递归来来遍历列然后一行一列确定皇后的唯一位置。
本题就不一样了,**本题中棋盘的每一个位置都要放一个数字并检查数字是否合法解数独的树形结构要比N皇后更宽更深**。
@ -59,7 +59,7 @@
**递归函数的返回值需要是bool类型为什么呢**
因为解数独找到一个符合的条件就在树的叶子节点上立刻就返回相当于找从根节点到叶子节点一条唯一路径所以需要使用bool返回值这一点在[回溯算法N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)中已经介绍过了,一样的道理。
因为解数独找到一个符合的条件就在树的叶子节点上立刻就返回相当于找从根节点到叶子节点一条唯一路径所以需要使用bool返回值这一点在[回溯算法N皇后问题](https://programmercarl.com/0051.N皇后.html)中已经介绍过了,一样的道理。
代码如下:
@ -90,7 +90,7 @@ bool backtracking(vector<vector<char>>& board)
代码如下:(**详细看注释**
```C++
```CPP
bool backtracking(vector<vector<char>>& board) {
for (int i = 0; i < board.size(); i++) { // 遍历行
for (int j = 0; j < board[0].size(); j++) { // 遍历列
@ -125,7 +125,7 @@ bool backtracking(vector<vector<char>>& board) {
代码如下:
```C++
```CPP
bool isValid(int row, int col, char val, vector<vector<char>>& board) {
for (int i = 0; i < 9; i++) { // 判断行里是否重复
if (board[row][i] == val) {
@ -154,7 +154,7 @@ bool isValid(int row, int col, char val, vector<vector<char>>& board) {
## C++代码
```C++
```CPP
class Solution {
private:
bool backtracking(vector<vector<char>>& board) {
@ -437,4 +437,4 @@ var solveSudoku = function(board) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 39. 组合总和
题目链接https://leetcode-cn.com/problems/combination-sum/
[力扣题目链接](https://leetcode-cn.com/problems/combination-sum/)
给定一个无重复元素的数组 candidates 和一个目标数 target 找出 candidates 中所有可以使数字和为 target 的组合。
@ -44,14 +44,14 @@ candidates 中的数字可以无限制重复被选取。
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**然后看到下面提示1 <= candidates[i] <= 200我就放心了。
本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
本题搜索的过程抽象成树形结构如下:
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png)
注意图中叶子节点的返回条件因为本题没有组合数量要求仅仅是总和的限制所以递归没有层数的限制只要选取的元素总和超过target就返回
而在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) 中都可以知道要递归K层因为要取k个元素的组合。
而在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层因为要取k个元素的组合。
## 回溯三部曲
@ -65,15 +65,15 @@ candidates 中的数字可以无限制重复被选取。
**本题还需要startIndex来控制for循环的起始位置对于组合问题什么时候需要startIndex呢**
我举过例子如果是一个集合来求组合的话就需要startIndex例如[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。
我举过例子如果是一个集合来求组合的话就需要startIndex例如[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。
如果是多个集合取组合各个集合之间相互不影响那么就不用startIndex例如[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
如果是多个集合取组合各个集合之间相互不影响那么就不用startIndex例如[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍**。
代码如下:
```C++
```CPP
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
@ -89,7 +89,7 @@ void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
sum等于target的时候需要收集结果代码如下
```C++
```CPP
if (sum > target) {
return;
}
@ -103,11 +103,11 @@ if (sum == target) {
单层for循环依然是从startIndex开始搜索candidates集合。
**注意本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的一个区别是:本题元素为可重复选取的**。
**注意本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)、[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)的一个区别是:本题元素为可重复选取的**。
如何重复选取呢,看代码,注释部分:
```C++
```CPP
for (int i = startIndex; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
@ -117,9 +117,9 @@ for (int i = startIndex; i < candidates.size(); i++) {
}
```
按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的模板不难写出如下C++完整代码:
按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的模板不难写出如下C++完整代码:
```C++
```CPP
// 版本一
class Solution {
private:
@ -179,7 +179,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
整体代码如下:(注意注释的部分)
```C++
```CPP
class Solution {
private:
vector<vector<int>> result;
@ -213,14 +213,14 @@ public:
## 总结
本题和我们之前讲过的[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)有两点不同:
本题和我们之前讲过的[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)、[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)有两点不同:
* 组合没有数量要求
* 元素可无限重复选取
针对这两个问题,我都做了详细的分析。
并且给出了对于组合问题什么时候用startIndex什么时候不用并用[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)做了对比。
并且给出了对于组合问题什么时候用startIndex什么时候不用并用[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)做了对比。
最后还给出了本题的剪枝优化,这个优化如果是初学者的话并不容易想到。
@ -346,9 +346,63 @@ var combinationSum = function(candidates, target) {
};
```
C:
```c
int* path;
int pathTop;
int** ans;
int ansTop;
//记录每一个和等于target的path数组长度
int* length;
void backTracking(int target, int index, int* candidates, int candidatesSize, int sum) {
//若sum>=target就应该终止遍历
if(sum >= target) {
//若sum等于target将当前的组合放入ans数组中
if(sum == target) {
int* tempPath = (int*)malloc(sizeof(int) * pathTop);
int j;
for(j = 0; j < pathTop; j++) {
tempPath[j] = path[j];
}
ans[ansTop] = tempPath;
length[ansTop++] = pathTop;
}
return ;
}
int i;
for(i = index; i < candidatesSize; i++) {
//将当前数字大小加入sum
sum+=candidates[i];
path[pathTop++] = candidates[i];
backTracking(target, i, candidates, candidatesSize, sum);
sum-=candidates[i];
pathTop--;
}
}
int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
//初始化变量
path = (int*)malloc(sizeof(int) * 50);
ans = (int**)malloc(sizeof(int*) * 200);
length = (int*)malloc(sizeof(int) * 200);
ansTop = pathTop = 0;
backTracking(target, 0, candidates, candidatesSize, 0);
//设置返回的数组大小
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; i++) {
(*returnColumnSizes)[i] = length[i];
}
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 40.组合总和II
题目链接:https://leetcode-cn.com/problems/combination-sum-ii/
[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-ii/)
给定一个数组 candidates 和一个目标数 target 找出 candidates 中所有可以使数字和为 target 的组合。
@ -44,12 +44,12 @@ candidates 中的每个数字在每个组合中只能使用一次。
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别:
这道题目和[39.组合总和](https://programmercarl.com/0039.组合总和.html)如下区别:
1. 本题candidates 中的每个数字在每个组合中只能使用一次。
2. 本题数组candidates的元素是有重复的而[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是无重复元素的数组candidates
2. 本题数组candidates的元素是有重复的而[39.组合总和](https://programmercarl.com/0039.组合总和.html)是无重复元素的数组candidates
最后本题和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求一样,解集不能包含重复的组合。
最后本题和[39.组合总和](https://programmercarl.com/0039.组合总和.html)要求一样,解集不能包含重复的组合。
**本题的难点在于区别2中集合数组candidates有重复元素但还不能有重复的组合**。
@ -63,7 +63,7 @@ candidates 中的每个数字在每个组合中只能使用一次。
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。**
那么问题来了,我们是要同一树层上使用过,还是一树枝上使用过呢?
那么问题来了,我们是要同一树层上使用过,还是一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
@ -84,13 +84,13 @@ candidates 中的每个数字在每个组合中只能使用一次。
* **递归函数参数**
与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同此题还需要加一个bool型数组used用来记录同一树枝上的元素是否使用过。
与[39.组合总和](https://programmercarl.com/0039.组合总和.html)套路相同此题还需要加一个bool型数组used用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
代码如下:
```C++
```CPP
vector<vector<int>> result; // 存放组合集合
vector<int> path; // 符合条件的组合
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
@ -98,11 +98,11 @@ void backtracking(vector<int>& candidates, int target, int sum, int startIndex,
* **递归终止条件**
与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)相同,终止条件为 `sum > target``sum == target`
与[39.组合总和](https://programmercarl.com/0039.组合总和.html)相同,终止条件为 `sum > target``sum == target`
代码如下:
```C++
```CPP
if (sum > target) { // 这个条件其实可以省略
return;
}
@ -116,7 +116,7 @@ if (sum == target) {
* **单层搜索的逻辑**
这里与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)最大的不同就是要去重了。
这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。
前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
@ -137,7 +137,7 @@ if (sum == target) {
那么单层搜索的逻辑代码如下:
```C++
```CPP
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true说明同一树支candidates[i - 1]使用过
// used[i - 1] == false说明同一树层candidates[i - 1]使用过
@ -161,7 +161,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
回溯三部曲分析完了整体C++代码如下:
```C++
```CPP
class Solution {
private:
vector<vector<int>> result;
@ -206,7 +206,7 @@ public:
这里直接用startIndex来去重也是可以的 就不用used数组了。
```C++
```CPP
class Solution {
private:
vector<vector<int>> result;
@ -244,7 +244,7 @@ public:
## 总结
本题同样是求组合总和但就是因为其数组candidates有重复元素而要求不能有重复的组合所以相对于[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)难度提升了不少。
本题同样是求组合总和但就是因为其数组candidates有重复元素而要求不能有重复的组合所以相对于[39.组合总和](https://programmercarl.com/0039.组合总和.html)难度提升了不少。
**关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可**。
@ -296,7 +296,7 @@ class Solution {
}
```
Python
```py
```python
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
@ -392,10 +392,69 @@ var combinationSum2 = function(candidates, target) {
}
};
```
C:
```c
int* path;
int pathTop;
int** ans;
int ansTop;
//记录ans中每一个一维数组的大小
int* length;
int cmp(const void* a1, const void* a2) {
return *((int*)a1) - *((int*)a2);
}
void backTracking(int* candidates, int candidatesSize, int target, int sum, int startIndex) {
if(sum >= target) {
//若sum等于target复制当前path进入
if(sum == target) {
int* tempPath = (int*)malloc(sizeof(int) * pathTop);
int j;
for(j = 0; j < pathTop; j++) {
tempPath[j] = path[j];
}
length[ansTop] = pathTop;
ans[ansTop++] = tempPath;
}
return ;
}
int i;
for(i = startIndex; i < candidatesSize; i++) {
//对同一层树中使用过的元素跳过
if(i > startIndex && candidates[i] == candidates[i-1])
continue;
path[pathTop++] = candidates[i];
sum += candidates[i];
backTracking(candidates, candidatesSize, target, sum, i + 1);
//回溯
sum -= candidates[i];
pathTop--;
}
}
int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
path = (int*)malloc(sizeof(int) * 50);
ans = (int**)malloc(sizeof(int*) * 100);
length = (int*)malloc(sizeof(int) * 100);
pathTop = ansTop = 0;
//快速排序candidates让相同元素挨到一起
qsort(candidates, candidatesSize, sizeof(int), cmp);
backTracking(candidates, candidatesSize, target, 0, 0);
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; i++) {
(*returnColumnSizes)[i] = length[i];
}
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 42. 接雨水
题目链接:https://leetcode-cn.com/problems/trapping-rain-water/
[力扣题目链接](https://leetcode-cn.com/problems/trapping-rain-water/)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
@ -27,7 +27,7 @@
* 输入height = [4,2,0,3,2,5]
* 输出9
 
# 思路
@ -77,7 +77,7 @@
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
```C++
```CPP
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue;
@ -86,7 +86,7 @@ for (int i = 0; i < height.size(); i++) {
在for循环中求左右两边最高柱子代码如下
```C++
```CPP
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
@ -99,14 +99,14 @@ for (int l = i - 1; l >= 0; l--) {
最后,计算该列的雨水高度,代码如下:
```C++
```CPP
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h; // 注意只有h大于零的时候在统计到总和中
```
整体代码如下:
```C++
```CPP
class Solution {
public:
int trap(vector<int>& height) {
@ -152,7 +152,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
int trap(vector<int>& height) {
@ -186,7 +186,7 @@ public:
这个解法可以说是最不好理解的了,所以下面我花了大量的篇幅来介绍这种方法。
单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://mp.weixin.qq.com/s/Xgcqx5eBa3xZabt_LurnNQ)一样,需要我们自己维持顺序,没有现成的容器可以用。
单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。
### 准备工作
@ -287,7 +287,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
求当前凹槽雨水的体积代码如下:
```C++
```CPP
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while持续跟新栈顶元素
int mid = st.top();
st.pop();
@ -301,7 +301,7 @@ while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
关键部分讲完了,整体代码如下:
```C++
```CPP
class Solution {
public:
int trap(vector<int>& height) {
@ -335,7 +335,7 @@ public:
以上代码冗余了一些,但是思路是清晰的,下面我将代码精简一下,如下:
```C++
```CPP
class Solution {
public:
int trap(vector<int>& height) {
@ -366,6 +366,107 @@ public:
Java:
双指针法
```java
class Solution {
public int trap(int[] height) {
int sum = 0;
for (int i = 0; i < height.length; i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i==0 || i== height.length - 1) continue;
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i+1; r < height.length; r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i-1; l >= 0; l--) {
if(height[l] > lHeight) lHeight = height[l];
}
int h = Math.min(lHeight, rHeight) - height[i];
if (h > 0) sum += h;
}
return sum;
}
}
```
动态规划法
```java
class Solution {
public int trap(int[] height) {
int length = height.length;
if (length <= 2) return 0;
int[] maxLeft = new int[length];
int[] maxRight = new int[length];
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]);
// 记录每个柱子右边柱子最大高度
maxRight[length - 1] = height[length - 1];
for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]);
// 求和
int sum = 0;
for (int i = 0; i < length; i++) {
int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
if (count > 0) sum += count;
}
return sum;
}
}
```
单调栈法
```java
class Solution {
public int trap(int[] height){
int size = height.length;
if (size <= 2) return 0;
// in the stack, we push the index of array
// using height[] to access the real height
Stack<Integer> stack = new Stack<Integer>();
stack.push(0);
int sum = 0;
for (int index = 1; index < size; index++){
int stackTop = stack.peek();
if (height[index] < height[stackTop]){
stack.push(index);
}else if (height[index] == height[stackTop]){
// 因为相等的相邻墙左边一个是不可能存放雨水的所以pop左边的index, push当前的index
stack.pop();
stack.push(index);
}else{
//pop up all lower value
int heightAtIdx = height[index];
while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){
int mid = stack.pop();
if (!stack.isEmpty()){
int left = stack.peek();
int h = Math.min(height[left], height[index]) - height[mid];
int w = index - left - 1;
int hold = h * w;
if (hold > 0) sum += hold;
stackTop = stack.peek();
}
}
stack.push(index);
}
}
return sum;
}
}
```
Python:
双指针法
@ -388,6 +489,44 @@ class Solution:
res += res1
return res
```
动态规划
```python3
class Solution:
def trap(self, height: List[int]) -> int:
leftheight, rightheight = [0]*len(height), [0]*len(height)
leftheight[0]=height[0]
for i in range(1,len(height)):
leftheight[i]=max(leftheight[i-1],height[i])
rightheight[-1]=height[-1]
for i in range(len(height)-2,-1,-1):
rightheight[i]=max(rightheight[i+1],height[i])
result = 0
for i in range(0,len(height)):
summ = min(leftheight[i],rightheight[i])-height[i]
result += summ
return result
```
单调栈
```python3
class Solution:
def trap(self, height: List[int]) -> int:
st =[0]
result = 0
for i in range(1,len(height)):
while st!=[] and height[i]>height[st[-1]]:
midh = height[st[-1]]
st.pop()
if st!=[]:
hright = height[i]
hleft = height[st[-1]]
h = min(hright,hleft)-midh
w = i-st[-1]-1
result+=h*w
st.append(i)
return result
```
Go:
@ -398,4 +537,4 @@ JavaScript:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 45.跳跃游戏II
题目地址https://leetcode-cn.com/problems/jump-game-ii/
[力扣题目链接](https://leetcode-cn.com/problems/jump-game-ii/)
给定一个非负整数数组,你最初位于数组的第一个位置。
@ -30,7 +30,7 @@
## 思路
本题相对于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。
本题相对于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)还是难了不少。
但思路是相似的,还是要看最大覆盖范围。
@ -63,7 +63,7 @@
C++代码如下:(详细注释)
```C++
```CPP
// 版本一
class Solution {
public:
@ -106,7 +106,7 @@ public:
代码如下:
```C++
```CPP
// 版本二
class Solution {
public:
@ -132,7 +132,7 @@ public:
## 总结
相信大家可以发现,这道题目相当于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。
相信大家可以发现,这道题目相当于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)难了不止一点。
但代码又十分简单,贪心就是这么巧妙。
@ -236,4 +236,4 @@ var jump = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 46.全排列
题目链接https://leetcode-cn.com/problems/permutations/
[力扣题目链接](https://leetcode-cn.com/problems/permutations/)
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
@ -30,11 +30,11 @@
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
此时我们已经学习了[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、 [131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。
此时我们已经学习了[77.组合问题](https://programmercarl.com/0077.组合.html)、 [131.分割回文串](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html),接下来看一看排列问题。
相信这个排列问题就算是让你用for循环暴力把结果搜索出来这个暴力也不是很好写。
所以正如我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?
所以正如我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?
**因为一些问题能暴力搜出来就已经很不错了!**
@ -84,7 +84,7 @@ if (path.size() == nums.size()) {
* 单层搜索的逻辑
这里和[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[131.切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。
这里和[77.组合问题](https://programmercarl.com/0077.组合.html)、[131.切割问题](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html)最大的不同就是for循环里不用startIndex了。
因为排列问题每次都要从头开始搜索例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
@ -106,7 +106,7 @@ for (int i = 0; i < nums.size(); i++) {
整体C++代码如下:
```C++
```CPP
class Solution {
public:
vector<vector<int>> result;
@ -183,6 +183,32 @@ class Solution {
}
}
```
```java
// 解法2通过判断path中是否存在数字排除已经选择的数字
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return result;
backtrack(nums, path);
return result;
}
public void backtrack(int[] nums, LinkedList<Integer> path) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
}
for (int i =0; i < nums.length; i++) {
// 如果path中已有则跳过
if (path.contains(nums[i])) {
continue;
}
path.add(nums[i]);
backtrack(nums, path);
path.removeLast();
}
}
}
```
Python
```python3
@ -227,24 +253,27 @@ class Solution:
Go
```Go
var result [][]int
func backtrack(nums,pathNums []int,used []bool){
if len(nums)==len(pathNums){
tmp:=make([]int,len(nums))
copy(tmp,pathNums)
result=append(result,tmp)
//result=append(result,pathNums)
return
}
for i:=0;i<len(nums);i++{
if !used[i]{
used[i]=true
pathNums=append(pathNums,nums[i])
backtrack(nums,pathNums,used)
pathNums=pathNums[:len(pathNums)-1]
used[i]=false
}
}
var res [][]int
func permute(nums []int) [][]int {
res = [][]int{}
backTrack(nums,len(nums),[]int{})
return res
}
func backTrack(nums []int,numsLen int,path []int) {
if len(nums)==0{
p:=make([]int,len(path))
copy(p,path)
res = append(res,p)
}
for i:=0;i<numsLen;i++{
cur:=nums[i]
path = append(path,cur)
nums = append(nums[:i],nums[i+1:]...)//直接使用切片
backTrack(nums,len(nums),path)
nums = append(nums[:i],append([]int{cur},nums[i:]...)...)//回溯的时候切片也要复原,元素位置不能变
path = path[:len(path)-1]
}
}
```
@ -286,4 +315,4 @@ var permute = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
## 47.全排列 II
题目链接https://leetcode-cn.com/problems/permutations-ii/
[力扣题目链接](https://leetcode-cn.com/problems/permutations-ii/)
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
@ -33,11 +33,11 @@
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。
这道题目和[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。
这里又涉及到去重了。
在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)我们分别详细讲解了组合问题和子集问题如何去重。
在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) 、[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)我们分别详细讲解了组合问题和子集问题如何去重。
那么排列问题其实也是一样的套路。
@ -51,7 +51,7 @@
**一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果**。
在[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)中已经详解讲解了排列问题的写法,在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下:
在[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html)中已经详解讲解了排列问题的写法,在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) 、[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下:
## C++代码
@ -228,35 +228,31 @@ Go
```go
var res [][]int
func permute(nums []int) [][]int {
res = [][]int{}
sort.Ints(nums)
dfs(nums, make([]int, 0), make([]bool, len(nums)))
return res
res = [][]int{}
backTrack(nums,len(nums),[]int{})
return res
}
func backTrack(nums []int,numsLen int,path []int) {
if len(nums)==0{
p:=make([]int,len(path))
copy(p,path)
res = append(res,p)
}
used := [21]int{}//跟前一题唯一的区别同一层不使用重复的数。关于used的思想carl在递增子序列那一题中提到过
for i:=0;i<numsLen;i++{
if used[nums[i]+10]==1{
continue
}
cur:=nums[i]
path = append(path,cur)
used[nums[i]+10]=1
nums = append(nums[:i],nums[i+1:]...)
backTrack(nums,len(nums),path)
nums = append(nums[:i],append([]int{cur},nums[i:]...)...)
path = path[:len(path)-1]
func dfs(nums, path []int, used []bool) {
if len(path) == len(nums) {
res = append(res, append([]int{}, path...))
return
}
}
m := make(map[int]bool)
for i := 0; i < len(nums); i++ {
// used 从剩余 nums 中选
if used[i] {
continue
}
// m 集合间去重
if _, ok := m[nums[i]]; ok {
continue
}
m[nums[i]] = true
path = append(path, nums[i])
used[i] = true
dfs(nums, path, used)
used[i] = false
path = path[:len(path)-1]
}
}
```
@ -342,4 +338,4 @@ func backTring(nums,subRes []int,res *[][]int,used []bool){
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 第51题. N皇后
题目链接: https://leetcode-cn.com/problems/n-queens/
[力扣题目链接](https://leetcode-cn.com/problems/n-queens/)
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
@ -21,18 +21,27 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q..", // 解法 1
输出:
解法 1
[
[".Q..",
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
解法 2
["..Q.",
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
提示:
@ -171,7 +180,7 @@ bool isValid(int row, int col, vector<string>& chessboard, int n) {
## C++代码
```C++
```CPP
class Solution {
private:
vector<vector<string>> result;
@ -332,7 +341,7 @@ class Solution {
public boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i=0; i<n; ++i) {
for (int i=0; i<row; ++i) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
@ -490,4 +499,4 @@ var solveNQueens = function(n) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

104
problems/0052.N皇后II.md Normal file
View File

@ -0,0 +1,104 @@
<p align="center">
<a href="https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ"><img src="https://img.shields.io/badge/PDF下载-代码随想录-blueviolet" alt=""></a>
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
</p>
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 52. N皇后II
题目链接https://leetcode-cn.com/problems/n-queens-ii/
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
![51n皇后](https://img-blog.csdnimg.cn/20200821152118456.png)
给定一个整数 n返回 n 皇后不同的解决方案的数量。
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
解法 1
[
 [".Q..",
  "...Q",
  "Q...",
  "..Q."],
解法 2
 ["..Q.",
  "Q...",
  "...Q",
  ".Q.."]
]
# 思路
想看:[51.N皇后](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) ,基本没有区别
# C++代码
```CPP
class Solution {
private:
int count = 0;
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
count++;
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col, chessboard, n)) {
chessboard[row][col] = 'Q'; // 放置皇后
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.'; // 回溯
}
}
}
bool isValid(int row, int col, vector<string>& chessboard, int n) {
int count = 0;
// 检查列
for (int i = 0; i < row; i++) { // 这是一个剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查 45度角是否有皇后
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查 135度角是否有皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
public:
int totalNQueens(int n) {
std::vector<std::string> chessboard(n, std::string(n, '.'));
backtracking(n, 0, chessboard);
return count;
}
};
```
# 其他语言补充

View File

@ -9,7 +9,7 @@
## 53. 最大子序和
题目地址:https://leetcode-cn.com/problems/maximum-subarray/
[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/)
给定一个整数数组 nums 找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。
@ -25,7 +25,7 @@
时间复杂度O(n^2)
空间复杂度O(1)
```C++
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
@ -81,7 +81,7 @@ if (count > result) result = count;
那么不难写出如下C++代码(关键地方已经注释)
```C++
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
@ -109,7 +109,7 @@ public:
那么先给出我的dp代码如下有时间的录友可以提前做一做
```C++
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
@ -214,4 +214,4 @@ var maxSubArray = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 53. 最大子序和
题目地址:https://leetcode-cn.com/problems/maximum-subarray/
[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/)
给定一个整数数组 nums 找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。
@ -19,7 +19,7 @@
## 思路
这道题之前我们在讲解贪心专题的时候用贪心算法解决过一次,[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)。
这道题之前我们在讲解贪心专题的时候用贪心算法解决过一次,[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)。
这次我们用动态规划的思路再来分析一次。
@ -65,7 +65,7 @@ dp[0]应该是多少呢?
以上动规五部曲分析完毕,完整代码如下:
```C++
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
@ -87,7 +87,7 @@ public:
## 总结
这道题目用贪心也很巧妙,但有一点绕,需要仔细想一想,如果想回顾一下贪心就看这里吧:[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)
这道题目用贪心也很巧妙,但有一点绕,需要仔细想一想,如果想回顾一下贪心就看这里吧:[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)
动规的解法还是很直接的。
@ -138,7 +138,52 @@ class Solution:
```
Go
```Go
// solution
// 1, dp
// 2, 贪心
func maxSubArray(nums []int) int {
n := len(nums)
// 这里的dp[i] 表示最大的连续子数组和包含num[i] 元素
dp := make([]int,n)
// 初始化由于dp 状态转移方程依赖dp[0]
dp[0] = nums[0]
// 初始化最大的和
mx := nums[0]
for i:=1;i<n;i++ {
// 这里的状态转移方程就是:求最大和
// 会面临2种情况一个是带前面的和一个是不带前面的和
dp[i] = max(dp[i-1]+nums[i],nums[i])
mx = max(mx,dp[i])
}
return mx
}
func max(a,b int) int{
if a>b {
return a
}
return b
}
```
JavaScript
```javascript
const maxSubArray = nums => {
// 数组长度dp初始化
const [len, dp] = [nums.length, [nums[0]]];
// 最大值初始化为dp[0]
let max = dp[0];
for (let i = 1; i < len; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
// 更新最大值
max = Math.max(max, dp[i]);
}
return max;
};
```
@ -146,4 +191,4 @@ Go
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 55. 跳跃游戏
题目链接https://leetcode-cn.com/problems/jump-game/
[力扣题目链接](https://leetcode-cn.com/problems/jump-game/)
给定一个非负整数数组,你最初位于数组的第一个位置。
@ -58,7 +58,7 @@ i每次移动只能在cover的范围内移动每移动一个元素cover得
C++代码如下:
```C++
```CPP
class Solution {
public:
bool canJump(vector<int>& nums) {
@ -161,4 +161,4 @@ var canJump = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 56. 合并区间
题目链接https://leetcode-cn.com/problems/merge-intervals/
[力扣题目链接](https://leetcode-cn.com/problems/merge-intervals/)
给出一个区间的集合,请合并所有重叠的区间。
@ -56,7 +56,7 @@
C++代码如下:
```C++
```CPP
class Solution {
public:
// 按照区间左边界从小到大排序
@ -92,7 +92,7 @@ public:
当然以上代码有冗余一些,可以优化一下,如下:(思路是一样的)
```C++
```CPP
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
@ -126,7 +126,7 @@ public:
那应该怎么办呢?
正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。
正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。
「代码随想录」会把贪心常见的经典题目覆盖到,大家只要认真学习打卡就可以了。
@ -157,6 +157,28 @@ class Solution {
}
}
```
```java
// 版本2
class Solution {
public int[][] merge(int[][] intervals) {
LinkedList<int[]> res = new LinkedList<>();
Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
res.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= res.getLast()[1]) {
int start = res.getLast()[0];
int end = Math.max(intervals[i][1], res.getLast()[1]);
res.removeLast();
res.add(new int[]{start, end});
}
else {
res.add(intervals[i]);
}
}
return res.toArray(new int[res.size()][]);
}
}
```
Python
```python
@ -176,30 +198,27 @@ class Solution:
```
Go
```Go
```golang
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0]<intervals[j][0]
})
res:=[][]int{}
prev:=intervals[0]
for i:=1;i<len(intervals);i++{
cur :=intervals[i]
if prev[1]<cur[0]{
res=append(res,prev)
prev=cur
}else {
prev[1]=max(prev[1],cur[1])
}
}
res=append(res,prev)
return res
//先从小到大排序
sort.Slice(intervals,func(i,j int)bool{
return intervals[i][0]<intervals[j][0]
})
//再弄重复的
for i:=0;i<len(intervals)-1;i++{
if intervals[i][1]>=intervals[i+1][0]{
intervals[i][1]=max(intervals[i][1],intervals[i+1][1])//赋值最大值
intervals=append(intervals[:i+1],intervals[i+2:]...)
i--
}
}
return intervals
}
func max(a, b int) int {
if a > b { return a }
return b
func max(a,b int)int{
if a>b{
return a
}
return b
}
```
@ -229,4 +248,4 @@ var merge = function (intervals) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,8 +10,9 @@
## 59.螺旋矩阵II
题目地址https://leetcode-cn.com/problems/spiral-matrix-ii/
给定一个正整数 n生成一个包含 1 到 n2 所有元素且元素按顺时针顺序螺旋排列的正方形矩阵。
[力扣题目链接](https://leetcode-cn.com/problems/spiral-matrix-ii/)
给定一个正整数 n生成一个包含 1 到 n^2 所有元素且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
@ -33,7 +34,7 @@
结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里哪里有问题,改了那里这里又跑不起来了。
大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/4X-8VRgnYRGd5LYGZ33m4w)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。
大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。
而求解本题依然是要坚持循环不变量原则。
@ -66,7 +67,7 @@
整体C++代码如下:
```C++
```CPP
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
@ -191,33 +192,48 @@ class Solution {
python:
```python
```python3
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
left, right, up, down = 0, n-1, 0, n-1
matrix = [ [0]*n for _ in range(n)]
num = 1
while left<=right and up<=down:
# 填充左到右
for i in range(left, right+1):
matrix[up][i] = num
num += 1
up += 1
# 填充上到下
for i in range(up, down+1):
matrix[i][right] = num
num += 1
right -= 1
# 填充右到左
for i in range(right, left-1, -1):
matrix[down][i] = num
num += 1
down -= 1
# 填充下到上
for i in range(down, up-1, -1):
matrix[i][left] = num
num += 1
# 初始化要填充的正方形
matrix = [[0] * n for _ in range(n)]
left, right, up, down = 0, n - 1, 0, n - 1
number = 1 # 要填充的数字
while left < right and up < down:
# 从左到右填充上边
for x in range(left, right):
matrix[up][x] = number
number += 1
# 从上到下填充右边
for y in range(up, down):
matrix[y][right] = number
number += 1
# 从右到左填充下边
for x in range(right, left, -1):
matrix[down][x] = number
number += 1
# 从下到上填充左边
for y in range(down, up, -1):
matrix[y][left] = number
number += 1
# 缩小要填充的范围
left += 1
right -= 1
up += 1
down -= 1
# 如果阶数为奇数,额外填充一次中心
if n % 2:
matrix[n // 2][n // 2] = number
return matrix
```
@ -302,11 +318,219 @@ func generateMatrix(n int) [][]int {
}
```
Swift:
```swift
func generateMatrix(_ n: Int) -> [[Int]] {
var result = [[Int]](repeating: [Int](repeating: 0, count: n), count: n)
var startRow = 0
var startColumn = 0
var loopCount = n / 2
let mid = n / 2
var count = 1
var offset = 1
var row: Int
var column: Int
while loopCount > 0 {
row = startRow
column = startColumn
for c in column ..< startColumn + n - offset {
result[startRow][c] = count
count += 1
column += 1
}
for r in row ..< startRow + n - offset {
result[r][column] = count
count += 1
row += 1
}
for _ in startColumn ..< column {
result[row][column] = count
count += 1
column -= 1
}
for _ in startRow ..< row {
result[row][column] = count
count += 1
row -= 1
}
startRow += 1
startColumn += 1
offset += 2
loopCount -= 1
}
if (n % 2) != 0 {
result[mid][mid] = count
}
return result
}
```
Rust:
```rust
impl Solution {
pub fn generate_matrix(n: i32) -> Vec<Vec<i32>> {
let mut res = vec![vec![0; n as usize]; n as usize];
let (mut startX, mut startY, mut offset): (usize, usize, usize) = (0, 0, 1);
let mut loopIdx = n/2;
let mid: usize = loopIdx as usize;
let mut count = 1;
let (mut i, mut j): (usize, usize) = (0, 0);
while loopIdx > 0 {
i = startX;
j = startY;
while j < (startY + (n as usize) - offset) {
res[i][j] = count;
count += 1;
j += 1;
}
while i < (startX + (n as usize) - offset) {
res[i][j] = count;
count += 1;
i += 1;
}
while j > startY {
res[i][j] = count;
count += 1;
j -= 1;
}
while i > startX {
res[i][j] = count;
count += 1;
i -= 1;
}
startX += 1;
startY += 1;
offset += 2;
loopIdx -= 1;
}
if(n % 2 == 1) {
res[mid][mid] = count;
}
res
}
}
```
PHP:
```php
class Solution {
/**
* @param Integer $n
* @return Integer[][]
*/
function generateMatrix($n) {
// 初始化数组
$res = array_fill(0, $n, array_fill(0, $n, 0));
$mid = $loop = floor($n / 2);
$startX = $startY = 0;
$offset = 1;
$count = 1;
while ($loop > 0) {
$i = $startX;
$j = $startY;
for (; $j < $startY + $n - $offset; $j++) {
$res[$i][$j] = $count++;
}
for (; $i < $startX + $n - $offset; $i++) {
$res[$i][$j] = $count++;
}
for (; $j > $startY; $j--) {
$res[$i][$j] = $count++;
}
for (; $i > $startX; $i--) {
$res[$i][$j] = $count++;
}
$startX += 1;
$startY += 1;
$offset += 2;
$loop--;
}
if ($n % 2 == 1) {
$res[$mid][$mid] = $count;
}
return $res;
}
}
```
C:
```c
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){
//初始化返回的结果数组的大小
*returnSize = n;
*returnColumnSizes = (int*)malloc(sizeof(int) * n);
//初始化返回结果数组ans
int** ans = (int**)malloc(sizeof(int*) * n);
int i;
for(i = 0; i < n; i++) {
ans[i] = (int*)malloc(sizeof(int) * n);
(*returnColumnSizes)[i] = n;
}
//设置每次循环的起始位置
int startX = 0;
int startY = 0;
//设置二维数组的中间值若n为奇数。需要最后在中间填入数字
int mid = n / 2;
//循环圈数
int loop = n / 2;
//偏移数
int offset = 1;
//当前要添加的元素
int count = 1;
while(loop) {
int i = startX;
int j = startY;
//模拟上侧从左到右
for(; j < startY + n - offset; j++) {
ans[startX][j] = count++;
}
//模拟右侧从上到下
for(; i < startX + n - offset; i++) {
ans[i][j] = count++;
}
//模拟下侧从右到左
for(; j > startY; j--) {
ans[i][j] = count++;
}
//模拟左侧从下到上
for(; i > startX; i--) {
ans[i][j] = count++;
}
//偏移值每次加2
offset+=2;
//遍历起始位置每次+1
startX++;
startY++;
loop--;
}
//若n为奇数需要单独给矩阵中间赋值
if(n%2)
ans[mid][mid] = count;
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 62.不同路径
题目链接https://leetcode-cn.com/problems/unique-paths/
[力扣题目链接](https://leetcode-cn.com/problems/unique-paths/)
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
@ -40,7 +40,7 @@
示例 4
输入m = 3, n = 3
输出6
 
提示:
* 1 <= m, n <= 100
* 题目数据保证答案小于等于 2 * 10^9
@ -59,7 +59,7 @@
此时问题就可以转化为求二叉树叶子节点的个数,代码如下:
```C++
```CPP
class Solution {
private:
int dfs(int i, int j, int m, int n) {
@ -128,7 +128,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1;
以上动规五部曲分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int uniquePaths(int m, int n) {
@ -149,7 +149,7 @@ public:
其实用一个一维数组也可以理解是滚动数组就可以了但是不利于理解可以优化点空间建议先理解了二维在理解一维C++代码如下:
```C++
```CPP
class Solution {
public:
int uniquePaths(int m, int n) {
@ -187,7 +187,7 @@ public:
例如如下代码是不行的。
```C++
```CPP
class Solution {
public:
int uniquePaths(int m, int n) {
@ -204,7 +204,7 @@ public:
需要在计算分子的时候,不断除以分母,代码如下:
```C++
```CPP
class Solution {
public:
int uniquePaths(int m, int n) {
@ -333,4 +333,4 @@ var uniquePaths = function(m, n) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 63. 不同路径 II
题目链接https://leetcode-cn.com/problems/unique-paths-ii/
[力扣题目链接](https://leetcode-cn.com/problems/unique-paths-ii/)
一个机器人位于一个 m x n 网格的左上角 起始点在下图中标记为“Start” )。
@ -49,11 +49,11 @@
## 思路
这道题相对于[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A) 就是有了障碍。
这道题相对于[62.不同路径](https://programmercarl.com/0062.不同路径.html) 就是有了障碍。
第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢?
[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)中我们已经详细分析了没有障碍的情况有障碍的话其实就是标记对应的dp tabledp数组保持初始值(0)就可以了。
[62.不同路径](https://programmercarl.com/0062.不同路径.html)中我们已经详细分析了没有障碍的情况有障碍的话其实就是标记对应的dp tabledp数组保持初始值(0)就可以了。
动规五部曲:
@ -77,7 +77,7 @@ if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候再推导dp[i
3. dp数组如何初始化
在[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)不同路径中我们给出如下的初始化:
在[62.不同路径](https://programmercarl.com/0062.不同路径.html)不同路径中我们给出如下的初始化:
```
vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始值为0
@ -97,7 +97,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1;
所以本题初始化代码为:
```C++
```CPP
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
@ -111,7 +111,7 @@ for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
代码如下:
```C++
```CPP
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) continue;
@ -135,7 +135,7 @@ for (int i = 1; i < m; i++) {
动规五部分分析完毕对应C++代码如下:
```C++
```CPP
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
@ -159,7 +159,7 @@ public:
## 总结
本题是[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)的障碍版,整体思路大体一致。
本题是[62.不同路径](https://programmercarl.com/0062.不同路径.html)的障碍版,整体思路大体一致。
但就算是做过62.不同路径,在做本题也会有感觉遇到障碍无从下手。
@ -341,4 +341,4 @@ var uniquePathsWithObstacles = function(obstacleGrid) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -7,7 +7,7 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 70. 爬楼梯
题目地址https://leetcode-cn.com/problems/climbing-stairs/
[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/)
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
@ -109,7 +109,7 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
以上五部分析完之后C++代码如下:
```C++
```CPP
// 版本一
class Solution {
public:
@ -130,7 +130,7 @@ public:
当然依然也可以,优化一下空间复杂度,代码如下:
```C++
```CPP
// 版本二
class Solution {
public:
@ -163,7 +163,7 @@ public:
这里我先给出我的实现代码:
```C++
```CPP
class Solution {
public:
int climbStairs(int n) {
@ -196,9 +196,9 @@ public:
## 总结
这道题目和[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)题目基本是一样的,但是会发现本题相比[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)难多了,为什么呢?
这道题目和[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)题目基本是一样的,但是会发现本题相比[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)难多了,为什么呢?
关键是 [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w) 题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。
关键是 [动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html) 题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。
而本题,就需要逐个分析了,大家现在应该初步感受出[关于动态规划,你该了解这些!](https://leetcode-cn.com/circle/article/tNuNnM/)里给出的动规五部曲了。
@ -301,4 +301,4 @@ var climbStairs = function(n) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -13,7 +13,7 @@
## 70. 爬楼梯
链接https://leetcode-cn.com/problems/climbing-stairs/
[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/)
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
@ -38,7 +38,7 @@
## 思路
这道题目 我们在[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) 中已经讲过一次了,原题其实是一道简单动规的题目。
这道题目 我们在[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 中已经讲过一次了,原题其实是一道简单动规的题目。
既然这么简单为什么还要讲呢,其实本题稍加改动就是一道面试好题。
@ -52,7 +52,7 @@
**此时大家应该发现这就是一个完全背包问题了!**
和昨天的题目[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了。
和昨天的题目[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)基本就是一道题了。
动规五部曲分析如下:
@ -62,7 +62,7 @@
2. 确定递推公式
在[动态规划494.目标和](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) 、 [动态规划518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)、[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中我们都讲过了求装满背包有几种方法递推公式一般都是dp[i] += dp[i - nums[j]];
在[动态规划494.目标和](https://programmercarl.com/0494.目标和.html) 、 [动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)、[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中我们都讲过了求装满背包有几种方法递推公式一般都是dp[i] += dp[i - nums[j]];
本题呢dp[i]有几种来源dp[i - 1]dp[i - 2]dp[i - 3] 等等dp[i - j]
@ -84,7 +84,7 @@
5. 举例来推导dp数组
介于本题和[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)几乎是一样的,这里我就不再重复举例了。
介于本题和[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)几乎是一样的,这里我就不再重复举例了。
以上分析完毕C++代码如下:
@ -192,4 +192,4 @@ func climbStairs(n int) int {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 72. 编辑距离
https://leetcode-cn.com/problems/edit-distance/
[力扣题目链接](https://leetcode-cn.com/problems/edit-distance/)
给你两个单词 word1 和 word2请你计算出将 word1 转换成 word2 所使用的最少操作数 。
@ -35,7 +35,7 @@ inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
 
提示:
@ -91,18 +91,18 @@ if (word1[i - 1] != word2[j - 1])
`if (word1[i - 1] != word2[j - 1])`,此时就需要编辑了,如何编辑呢?
操作一word1增加一个元素使其word1[i - 1]与word2[j - 1]相同那么就是以下标i-2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。
* 操作一word1删除一个元素那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 加上一个操作。
`dp[i][j] = dp[i - 1][j] + 1;`
操作二word2添加一个元素使其word1[i - 1]与word2[j - 1]相同那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。
* 操作二word2删除一个元素那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个操作。
`dp[i][j] = dp[i][j - 1] + 1;`
这里有同学发现了,怎么都是添加元素,删除元素去哪了。
这里有同学发现了,怎么都是删除元素,添加元素去哪了。
**word2添加一个元素相当于word1删除一个元素**,例如 `word1 = "ad" word2 = "a"``word1`删除元素`'d'``word2`添加一个元素`'d'`,变成`word1="a", word2="ad"` 最终的操作数是一样! dp数组如下图所示意的
**word2添加一个元素相当于word1删除一个元素**,例如 `word1 = "ad" word2 = "a"``word1`删除元素`'d'``word2`添加一个元素`'d'`,变成`word1="a", word2="ad"` 最终的操作数是一样! dp数组如下图所示意的
```
a a d
@ -123,7 +123,7 @@ if (word1[i - 1] != word2[j - 1])
递归公式代码如下:
```C++
```CPP
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
@ -151,7 +151,7 @@ dp[i][0] 以下标i-1为结尾的字符串word1和空字符串word2
所以C++代码如下:
```C++
```CPP
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
```
@ -175,7 +175,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
代码如下:
```C++
```CPP
for (int i = 1; i <= word1.size(); i++) {
for (int j = 1; j <= word2.size(); j++) {
if (word1[i - 1] == word2[j - 1]) {
@ -198,7 +198,7 @@ for (int i = 1; i <= word1.size(); i++) {
以上动规五部分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int minDistance(string word1, string word2) {
@ -338,4 +338,4 @@ const minDistance = (word1, word2) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 第77题. 组合
题目链接https://leetcode-cn.com/problems/combinations/
[力扣题目链接](https://leetcode-cn.com/problems/combinations/ )
给定两个整数 n 和 k返回 1 ... n 中所有可能的 k 个数的组合。
@ -80,7 +80,7 @@ for (int i = 1; i <= n; i++) {
如果脑洞模拟回溯搜索的过程,绝对可以让人窒息,所以需要抽象图形结构来进一步理解。
**我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中说道回溯法解决的问题都可以抽象为树形结构N叉树用树形结构来理解回溯就容易多了**。
**我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中说道回溯法解决的问题都可以抽象为树形结构N叉树用树形结构来理解回溯就容易多了**。
那么我把组合问题抽象为如下树形结构:
@ -100,7 +100,7 @@ for (int i = 1; i <= n; i++) {
相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。
在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中我们提到了回溯法三部曲,那么我们按照回溯法三部曲开始正式讲解代码了。
在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中我们提到了回溯法三部曲,那么我们按照回溯法三部曲开始正式讲解代码了。
## 回溯法三部曲
@ -173,7 +173,7 @@ for循环每次从startIndex开始遍历然后用path保存取到的节点i
代码如下:
```C++
```CPP
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归控制树的纵向遍历注意下一层搜索要从i+1开始
@ -188,7 +188,7 @@ backtracking的下面部分就是回溯的操作了撤销本次处理的结
关键地方都讲完了组合问题C++完整代码如下:
```C++
```CPP
class Solution {
private:
vector<vector<int>> result; // 存放符合条件结果的集合
@ -214,7 +214,7 @@ public:
};
```
还记得我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的回溯法模板么?
还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么?
如下:
```
@ -435,9 +435,62 @@ func backtrack(n,k,start int,track []int){
}
```
C:
```c
int* path;
int pathTop;
int** ans;
int ansTop;
void backtracking(int n, int k,int startIndex) {
//当path中元素个数为k个时我们需要将path数组放入ans二维数组中
if(pathTop == k) {
//path数组为我们动态申请若直接将其地址放入二维数组path数组中的值会随着我们回溯而逐渐变化
//因此创建新的数组存储path中的值
int* temp = (int*)malloc(sizeof(int) * k);
int i;
for(i = 0; i < k; i++) {
temp[i] = path[i];
}
ans[ansTop++] = temp;
return ;
}
int j;
for(j = startIndex; j <=n ;j++) {
//将当前结点放入path数组
path[pathTop++] = j;
//进行递归
backtracking(n, k, j + 1);
//进行回溯,将数组最上层结点弹出
pathTop--;
}
}
int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
//path数组存储符合条件的结果
path = (int*)malloc(sizeof(int) * k);
//ans二维数组存储符合条件的结果数组的集合。数组足够大避免极端情况
ans = (int**)malloc(sizeof(int*) * 10000);
pathTop = ansTop = 0;
//回溯算法
backtracking(n, k, 1);
//最后的返回大小为ans数组大小
*returnSize = ansTop;
//returnColumnSizes数组存储ans二维数组对应下标中一维数组的长度都为k
*returnColumnSizes = (int*)malloc(sizeof(int) *(*returnSize));
int i;
for(i = 0; i < *returnSize; i++) {
(*returnColumnSizes)[i] = k;
}
//返回ans二维数组
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中我们通过回溯搜索法解决了n个数中求k个数的组合问题。
在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中我们通过回溯搜索法解决了n个数中求k个数的组合问题。
> 可以直接看我的B栈视频讲解[带你学透回溯算法-组合问题的剪枝操作](https://www.bilibili.com/video/BV1wi4y157er)
@ -18,7 +18,7 @@
链接https://leetcode-cn.com/problems/combinations/
**看本篇之前,需要先看[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)**。
**看本篇之前,需要先看[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)**。
大家先回忆一下[77. 组合]给出的回溯法的代码:
@ -242,11 +242,62 @@ var combine = function(n, k) {
};
```
C:
```c
int* path;
int pathTop;
int** ans;
int ansTop;
void backtracking(int n, int k,int startIndex) {
//当path中元素个数为k个时我们需要将path数组放入ans二维数组中
if(pathTop == k) {
//path数组为我们动态申请若直接将其地址放入二维数组path数组中的值会随着我们回溯而逐渐变化
//因此创建新的数组存储path中的值
int* temp = (int*)malloc(sizeof(int) * k);
int i;
for(i = 0; i < k; i++) {
temp[i] = path[i];
}
ans[ansTop++] = temp;
return ;
}
int j;
for(j = startIndex; j <= n- (k - pathTop) + 1;j++) {
//将当前结点放入path数组
path[pathTop++] = j;
//进行递归
backtracking(n, k, j + 1);
//进行回溯,将数组最上层结点弹出
pathTop--;
}
}
int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
//path数组存储符合条件的结果
path = (int*)malloc(sizeof(int) * k);
//ans二维数组存储符合条件的结果数组的集合。数组足够大避免极端情况
ans = (int**)malloc(sizeof(int*) * 10000);
pathTop = ansTop = 0;
//回溯算法
backtracking(n, k, 1);
//最后的返回大小为ans数组大小
*returnSize = ansTop;
//returnColumnSizes数组存储ans二维数组对应下标中一维数组的长度都为k
*returnColumnSizes = (int*)malloc(sizeof(int) *(*returnSize));
int i;
for(i = 0; i < *returnSize; i++) {
(*returnColumnSizes)[i] = k;
}
//返回ans二维数组
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 第78题. 子集
题目地址https://leetcode-cn.com/problems/subsets/
[力扣题目链接](https://leetcode-cn.com/problems/subsets/)
给定一组不含重复元素的整数数组 nums返回该数组所有可能的子集幂集
@ -31,7 +31,7 @@
## 思路
求子集问题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:分割问题!](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)又不一样了。
求子集问题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:分割问题!](https://programmercarl.com/0131.分割回文串.html)又不一样了。
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,**那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!**
@ -101,7 +101,7 @@ for (int i = startIndex; i < nums.size(); i++) {
## C++代码
根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板:
根据[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)给出的回溯算法模板:
```
void backtracking(参数) {
@ -120,7 +120,7 @@ void backtracking(参数) {
可以写出如下回溯算法C++代码:
```C++
```CPP
class Solution {
private:
vector<vector<int>> result;
@ -157,15 +157,15 @@ public:
相信大家经过了
* 组合问题:
* [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)
* [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)
* [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)
* [回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
* [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)
* [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)
* [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)
* [回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)
* [回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)
* [回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
* [回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)
* [回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)
* 分割问题:
* [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)
* [回溯算法复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)
* [回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)
* [回溯算法复原IP地址](https://programmercarl.com/0093.复原IP地址.html)
洗礼之后,发现子集问题还真的有点简单了,其实这就是一道标准的模板题。
@ -268,4 +268,4 @@ var subsets = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -0,0 +1,324 @@
# 84.柱状图中最大的矩形
[力扣题目链接](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/)
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210803220437.png)
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210803220506.png)
# 思路
本题和[42. 接雨水](https://programmercarl.com/0042.接雨水.html),是遥相呼应的两道题目,建议都要仔细做一做,原理上有很多相同的地方,但细节上又有差异,更可以加深对单调栈的理解!
其实这两道题目先做那一道都可以但我先写的42.接雨水的题解,所以如果没做过接雨水的话,建议先做一做接雨水,可以参考我的题解:[42. 接雨水](https://programmercarl.com/0042.接雨水.html)
我们先来看一下双指针的解法:
## 双指针解法
```CPP
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sum = 0;
for (int i = 0; i < heights.size(); i++) {
int left = i;
int right = i;
for (; left >= 0; left--) {
if (heights[left] < heights[i]) break;
}
for (; right < heights.size(); right++) {
if (heights[right] < heights[i]) break;
}
int w = right - left - 1;
int h = heights[i];
sum = max(sum, w * h);
}
return sum;
}
};
```
如上代码并不能通过leetcode超时了因为时间复杂度是O(n^2)。
## 动态规划
本题动态规划的写法整体思路和[42. 接雨水](https://programmercarl.com/0042.接雨水.html)是一致的,但要比[42. 接雨水](https://programmercarl.com/0042.接雨水.html)难一些。
难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。
所以需要循环查找也就是下面在寻找的过程中使用了while详细请看下面注释整理思路在题解[42. 接雨水](https://programmercarl.com/0042.接雨水.html)中已经介绍了。
```CPP
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
vector<int> minLeftIndex(heights.size());
vector<int> minRightIndex(heights.size());
int size = heights.size();
// 记录每个柱子 左边第一个小于该柱子的下标
minLeftIndex[0] = -1; // 注意这里初始化防止下面while死循环
for (int i = 1; i < size; i++) {
int t = i - 1;
// 这里不是用if而是不断向左寻找的过程
while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
minLeftIndex[i] = t;
}
// 记录每个柱子 右边第一个小于该柱子的下标
minRightIndex[size - 1] = size; // 注意这里初始化防止下面while死循环
for (int i = size - 2; i >= 0; i--) {
int t = i + 1;
// 这里不是用if而是不断向右寻找的过程
while (t < size && heights[t] >= heights[i]) t = minRightIndex[t];
minRightIndex[i] = t;
}
// 求和
int result = 0;
for (int i = 0; i < size; i++) {
int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
result = max(sum, result);
}
return result;
}
};
```
## 单调栈
本地单调栈的解法和接雨水的题目是遥相呼应的。
为什么这么说呢,[42. 接雨水](https://programmercarl.com/0042.接雨水.html)是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。
**这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小**。
在题解[42. 接雨水](https://programmercarl.com/0042.接雨水.html)中我讲解了接雨水的单调栈从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
那么因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!
我来举一个例子,如图:
![84.柱状图中最大的矩形](https://img-blog.csdnimg.cn/20210223155303971.jpg)
只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。
所以本题单调栈的顺序正好与接雨水反过来。
此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度**
理解这一点,对单调栈就掌握的比较到位了。
除了栈内元素顺序和接雨水不同,剩下的逻辑就都差不多了,在题解[42. 接雨水](https://programmercarl.com/0042.接雨水.html)我已经对单调栈的各个方面做了详细讲解,这里就不赘述了。
剩下就是分析清楚如下三种情况:
* 情况一当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况
* 情况二当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
* 情况三当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
C++代码如下:
```CPP
// 版本一
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> st;
heights.insert(heights.begin(), 0); // 数组头部加入元素0
heights.push_back(0); // 数组尾部加入元素0
st.push(0);
int result = 0;
// 第一个元素已经入栈从下表1开始
for (int i = 1; i < heights.size(); i++) {
// 注意heights[i] 是和heights[st.top()] 比较 st.top()是下表
if (heights[i] > heights[st.top()]) {
st.push(i);
} else if (heights[i] == heights[st.top()]) {
st.pop(); // 这个可以加,可以不加,效果一样,思路不同
st.push(i);
} else {
while (heights[i] < heights[st.top()]) { // 注意是while
int mid = st.top();
st.pop();
int left = st.top();
int right = i;
int w = right - left - 1;
int h = heights[mid];
result = max(result, w * h);
}
st.push(i);
}
}
return result;
}
};
```
代码精简之后:
```CPP
// 版本二
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> st;
heights.insert(heights.begin(), 0); // 数组头部加入元素0
heights.push_back(0); // 数组尾部加入元素0
st.push(0);
int result = 0;
for (int i = 1; i < heights.size(); i++) {
while (heights[i] < heights[st.top()]) {
int mid = st.top();
st.pop();
int w = i - st.top() - 1;
int h = heights[mid];
result = max(result, w * h);
}
st.push(i);
}
return result;
}
};
```
这里我依然建议大家按部就班把版本一写出来,把情况一二三分析清楚,然后在精简代码到版本二。 直接看版本二容易忽略细节!
## 其他语言版本
Java:
动态规划
```java
class Solution {
public int largestRectangleArea(int[] heights) {
int length = heights.length;
int[] minLeftIndex = new int [length];
int[] maxRigthIndex = new int [length];
// 记录左边第一个小于该柱子的下标
minLeftIndex[0] = -1 ;
for (int i = 1; i < length; i++) {
int t = i - 1;
// 这里不是用if而是不断向右寻找的过程
while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
minLeftIndex[i] = t;
}
// 记录每个柱子 右边第一个小于该柱子的下标
maxRigthIndex[length - 1] = length;
for (int i = length - 2; i >= 0; i--) {
int t = i + 1;
while(t < length && heights[t] >= heights[i]) t = maxRigthIndex[t];
maxRigthIndex[i] = t;
}
// 求和
int result = 0;
for (int i = 0; i < length; i++) {
int sum = heights[i] * (maxRigthIndex[i] - minLeftIndex[i] - 1);
result = Math.max(sum, result);
}
return result;
}
}
```
单调栈
```java
class Solution {
int largestRectangleArea(int[] heights) {
Stack<Integer> st = new Stack<Integer>();
// 数组扩容,在头和尾各加入一个元素
int [] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
for (int index = 0; index < heights.length; index++){
newHeights[index + 1] = heights[index];
}
heights = newHeights;
st.push(0);
int result = 0;
// 第一个元素已经入栈从下表1开始
for (int i = 1; i < heights.length; i++) {
// 注意heights[i] 是和heights[st.top()] 比较 st.top()是下表
if (heights[i] > heights[st.peek()]) {
st.push(i);
} else if (heights[i] == heights[st.peek()]) {
st.pop(); // 这个可以加,可以不加,效果一样,思路不同
st.push(i);
} else {
while (heights[i] < heights[st.peek()]) { // 注意是while
int mid = st.peek();
st.pop();
int left = st.peek();
int right = i;
int w = right - left - 1;
int h = heights[mid];
result = Math.max(result, w * h);
}
st.push(i);
}
}
return result;
}
}
```
Python:
动态规划
```python3
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
result = 0
minleftindex, minrightindex = [0]*len(heights), [0]*len(heights)
minleftindex[0]=-1
for i in range(1,len(heights)):
t = i-1
while t>=0 and heights[t]>=heights[i]: t=minleftindex[t]
minleftindex[i]=t
minrightindex[-1]=len(heights)
for i in range(len(heights)-2,-1,-1):
t=i+1
while t<len(heights) and heights[t]>=heights[i]: t=minrightindex[t]
minrightindex[i]=t
for i in range(0,len(heights)):
left = minleftindex[i]
right = minrightindex[i]
summ = (right-left-1)*heights[i]
result = max(result,summ)
return result
```
单调栈 版本二
```python3
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
heights.insert(0,0) # 数组头部加入元素0
heights.append(0) # 数组尾部加入元素0
st = [0]
result = 0
for i in range(1,len(heights)):
while st!=[] and heights[i]<heights[st[-1]]:
midh = heights[st[-1]]
st.pop()
if st!=[]:
minrightindex = i
minleftindex = st[-1]
summ = (minrightindex-minleftindex-1)*midh
result = max(summ,result)
st.append(i)
return result
```
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 第90题.子集II
题目链接https://leetcode-cn.com/problems/subsets-ii/
[力扣题目链接](https://leetcode-cn.com/problems/subsets-ii/)
给定一个可能包含重复元素的整数数组 nums返回该数组所有可能的子集幂集
@ -30,11 +30,11 @@
## 思路
做本题之前一定要先做[78.子集](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)。
做本题之前一定要先做[78.子集](https://programmercarl.com/0078.子集.html)。
这道题目和[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)区别就是集合里有重复元素了,而且求取的子集要去重。
这道题目和[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)区别就是集合里有重复元素了,而且求取的子集要去重。
那么关于回溯算法中的去重问题,**在[40.组合总和II](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中已经详细讲解过了,和本题是一个套路**。
那么关于回溯算法中的去重问题,**在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html)中已经详细讲解过了,和本题是一个套路**。
**剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要**。
@ -44,11 +44,11 @@
从图中可以看出同一树层上重复取2 就要过滤掉同一树枝上就可以重复取2因为同一树枝上元素的集合才是唯一子集
本题就是其实就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了,所以我就直接给出代码了:
本题就是其实就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了,所以我就直接给出代码了:
## C++代码
```
```c++
class Solution {
private:
vector<vector<int>> result;
@ -80,11 +80,10 @@ public:
return result;
}
};
```
使用set去重的版本。
```
```c++
class Solution {
private:
vector<vector<int>> result;
@ -113,7 +112,6 @@ public:
return result;
}
};
```
## 补充
@ -124,7 +122,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
private:
vector<vector<int>> result;
@ -151,7 +149,6 @@ public:
return result;
}
};
```
## 总结
@ -288,4 +285,4 @@ var subsetsWithDup = function(nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
## 93.复原IP地址
题目地址:https://leetcode-cn.com/problems/restore-ip-addresses/
[力扣题目链接](https://leetcode-cn.com/problems/restore-ip-addresses/)
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
@ -45,11 +45,11 @@ s 仅由数字组成
## 思路
做这道题目之前,最好先把[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)这个做了。
做这道题目之前,最好先把[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)这个做了。
这道题目相信大家刚看的时候,应该会一脸茫然。
其实只要意识到这是切割问题,**切割问题就可以使用回溯搜索法把所有可能性搜出来**,和刚做过的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)就十分类似了。
其实只要意识到这是切割问题,**切割问题就可以使用回溯搜索法把所有可能性搜出来**,和刚做过的[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)就十分类似了。
切割问题可以抽象为树型结构,如图:
@ -60,7 +60,7 @@ s 仅由数字组成
* 递归参数
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我们就提到切割问题类似组合问题。
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中我们就提到切割问题类似组合问题。
startIndex一定是需要的因为不能重复分割记录下一层递归分割的起始位置。
@ -76,7 +76,7 @@ startIndex一定是需要的因为不能重复分割记录下一层递归
* 递归终止条件
终止条件和[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)情况就不同了本题明确要求只会分成4段所以不能用切割线切到最后作为终止条件而是分割的段数作为终止条件。
终止条件和[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)情况就不同了本题明确要求只会分成4段所以不能用切割线切到最后作为终止条件而是分割的段数作为终止条件。
pointNum表示逗点数量pointNum为3说明字符串分成了4段了。
@ -96,7 +96,7 @@ if (pointNum == 3) { // 逗点数量为3时分隔结束
* 单层搜索的逻辑
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中已经讲过在循环遍历中如何截取子串。
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中已经讲过在循环遍历中如何截取子串。
在`for (int i = startIndex; i < s.size(); i++)`循环中 [startIndex, i]这个区间就是截取的子串需要判断这个子串是否合法
@ -164,7 +164,7 @@ bool isValid(const string& s, int start, int end) {
## C++代码
根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板:
根据[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)给出的回溯算法模板:
```
void backtracking(参数) {
@ -183,7 +183,7 @@ void backtracking(参数) {
可以写出如下回溯算法C++代码:
```C++
```CPP
class Solution {
private:
vector<string> result;// 记录结果
@ -239,11 +239,11 @@ public:
## 总结
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我列举的分割字符串的难点,本题都覆盖了。
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中我列举的分割字符串的难点,本题都覆盖了。
而且本题还需要操作字符串添加逗号作为分隔符,并验证区间的合法性。
可以说是[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)的加强版。
可以说是[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)的加强版。
在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少!
@ -309,7 +309,35 @@ class Solution {
```
python版本
```python
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
res = []
path = [] # 存放分割后的字符
# 判断数组中的数字是否合法
def isValid(p):
if p == '0': return True # 解决"0000"
if p[0] == '0': return False
if int(p) > 0 and int(p) <256: return True
return False
def backtrack(s, startIndex):
if len(s) > 12: return # 字符串长度最大为12
if len(path) == 4 and startIndex == len(s): # 确保切割完且切割后的长度为4
res.append(".".join(path[:])) # 字符拼接
return
for i in range(startIndex, len(s)):
if len(s) - startIndex > 3*(4 - len(path)): continue # 剪枝,剩下的字符串大于允许的最大长度则跳过
p = s[startIndex:i+1] # 分割字符
if isValid(p): # 判断字符是否有效
path.append(p)
else: continue
backtrack(s, i + 1) # 寻找i+1为起始位置的子串
path.pop()
backtrack(s, 0)
return res
```
```python
class Solution(object):
def restoreIpAddresses(self, s):
@ -453,4 +481,4 @@ func isNormalIp(s string,startIndex,end int)bool{
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 96.不同的二叉搜索树
题目链接:https://leetcode-cn.com/problems/unique-binary-search-trees/
[力扣题目链接](https://leetcode-cn.com/problems/unique-binary-search-trees/)
给定一个整数 n求以 1 ... n 为节点组成的二叉搜索树有多少种?
@ -20,7 +20,7 @@
这道题目描述很简短,但估计大部分同学看完都是懵懵的状态,这得怎么统计呢?
关于什么是二叉搜索树,我们之前在讲解二叉树专题的时候已经详细讲解过了,也可以看看这篇[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)在回顾一波。
关于什么是二叉搜索树,我们之前在讲解二叉树专题的时候已经详细讲解过了,也可以看看这篇[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)在回顾一波。
了解了二叉搜索树之后,我们应该先举几个例子,画画图,看看有没有什么规律,如图:
@ -103,7 +103,7 @@ j相当于是头结点的元素从1遍历到i为止。
代码如下:
```C++
```CPP
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
@ -123,7 +123,7 @@ n为5时候的dp数组状态如图
综上分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int numTrees(int n) {
@ -234,4 +234,4 @@ const numTrees =(n) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -7,9 +7,9 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 98.验证二叉搜索树
# 98.验证二叉搜索树
题目地址:https://leetcode-cn.com/problems/validate-binary-search-tree/
[力扣题目链接](https://leetcode-cn.com/problems/validate-binary-search-tree/)
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
@ -22,7 +22,7 @@
![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png)
## 思路
# 思路
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
@ -32,7 +32,7 @@
可以递归中序遍历将二叉搜索树转变成一个数组,代码如下:
```C++
```CPP
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
@ -44,7 +44,7 @@ void traversal(TreeNode* root) {
然后只要比较一下,这个数组是否是有序的,**注意二叉搜索树中不能有重复元素**。
```C++
```CPP
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
@ -55,7 +55,7 @@ return true;
整体代码如下:
```C++
```CPP
class Solution {
private:
vector<int> vec;
@ -103,7 +103,7 @@ if (root->val > root->left->val && root->val < root->right->val) {
![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png)
节点10小于左节点5于右节点15但右子树里出现了一个6 这就不符合了!
节点10大于左节点5于右节点15但右子树里出现了一个6 这就不符合了!
* 陷阱2
@ -121,7 +121,7 @@ if (root->val > root->left->val && root->val < root->right->val) {
要定义一个longlong的全局变量用来比较遍历的节点是否有序因为后台测试数据中有int最小值所以定义为longlong的类型初始化为longlong最小值。
注意递归函数要有bool类型的返回值 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 中讲了只有寻找某一条边或者一个节点的时候递归函数会有bool类型的返回值。
注意递归函数要有bool类型的返回值 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html) 中讲了只有寻找某一条边或者一个节点的时候递归函数会有bool类型的返回值。
其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。
@ -163,7 +163,7 @@ return left && right;
整体代码如下:
```C++
```CPP
class Solution {
public:
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
@ -189,7 +189,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
@ -210,11 +210,11 @@ public:
## 迭代法
可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)
可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)[二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html)
迭代法中序遍历稍加改动就可以了,代码如下:
```C++
```CPP
class Solution {
public:
bool isValidBST(TreeNode* root) {
@ -240,9 +240,9 @@ public:
};
```
在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。
在[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。
## 总结
# 总结
这道题目是一个简单题,但对于没接触过的同学还是有难度的。
@ -251,10 +251,10 @@ public:
只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。
## 其他语言版本
# 其他语言版本
Java
## Java
```Java
class Solution {
@ -336,38 +336,62 @@ class Solution {
}
```
Python
## Python
**递归** - 利用BST中序遍历特性,把树"压缩"成数组
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
# 递归法
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
res = [] //把二叉搜索树按中序遍历写成list
def buildalist(root):
if not root: return
buildalist(root.left) //左
res.append(root.val) //中
buildalist(root.right) //右
return res
buildalist(root)
return res == sorted(res) and len(set(res)) == len(res) //检查list里的数有没有重复元素以及是否按从小到大排列
# 思路: 利用BST中序遍历的特性.
# 中序遍历输出的二叉搜索树节点的数值是有序序列
candidate_list = []
def __traverse(root: TreeNode) -> None:
nonlocal candidate_list
if not root:
return
__traverse(root.left)
candidate_list.append(root.val)
__traverse(root.right)
def __is_sorted(nums: list) -> bool:
for i in range(1, len(nums)):
if nums[i] <= nums[i - 1]: # ⚠️ 注意: Leetcode定义二叉搜索树中不能有重复元素
return False
return True
__traverse(root)
res = __is_sorted(candidate_list)
return res
```
# 简单递归法
**递归** - 标准做法
```python
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def isBST(root, min_val, max_val):
if not root: return True
if root.val >= max_val or root.val <= min_val:
# 规律: BST的中序遍历节点数值是从小到大.
cur_max = -float("INF")
def __isValidBST(root: TreeNode) -> bool:
nonlocal cur_max
if not root:
return True
is_left_valid = __isValidBST(root.left)
if cur_max < root.val:
cur_max = root.val
else:
return False
return isBST(root.left, min_val, root.val) and isBST(root.right, root.val, max_val)
return isBST(root, float("-inf"), float("inf"))
is_right_valid = __isValidBST(root.right)
return is_left_valid and is_right_valid
return __isValidBST(root)
```
# 迭代-中序遍历
```python
迭代-中序遍历
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
stack = []
@ -386,7 +410,8 @@ class Solution:
return True
```
Go
## Go
```Go
import "math"
@ -429,9 +454,9 @@ func isValidBST(root *TreeNode) bool {
}
```
JavaScript版本
## JavaScript
> 辅助数组解决
辅助数组解决
```javascript
/**
@ -464,7 +489,7 @@ var isValidBST = function (root) {
};
```
> 递归中解决
递归中解决
```javascript
/**
@ -504,4 +529,4 @@ var isValidBST = function (root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
# 100. 相同的树
题目地址https://leetcode-cn.com/problems/same-tree/
[力扣题目链接](https://leetcode-cn.com/problems/same-tree/)
给定两个二叉树,编写一个函数来检验它们是否相同。
@ -23,11 +23,11 @@
# 思路
在[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
在[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。
**如果没有读过[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)这一篇,请认真读完再做这道题,就会有感觉了。**
**如果没有读过[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)这一篇,请认真读完再做这道题,就会有感觉了。**
递归三部曲中:
@ -42,7 +42,7 @@
bool compare(TreeNode* tree1, TreeNode* tree2)
```
分析过程同[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。
分析过程同[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html)。
2. 确定终止条件
@ -61,14 +61,14 @@ bool compare(TreeNode* tree1, TreeNode* tree2)
此时tree1、tree2节点不为空且数值也不相同的情况我们也处理了。
代码如下:
```C++
```CPP
if (tree1 == NULL && tree2 != NULL) return false;
else if (tree1 != NULL && tree2 == NULL) return false;
else if (tree1 == NULL && tree2 == NULL) return true;
else if (tree1->val != tree2->val) return false; // 注意这里我没有使用else
```
分析过程同[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)
分析过程同[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html)
3. 确定单层递归的逻辑
@ -77,7 +77,7 @@ else if (tree1->val != tree2->val) return false; // 注意这里我没有
代码如下:
```C++
```CPP
bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左
bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右
bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理)
@ -85,7 +85,7 @@ return isSame;
```
最后递归的C++整体代码如下:
```C++
```CPP
class Solution {
public:
bool compare(TreeNode* tree1, TreeNode* tree2) {
@ -119,7 +119,7 @@ public:
## 递归
```C++
```CPP
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
@ -138,30 +138,30 @@ public:
## 迭代法
```C++
lass Solution {
```CPP
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == NULL && q == NULL) return true;
if (p == NULL || q == NULL) return false;
queue<TreeNode*> que;
que.push(p); //
que.push(q); //
que.push(p); // 添加根节点p
que.push(q); // 添加根节点q
while (!que.empty()) { //
TreeNode* leftNode = que.front(); que.pop();
TreeNode* rightNode = que.front(); que.pop();
if (!leftNode && !rightNode) { //
if (!leftNode && !rightNode) { // 若p的节点与q的节点都为空
continue;
}
//
// 若p的节点与q的节点有一个为空或p的节点的值与q节点不同
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
que.push(leftNode->left); //
que.push(rightNode->left); //
que.push(leftNode->right); //
que.push(rightNode->right); //
que.push(leftNode->left); // 添加p节点的左子树节点
que.push(rightNode->left); // 添加q节点的左子树节点
que.push(leftNode->right); // 添加p节点的右子树节点
que.push(rightNode->right); // 添加q节点的右子树节点
}
return true;
}
@ -172,8 +172,72 @@ public:
Java
Python
```java
// 递归法
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
else if (q == null || p == null) return false;
else if (q.val != p.val) return false;
return isSameTree(q.left, p.left) && isSameTree(q.right, p.right);
}
}
```
```java
// 迭代法
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
Queue<TreeNode> que= new LinkedList<TreeNode>();
que.offer(p);
que.offer(q);
while(!que.isEmpty()){
TreeNode leftNode = que.poll();
TreeNode rightNode = que.poll();
if(leftNode == null && rightNode == null) continue;
if(leftNode == null || rightNode== null || leftNode.val != rightNode.val) return false;
que.offer(leftNode.left);
que.offer(rightNode.left);
que.offer(leftNode.right);
que.offer(rightNode.right);
}
return true;
}
}
```
Python
```python
# 递归法
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q: return True
elif not p or not q: return False
elif p.val != q.val: return False
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
```
```python
# 迭代法
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q: return True
if not p or not q: return False
que = collections.deque()
que.append(p)
que.append(q)
while que:
leftNode = que.popleft()
rightNode = que.popleft()
if not leftNode and not rightNode: continue
if not leftNode or not rightNode or leftNode.val != rightNode.val: return False
que.append(leftNode.left)
que.append(rightNode.left)
que.append(leftNode.right)
que.append(rightNode.right)
return True
```
Go
JavaScript
@ -182,5 +246,5 @@ JavaScript
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 101. 对称二叉树
题目地址https://leetcode-cn.com/problems/symmetric-tree/
[力扣题目链接](https://leetcode-cn.com/problems/symmetric-tree/)
给定一个二叉树,检查它是否是镜像对称的。
@ -73,7 +73,7 @@ bool compare(TreeNode* left, TreeNode* right)
此时左右节点不为空,且数值也不相同的情况我们也处理了。
代码如下:
```C++
```CPP
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
@ -93,7 +93,7 @@ else if (left->val != right->val) return false; // 注意这里我没有
代码如下:
```C++
```CPP
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
@ -104,7 +104,7 @@ return isSame;
最后递归的C++整体代码如下:
```C++
```CPP
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
@ -137,7 +137,7 @@ public:
**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。**
当然我可以把如上代码整理如下:
```C++
```CPP
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
@ -177,7 +177,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
bool isSymmetric(TreeNode* root) {
@ -212,7 +212,7 @@ public:
只要把队列原封不动的改成栈就可以了,我下面也给出了代码。
```C++
```CPP
class Solution {
public:
bool isSymmetric(TreeNode* root) {
@ -251,6 +251,8 @@ public:
# 相关题目推荐
这两道题目基本和本题是一样的只要稍加修改就可以AC。
* 100.相同的树
* 572.另一个树的子树
@ -579,4 +581,4 @@ var isSymmetric = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,13 @@
看完本篇可以一起做了如下两道题目:
* 104.二叉树的最大深度
* 559.N叉树的最大深度
* 559.n叉树的最大深度
## 104.二叉树的最大深度
# 104.二叉树的最大深度
题目地址:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
给定一个二叉树,找出其最大深度。
@ -28,7 +29,7 @@
返回它的最大深度 3 。
### 递归法
## 递归法
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
@ -41,53 +42,53 @@
1. 确定递归函数的参数和返回值参数就是传入树的根节点返回就返回这棵树的深度所以返回值为int类型。
代码如下:
```
int getDepth(TreeNode* node)
```c++
int getdepth(treenode* node)
```
2. 确定终止条件如果为空节点的话就返回0表示高度为0。
代码如下:
```
if (node == NULL) return 0;
```c++
if (node == null) return 0;
```
3. 确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 加1是因为算上当前中间节点就是目前节点为根节点的树的深度。
代码如下:
```
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
int depth = 1 + max(leftDepth, rightDepth); // 中
```c++
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
```
所以整体C++代码如下:
所以整体c++代码如下:
```C++
class Solution {
```c++
class solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
int depth = 1 + max(leftDepth, rightDepth); // 中
int getdepth(treenode* node) {
if (node == null) return 0;
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getDepth(root);
int maxdepth(treenode* root) {
return getdepth(root);
}
};
```
代码精简之后C++代码如下:
```C++
class Solution {
代码精简之后c++代码如下:
```c++
class solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
int maxdepth(treenode* root) {
if (root == null) return 0;
return 1 + max(maxdepth(root->left), maxdepth(root->right));
}
};
@ -98,65 +99,65 @@ public:
本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**)
```C++
class Solution {
```c++
class solution {
public:
int result;
void getDepth(TreeNode* node, int depth) {
void getdepth(treenode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ;
if (node->left == null && node->right == null) return ;
if (node->left) { // 左
depth++; // 深度+1
getDepth(node->left, depth);
getdepth(node->left, depth);
depth--; // 回溯,深度-1
}
if (node->right) { // 右
depth++; // 深度+1
getDepth(node->right, depth);
getdepth(node->right, depth);
depth--; // 回溯,深度-1
}
return ;
}
int maxDepth(TreeNode* root) {
int maxdepth(treenode* root) {
result = 0;
if (root == 0) return result;
getDepth(root, 1);
return result;
}
};
```
**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!**
注意以上代码是为了把细节体现出来,简化一下代码如下:
```C++
class Solution {
public:
int result;
void getDepth(TreeNode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ;
if (node->left) { // 左
getDepth(node->left, depth + 1);
}
if (node->right) { // 右
getDepth(node->right, depth + 1);
}
return ;
}
int maxDepth(TreeNode* root) {
result = 0;
if (root == 0) return result;
getDepth(root, 1);
getdepth(root, 1);
return result;
}
};
```
### 迭代法
**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!**
注意以上代码是为了把细节体现出来,简化一下代码如下:
```c++
class solution {
public:
int result;
void getdepth(treenode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == null && node->right == null) return ;
if (node->left) { // 左
getdepth(node->left, depth + 1);
}
if (node->right) { // 右
getdepth(node->right, depth + 1);
}
return ;
}
int maxdepth(treenode* root) {
result = 0;
if (root == 0) return result;
getdepth(root, 1);
return result;
}
};
```
## 迭代法
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
@ -166,23 +167,23 @@ public:
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
C++代码如下:
c++代码如下:
```C++
class Solution {
```c++
class solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
int maxdepth(treenode* root) {
if (root == null) return 0;
int depth = 0;
queue<TreeNode*> que;
queue<treenode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录深度
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
treenode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
@ -193,19 +194,19 @@ public:
};
```
那么我们可以顺便解决一下N叉树的最大深度问题
那么我们可以顺便解决一下n叉树的最大深度问题
## 559.N叉树的最大深度
# 559.n叉树的最大深度
题目地址:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/)
给定一个 N 叉树,找到其最大深度。
给定一个 n 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
例如,给定一个 3叉树 :
![559.N叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png)
![559.n叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png)
我们应返回其最大深度3。
@ -213,39 +214,39 @@ public:
依然可以提供递归法和迭代法,来解决这个问题,思路是和二叉树思路一样的,直接给出代码如下:
### 递归法
## 递归法
C++代码:
c++代码:
```C++
class Solution {
```c++
class solution {
public:
int maxDepth(Node* root) {
int maxdepth(node* root) {
if (root == 0) return 0;
int depth = 0;
for (int i = 0; i < root->children.size(); i++) {
depth = max (depth, maxDepth(root->children[i]));
depth = max (depth, maxdepth(root->children[i]));
}
return depth + 1;
}
};
```
### 迭代法
## 迭代法
依然是层序遍历,代码如下:
```C++
class Solution {
```c++
class solution {
public:
int maxDepth(Node* root) {
queue<Node*> que;
if (root != NULL) que.push(root);
int maxdepth(node* root) {
queue<node*> que;
if (root != null) que.push(root);
int depth = 0;
while (!que.empty()) {
int size = que.size();
depth++; // 记录深度
for (int i = 0; i < size; i++) {
Node* node = que.front();
node* node = que.front();
que.pop();
for (int j = 0; j < node->children.size(); j++) {
if (node->children[j]) que.push(node->children[j]);
@ -257,45 +258,46 @@ public:
};
```
## 其他语言版本
# 其他语言版本
## java
Java
### 104.二叉树的最大深度
```Java
class Solution {
```java
class solution {
/**
* 递归法
*/
public int maxDepth(TreeNode root) {
public int maxdepth(treenode root) {
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
int leftdepth = maxdepth(root.left);
int rightdepth = maxdepth(root.right);
return math.max(leftdepth, rightdepth) + 1;
}
}
```
```Java
class Solution {
```java
class solution {
/**
* 迭代法,使用层序遍历
*/
public int maxDepth(TreeNode root) {
public int maxdepth(treenode root) {
if(root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque<treenode> deque = new linkedlist<>();
deque.offer(root);
int depth = 0;
while (!deque.isEmpty()) {
while (!deque.isempty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode poll = deque.poll();
treenode poll = deque.poll();
if (poll.left != null) {
deque.offer(poll.left);
}
@ -309,37 +311,68 @@ class Solution {
}
```
Python
### 559.n叉树的最大深度
```java
class solution {
/**
* 迭代法,使用层序遍历
*/
public int maxDepth(Node root) {
if (root == null) return 0;
int depth = 0;
Queue<Node> que = new LinkedList<>();
que.offer(root);
while (!que.isEmpty())
{
depth ++;
int len = que.size();
while (len > 0)
{
Node node = que.poll();
for (int i = 0; i < node.children.size(); i++)
if (node.children.get(i) != null)
que.offer(node.children.get(i));
len--;
}
}
return depth;
}
}
```
104.二叉树的最大深度
> 递归法:
## python
### 104.二叉树的最大深度
递归法:
```python
class Solution:
def maxDepth(self, root: TreeNode) -> int:
return self.getDepth(root)
class solution:
def maxdepth(self, root: treenode) -> int:
return self.getdepth(root)
def getDepth(self, node):
def getdepth(self, node):
if not node:
return 0
leftDepth = self.getDepth(node.left) #左
rightDepth = self.getDepth(node.right) #右
depth = 1 + max(leftDepth, rightDepth) #中
leftdepth = self.getdepth(node.left) #左
rightdepth = self.getdepth(node.right) #右
depth = 1 + max(leftdepth, rightdepth) #中
return depth
```
> 递归法;精简代码
递归法:精简代码
```python
class Solution:
def maxDepth(self, root: TreeNode) -> int:
class solution:
def maxdepth(self, root: treenode) -> int:
if not root:
return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
return 1 + max(self.maxdepth(root.left), self.maxdepth(root.right))
```
> 迭代法:
迭代法:
```python
import collections
class Solution:
def maxDepth(self, root: TreeNode) -> int:
class solution:
def maxdepth(self, root: treenode) -> int:
if not root:
return 0
depth = 0 #记录深度
@ -357,24 +390,25 @@ class Solution:
return depth
```
559.N叉树的最大深度
> 递归法:
### 559.n叉树的最大深度
递归法:
```python
class Solution:
def maxDepth(self, root: 'Node') -> int:
class solution:
def maxdepth(self, root: 'node') -> int:
if not root:
return 0
depth = 0
for i in range(len(root.children)):
depth = max(depth, self.maxDepth(root.children[i]))
depth = max(depth, self.maxdepth(root.children[i]))
return depth + 1
```
> 迭代法:
迭代法:
```python
import collections
class Solution:
def maxDepth(self, root: 'Node') -> int:
class solution:
def maxdepth(self, root: 'node') -> int:
queue = collections.deque()
if root:
queue.append(root)
@ -390,10 +424,10 @@ class Solution:
return depth
```
> 使用栈来模拟后序遍历依然可以
使用栈来模拟后序遍历依然可以
```python
class Solution:
def maxDepth(self, root: 'Node') -> int:
class solution:
def maxdepth(self, root: 'node') -> int:
st = []
if root:
st.append(root)
@ -401,9 +435,9 @@ class Solution:
result = 0
while st:
node = st.pop()
if node != None:
if node != none:
st.append(node) #中
st.append(None)
st.append(none)
depth += 1
for i in range(len(node.children)): #处理孩子
if node.children[i]:
@ -417,15 +451,15 @@ class Solution:
```
Go
## go
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* definition for a binary tree node.
* type treenode struct {
* val int
* left *treenode
* right *treenode
* }
*/
func max (a, b int) int {
@ -435,28 +469,28 @@ func max (a, b int) int {
return b;
}
// 递归
func maxDepth(root *TreeNode) int {
func maxdepth(root *treenode) int {
if root == nil {
return 0;
}
return max(maxDepth(root.Left), maxDepth(root.Right)) + 1;
return max(maxdepth(root.left), maxdepth(root.right)) + 1;
}
// 遍历
func maxDepth(root *TreeNode) int {
func maxdepth(root *treenode) int {
levl := 0;
queue := make([]*TreeNode, 0);
queue := make([]*treenode, 0);
if root != nil {
queue = append(queue, root);
}
for l := len(queue); l > 0; {
for ;l > 0;l-- {
node := queue[0];
if node.Left != nil {
queue = append(queue, node.Left);
if node.left != nil {
queue = append(queue, node.left);
}
if node.Right != nil {
queue = append(queue, node.Right);
if node.right != nil {
queue = append(queue, node.right);
}
queue = queue[1:];
}
@ -469,49 +503,82 @@ func maxDepth(root *TreeNode) int {
```
JavaScript
## javascript
```javascript
var maxDepth = function(root) {
var maxdepth = function(root) {
if (!root) return root
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right))
return 1 + math.max(maxdepth(root.left), maxdepth(root.right))
};
```
二叉树最大深度递归遍历
```javascript
var maxDepth = function(root) {
var maxdepth = function(root) {
//使用递归的方法 递归三部曲
//1. 确定递归函数的参数和返回值
const getDepth=function(node){
const getdepth=function(node){
//2. 确定终止条件
if(node===null){
return 0;
}
//3. 确定单层逻辑
let leftDepth=getDepth(node.left);
let rightDepth=getDepth(node.right);
let depth=1+Math.max(leftDepth,rightDepth);
let leftdepth=getdepth(node.left);
let rightdepth=getdepth(node.right);
let depth=1+math.max(leftdepth,rightdepth);
return depth;
}
return getDepth(root);
return getdepth(root);
};
```
二叉树最大深度层级遍历
```javascript
var maxDepth = function(root) {
//使用递归的方法 递归三部曲
//1. 确定递归函数的参数和返回值
const getDepth=function(node){
//2. 确定终止条件
if(node===null){
return 0;
if(!root) return 0
let count = 0
const queue = [root]
while(queue.length) {
let size = queue.length
/* 层数+1 */
count++
while(size--) {
let node = queue.shift();
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
//3. 确定单层逻辑
let leftDepth=getDepth(node.left);
let rightDepth=getDepth(node.right);
let depth=1+Math.max(leftDepth,rightDepth);
return depth;
}
return getDepth(root);
return count
};
```
N叉树的最大深度 递归写法
```js
var maxDepth = function(root) {
if(!root) return 0
let depth = 0
for(let node of root.children) {
depth = Math.max(depth, maxDepth(node))
}
return depth + 1
}
```
N叉树的最大深度 层序遍历
```js
var maxDepth = function(root) {
if(!root) return 0
let count = 0
let queue = [root]
while(queue.length) {
let size = queue.length
count++
while(size--) {
let node = queue.shift()
node && (queue = [...queue, ...node.children])
}
}
return count
};
```
@ -519,4 +586,4 @@ var maxDepth = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,9 +12,9 @@
* 106.从中序与后序遍历序列构造二叉树
* 105.从前序与中序遍历序列构造二叉树
## 106.从中序与后序遍历序列构造二叉树
# 106.从中序与后序遍历序列构造二叉树
题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/)
根据一棵树的中序遍历与后序遍历构造二叉树。
@ -29,7 +29,7 @@
![106. 从中序与后序遍历序列构造二叉树1](https://img-blog.csdnimg.cn/20210203154316774.png)
### 思路
## 思路
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
@ -59,7 +59,7 @@
不难写出如下代码:(先把框架写出来)
```C++
```CPP
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
// 第一步
@ -95,7 +95,7 @@ TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
**在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必然乱套!**
我在[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)和[数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。
我在[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0035.搜索插入位置.html)和[数组:这个循环可以转懵很多人!](https://programmercarl.com/0059.螺旋矩阵II.html)中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。
首先要切割中序数组,为什么先切割中序数组呢?
@ -105,7 +105,7 @@ TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,如下代码中我坚持左闭右开的原则:
```
```C++
// 找到中序遍历的切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
@ -155,7 +155,7 @@ root->right = traversal(rightInorder, rightPostorder);
### C++完整代码
```C++
```CPP
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
@ -209,7 +209,7 @@ public:
加了日志的代码如下加了日志的代码不要在leetcode上提交容易超时
```C++
```CPP
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
@ -277,7 +277,7 @@ public:
下面给出用下表索引写出的代码版本思路是一样的只不过不用重复定义vector了每次用下表索引来分割
### C++优化版本
```C++
```CPP
class Solution {
private:
// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
@ -325,7 +325,7 @@ public:
那么这个版本写出来依然要打日志进行调试,打日志的版本如下:(**该版本不要在leetcode上提交容易超时**
```C++
```CPP
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
@ -394,9 +394,9 @@ public:
};
```
## 105.从前序与中序遍历序列构造二叉树
# 105.从前序与中序遍历序列构造二叉树
题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)
根据一棵树的前序遍历与中序遍历构造二叉树。
@ -411,7 +411,7 @@ public:
![105. 从前序与中序遍历序列构造二叉树](https://img-blog.csdnimg.cn/20210203154626672.png)
### 思路
## 思路
本题和106是一样的道理。
@ -419,7 +419,7 @@ public:
带日志的版本C++代码如下: **带日志的版本仅用于调试不要在leetcode上提交会超时**
```C++
```CPP
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
@ -493,7 +493,7 @@ public:
105.从前序与中序遍历序列构造二叉树最后版本C++代码:
```C++
```CPP
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
@ -540,7 +540,7 @@ public:
};
```
## 思考题
# 思考题
前序和中序可以唯一确定一颗二叉树。
@ -562,7 +562,7 @@ tree2 的前序遍历是[1 2 3] 后序遍历是[3 2 1]。
所以前序和后序不能唯一确定一颗二叉树!
## 总结
# 总结
之前我们讲的二叉树题目都是各种遍历二叉树,这次开始构造二叉树了,思路其实比较简单,但是真正代码实现出来并不容易。
@ -578,9 +578,9 @@ tree2 的前序遍历是[1 2 3] 后序遍历是[3 2 1]。
## 其他语言版本
# 其他语言版本
Java
## Java
106.从中序与后序遍历序列构造二叉树
@ -607,6 +607,7 @@ class Solution {
for (int i = inLeft; i < inRight; i++) {
if (inorder[i] == rootVal) {
rootIndex = i;
break;
}
}
// 根据rootIndex划分左右子树
@ -653,47 +654,75 @@ class Solution {
}
```
Python
## Python
105.从前序与中序遍历序列构造二叉树
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
//递归法
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder: return None //特殊情况
root = TreeNode(preorder[0]) //新建父节点
p=inorder.index(preorder[0]) //找到父节点在中序遍历的位置(因为没有重复的元素,才可以这样找)
root.left = self.buildTree(preorder[1:p+1],inorder[:p]) //注意左节点时分割中序数组和前续数组的开闭环
root.right = self.buildTree(preorder[p+1:],inorder[p+1:]) //分割中序数组和前续数组
return root
```
# 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
if not preorder:
return None
# 第二步: 前序遍历的第一个就是当前的中间节点.
root_val = preorder[0]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
# 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
preorder_left = preorder[1:1 + len(inorder_left)]
preorder_right = preorder[1 + len(inorder_left):]
# 第六步: 递归
root.left = self.buildTree(preorder_left, inorder_left)
root.right = self.buildTree(preorder_right, inorder_right)
return root
```
106.从中序与后序遍历序列构造二叉树
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
//递归法
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
if not postorder: return None //特殊情况
root = TreeNode(postorder[-1]) //新建父节点
p=inorder.index(postorder[-1]) //找到父节点在中序遍历的位置*因为没有重复的元素,才可以这样找
root.left = self.buildTree(inorder[:p],postorder[:p]) //分割中序数组和后续数组
root.right = self.buildTree(inorder[p+1:],postorder[p:-1]) //注意右节点时分割中序数组和后续数组的开闭环
return root
```
Go
> 106 从中序与后序遍历序列构造二叉树
# 第一步: 特殊情况讨论: 树为空. (递归终止条件)
if not postorder:
return None
# 第二步: 后序遍历的最后一个就是当前的中间节点.
root_val = postorder[-1]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
# 第五步: 切割postorder数组. 得到postorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.
postorder_left = postorder[:len(inorder_left)]
postorder_right = postorder[len(inorder_left): len(postorder) - 1]
# 第六步: 递归
root.left = self.buildTree(inorder_left, postorder_left)
root.right = self.buildTree(inorder_right, postorder_right)
return root
```
## Go
106 从中序与后序遍历序列构造二叉树
```go
/**
@ -726,7 +755,7 @@ func findRootIndex(inorder []int,target int) (index int){
}
```
> 105 从前序与中序遍历序列构造二叉树
105 从前序与中序遍历序列构造二叉树
```go
/**
@ -759,7 +788,8 @@ func findRootIndex(target int,inorder []int) int{
JavaScript
## JavaScript
```javascript
var buildTree = function(inorder, postorder) {
if (!postorder.length) return null
@ -793,4 +823,4 @@ var buildTree = function(preorder, inorder) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,9 +9,9 @@
> 构造二叉搜索树,一不小心就平衡了
## 108.将有序数组转换为二叉搜索树
# 108.将有序数组转换为二叉搜索树
题目链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/
[力扣题目链接](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/)
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
@ -21,14 +21,14 @@
![108.将有序数组转换为二叉搜索树](https://img-blog.csdnimg.cn/20201022164420763.png)
## 思路
# 思路
做这道题目之前大家可以了解一下这几道:
* [106.从中序与后序遍历序列构造二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
* [654.最大二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中其实已经讲过了,如果根据数组构造一颗二叉树。
* [701.二叉搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)
* [450.删除二叉搜索树中的节点](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)
* [106.从中序与后序遍历序列构造二叉树](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)
* [654.最大二叉树](https://programmercarl.com/0654.最大二叉树.html)中其实已经讲过了,如果根据数组构造一颗二叉树。
* [701.二叉搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)
* [450.删除二叉搜索树中的节点](https://programmercarl.com/0450.删除二叉搜索树中的节点.html)
进入正题:
@ -38,11 +38,11 @@
其实这里不用强调平衡二叉搜索树,数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取,**所以想构成不平衡的二叉树是自找麻烦**。
在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)和[二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中其实已经讲过了,如果根据数组构造一颗二叉树。
在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)和[二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)中其实已经讲过了,如果根据数组构造一颗二叉树。
**本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间**。
本题其实要比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 和 [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)简单一些,因为有序数组构造二叉搜索树,寻找分割点就比较容易了。
本题其实要比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 和 [二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)简单一些,因为有序数组构造二叉搜索树,寻找分割点就比较容易了。
分割点就是数组中间位置的节点。
@ -68,11 +68,11 @@
删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。
相信大家如果仔细看了[二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)和[二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw),一定会对递归函数返回值的作用深有感触。
相信大家如果仔细看了[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)和[二叉树:搜索树中的删除操作](https://programmercarl.com/0450.删除二叉搜索树中的节点.html),一定会对递归函数返回值的作用深有感触。
那么本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。
再来看参数首先是传入数组然后就是左下表left和右下表right我们在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下表来操作原数组。
再来看参数首先是传入数组然后就是左下表left和右下表right我们在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下表来操作原数组。
所以代码如下:
@ -83,7 +83,7 @@ TreeNode* traversal(vector<int>& nums, int left, int right)
这里注意,**我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量**。
在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)[35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) 和[59.螺旋矩阵II](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)都详细讲过循环不变量。
在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)[35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html) 和[59.螺旋矩阵II](https://programmercarl.com/0059.螺旋矩阵II.html)都详细讲过循环不变量。
* 确定递归终止条件
@ -98,7 +98,7 @@ if (left > right) return nullptr;
* 确定单层递归的逻辑
首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`**这么写其实有一个问题就是数值越界例如left和right都是最大int这么操作就越界了在[二分法](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中尤其需要注意!**
首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`**这么写其实有一个问题就是数值越界例如left和right都是最大int这么操作就越界了在[二分法](https://programmercarl.com/0035.搜索插入位置.html)中尤其需要注意!**
所以可以这么写:`int mid = left + ((right - left) / 2);`
@ -122,7 +122,7 @@ return root;
* 递归整体代码如下:
```C++
```CPP
class Solution {
private:
TreeNode* traversal(vector<int>& nums, int left, int right) {
@ -150,7 +150,7 @@ public:
模拟的就是不断分割的过程C++代码如下:(我已经详细注释)
```C++
```CPP
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
@ -192,9 +192,9 @@ public:
};
```
## 总结
# 总结
**在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 和 [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)之后,我们顺理成章的应该构造一下二叉搜索树了,一不小心还是一棵平衡二叉搜索树**。
**在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 和 [二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)之后,我们顺理成章的应该构造一下二叉搜索树了,一不小心还是一棵平衡二叉搜索树**。
其实思路也是一样的,不断中间分割,然后递归处理左区间,右区间,也可以说是分治。
@ -205,10 +205,10 @@ public:
最后依然给出迭代的方法,其实就是模拟取中间元素,然后不断分割去构造二叉树的过程。
## 其他语言版本
# 其他语言版本
Java
## Java
递归: 左闭右开 [left,right)
```Java
@ -253,7 +253,8 @@ class Solution {
return root;
}
}
```
```
迭代: 左闭右闭 [left,right]
```java
class Solution {
@ -303,15 +304,10 @@ class Solution {
}
```
Python
## Python
递归法:
```python3
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
#递归法
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def buildaTree(left,right):
@ -326,21 +322,11 @@ class Solution:
return root
```
Go
## Go
> 递归(隐含回溯)
递归(隐含回溯)
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//递归(隐含回溯)
func sortedArrayToBST(nums []int) *TreeNode {
if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回
root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点从中间构造节点
@ -350,21 +336,9 @@ func sortedArrayToBST(nums []int) *TreeNode {
}
```
JavaScript版本
## JavaScript
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} nums
* @return {TreeNode}
*/
var sortedArrayToBST = function (nums) {
const buildTree = (Arr, left, right) => {
if (left > right)
@ -388,4 +362,4 @@ var sortedArrayToBST = function (nums) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,9 +9,9 @@
> 求高度还是求深度,你搞懂了不?
## 110.平衡二叉树
# 110.平衡二叉树
题目地址:https://leetcode-cn.com/problems/balanced-binary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/balanced-binary-tree/)
给定一个二叉树,判断它是否是高度平衡的二叉树。
@ -33,9 +33,10 @@
返回 false 。
## 题外话
# 题外话
咋眼一看这道题目和[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。
咋眼一看这道题目和[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)很像,其实有很大区别。
这里强调一波概念:
@ -50,13 +51,13 @@
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
有的同学一定疑惑,为什么[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中求的是二叉树的最大深度,也用的是后序遍历。
有的同学一定疑惑,为什么[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中求的是二叉树的最大深度,也用的是后序遍历。
**那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这颗树的最大深度,所以才可以使用后序遍历。**
在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历)
在[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历)
```C++
```CPP
class Solution {
public:
int result;
@ -90,7 +91,7 @@ public:
注意以上代码是为了把细节体现出来,简化一下代码如下:
```C++
```CPP
class Solution {
public:
int result;
@ -114,9 +115,9 @@ public:
};
```
## 本题思路
# 本题思路
### 递归
## 递归
此时大家应该明白了既然要求比较高度,必然是要后序遍历。
@ -160,7 +161,7 @@ if (node == NULL) {
代码如下:
```
```CPP
int leftDepth = depth(node->left); // 左
if (leftDepth == -1) return -1;
int rightDepth = depth(node->right); // 右
@ -178,7 +179,7 @@ return result;
代码精简之后如下:
```
```CPP
int leftDepth = getDepth(node->left);
if (leftDepth == -1) return -1;
int rightDepth = getDepth(node->right);
@ -190,7 +191,7 @@ return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
getDepth整体代码如下
```C++
```CPP
int getDepth(TreeNode* node) {
if (node == NULL) {
return 0;
@ -205,7 +206,7 @@ int getDepth(TreeNode* node) {
最后本题整体递归代码如下:
```C++
```CPP
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1
@ -225,9 +226,9 @@ public:
};
```
### 迭代
## 迭代
在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。
在[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。
本题的迭代方式可以先定义一个函数,专门用来求高度。
@ -235,7 +236,7 @@ public:
代码如下:
```C++
```CPP
// cur节点的最大深度就是cur的高度
int getDepth(TreeNode* cur) {
stack<TreeNode*> st;
@ -266,7 +267,7 @@ int getDepth(TreeNode* cur) {
然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下:
```
```CPP
bool isBalanced(TreeNode* root) {
stack<TreeNode*> st;
if (root == NULL) return true;
@ -286,7 +287,7 @@ bool isBalanced(TreeNode* root) {
整体代码如下:
```
```CPP
class Solution {
private:
int getDepth(TreeNode* cur) {
@ -342,7 +343,7 @@ public:
因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
## 总结
# 总结
通过本题可以了解求二叉树深度 和 二叉树高度的差异,求深度适合用前序遍历,而求高度适合用后序遍历。
@ -351,9 +352,9 @@ public:
但是递归方式是一定要掌握的!
## 其他语言版本
# 其他语言版本
Java
## Java
```Java
class Solution {
@ -494,9 +495,9 @@ class Solution {
}
```
Python
## Python
> 递归法:
递归法:
```python
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
@ -513,7 +514,7 @@ class Solution:
return -1 if abs(leftDepth - rightDepth)>1 else 1 + max(leftDepth, rightDepth)
```
> 迭代法:
迭代法:
```python
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
@ -553,7 +554,7 @@ class Solution:
```
Go
## Go
```Go
func isBalanced(root *TreeNode) bool {
if root==nil{
@ -589,7 +590,7 @@ func abs(a int)int{
}
```
JavaScript:
## JavaScript
```javascript
var isBalanced = function(root) {
//还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1
@ -623,4 +624,4 @@ var isBalanced = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,9 +9,9 @@
> 和求最大深度一个套路?
## 111.二叉树的最小深度
# 111.二叉树的最小深度
题目地址:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/)
给定一个二叉树,找出其最小深度。
@ -27,9 +27,9 @@
返回它的最小深度 2.
## 思路
# 思路
看完了这篇[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。
看完了这篇[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html),再来看看如何求最小深度。
直觉上好像和求最大深度差不多,其实还是差不少的。
@ -87,7 +87,7 @@ return result;
代码如下:
```C++
```CPP
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
@ -106,7 +106,7 @@ return result;
遍历的顺序为后序(左右中),可以看出:**求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。**
整体递归代码如下:
```C++
```CPP
class Solution {
public:
int getDepth(TreeNode* node) {
@ -134,7 +134,7 @@ public:
精简之后代码如下:
```C++
```CPP
class Solution {
public:
int minDepth(TreeNode* root) {
@ -154,15 +154,15 @@ public:
## 迭代法
相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。
相对于[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html),本题还可以使用层序遍历的方式来解决,思路是一样的。
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
**需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点**
代码如下:(详细注释)
```C++
```CPP
class Solution {
public:
@ -190,10 +190,10 @@ public:
```
## 其他语言版本
# 其他语言版本
Java
## Java
```Java
class Solution {
@ -253,7 +253,7 @@ class Solution {
}
```
Python
## Python
递归法:
@ -299,7 +299,7 @@ class Solution:
```
Go
## Go
```go
/**
@ -360,7 +360,7 @@ func minDepth(root *TreeNode) int {
```
JavaScript:
## JavaScript
递归法:
@ -413,4 +413,4 @@ var minDepth = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,16 +9,16 @@
> 递归函数什么时候需要返回值
相信很多同学都会疑惑递归函数什么时候要有返回值什么时候没有返回值特别是有的时候递归函数返回类型为bool类型。那么
相信很多同学都会疑惑递归函数什么时候要有返回值什么时候没有返回值特别是有的时候递归函数返回类型为bool类型。
接下来我通过详细讲解如下两道题,来回答这个问题:
那么接下来我通过详细讲解如下两道题,来回答这个问题:
* 112.路径总和
* 113.路径总和II
* 113.路径总和ii
## 112. 路径总和
# 112. 路径总和
题目地址https://leetcode-cn.com/problems/path-sum/
[力扣题目链接](https://leetcode-cn.com/problems/path-sum/)
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
@ -31,11 +31,11 @@
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
### 思路
# 思路
这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和。
### 递归
## 递归
可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树
@ -43,13 +43,11 @@
参数需要二叉树的根节点还需要一个计数器这个计数器用来计算二叉树的一条边之和是否正好是目标和计数器为int型。
**再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?**
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
在文章[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中,我给出了一个结论:
**如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。**
在[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中,因为要遍历树的所有路径,找出深度最深的叶子节点,所以递归函数不要返回值。
* 如果需要搜索整颗二叉树且不用处理递归返回值递归函数就不要返回值。这种情况就是本文下半部分介绍的113.路径总和ii
* 如果需要搜索整颗二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在[236. 二叉树的最近公共祖先](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中介绍)
* 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?
@ -62,7 +60,7 @@
所以代码如下:
```
bool traversal(TreeNode* cur, int count) // 注意函数的返回类型
bool traversal(treenode* cur, int count) // 注意函数的返回类型
```
@ -91,7 +89,7 @@ if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找
代码如下:
```C++
```cpp
if (cur->left) { // 左 (空节点不遍历)
// 遇到叶子节点返回true则直接返回true
if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑
@ -109,7 +107,7 @@ return false;
为了把回溯的过程体现出来,可以改为如下代码:
```C++
```cpp
if (cur->left) { // 左
count -= cur->left->val; // 递归,处理节点;
if (traversal(cur->left, count)) return true;
@ -126,10 +124,10 @@ return false;
整体代码如下:
```C++
class Solution {
```cpp
class solution {
private:
bool traversal(TreeNode* cur, int count) {
bool traversal(treenode* cur, int count) {
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回
@ -147,8 +145,8 @@ private:
}
public:
bool hasPathSum(TreeNode* root, int sum) {
if (root == NULL) return false;
bool haspathsum(treenode* root, int sum) {
if (root == null) return false;
return traversal(root, sum - root->val);
}
};
@ -156,15 +154,15 @@ public:
以上代码精简之后如下:
```C++
class Solution {
```cpp
class solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (root == NULL) return false;
bool haspathsum(treenode* root, int sum) {
if (root == null) return false;
if (!root->left && !root->right && sum == root->val) {
return true;
}
return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
return haspathsum(root->left, sum - root->val) || haspathsum(root->right, sum - root->val);
}
};
```
@ -172,43 +170,43 @@ public:
**是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,在追求代码精简。** 这一点我已经强调很多次了!
### 迭代
## 迭代
如果使用栈模拟递归的话,那么如果做回溯呢?
**此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。**
C++就我们用pair结构来存放这个栈里的元素。
c++就我们用pair结构来存放这个栈里的元素。
定义为:`pair<TreeNode*, int>` pair<节点指针路径数值>
定义为:`pair<treenode*, int>` pair<节点指针路径数值>
这个为栈里的一个元素。
如下代码是使用栈模拟的前序遍历,如下:(详细注释)
```C++
class Solution {
```cpp
class solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (root == NULL) return false;
bool haspathsum(treenode* root, int sum) {
if (root == null) return false;
// 此时栈里要放的是pair<节点指针路径数值>
stack<pair<TreeNode*, int>> st;
st.push(pair<TreeNode*, int>(root, root->val));
stack<pair<treenode*, int>> st;
st.push(pair<treenode*, int>(root, root->val));
while (!st.empty()) {
pair<TreeNode*, int> node = st.top();
pair<treenode*, int> node = st.top();
st.pop();
// 如果该节点是叶子节点了同时该节点的路径数值等于sum那么就返回true
if (!node.first->left && !node.first->right && sum == node.second) return true;
// 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
if (node.first->right) {
st.push(pair<TreeNode*, int>(node.first->right, node.second + node.first->right->val));
st.push(pair<treenode*, int>(node.first->right, node.second + node.first->right->val));
}
// 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来
if (node.first->left) {
st.push(pair<TreeNode*, int>(node.first->left, node.second + node.first->left->val));
st.push(pair<treenode*, int>(node.first->left, node.second + node.first->left->val));
}
}
return false;
@ -216,11 +214,11 @@ public:
};
```
如果大家完全理解了本地的递归方法之后就可以顺便把leetcode上113. 路径总和II做了。
如果大家完全理解了本地的递归方法之后就可以顺便把leetcode上113. 路径总和ii做了。
## 113. 路径总和II
# 113. 路径总和ii
题目地址https://leetcode-cn.com/problems/path-sum-ii/
[力扣题目链接](https://leetcode-cn.com/problems/path-sum-ii/)
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
@ -230,26 +228,27 @@ public:
给定如下二叉树以及目标和 sum = 22
![113.路径总和II1.png](https://img-blog.csdnimg.cn/20210203160854654.png)
![113.路径总和ii1.png](https://img-blog.csdnimg.cn/20210203160854654.png)
### 思路
## 思路
113.路径总和II要遍历整个树找到所有路径**所以递归函数不要返回值!**
113.路径总和ii要遍历整个树找到所有路径**所以递归函数不要返回值!**
如图:
![113.路径总和II](https://img-blog.csdnimg.cn/20210203160922745.png)
![113.路径总和ii](https://img-blog.csdnimg.cn/20210203160922745.png)
为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰**
```C++
class Solution {
```cpp
class solution {
private:
vector<vector<int>> result;
vector<int> path;
// 递归函数不需要返回值,因为我们要遍历整个树
void traversal(TreeNode* cur, int count) {
void traversal(treenode* cur, int count) {
if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
result.push_back(path);
return;
@ -275,10 +274,10 @@ private:
}
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
vector<vector<int>> pathsum(treenode* root, int sum) {
result.clear();
path.clear();
if (root == NULL) return result;
if (root == null) return result;
path.push_back(root->val); // 把根节点放进路径
traversal(root, sum - root->val);
return result;
@ -286,11 +285,11 @@ public:
};
```
至于113. 路径总和II 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。
至于113. 路径总和ii 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。
## 总结
# 总结
本篇通过leetcode上112. 路径总和 和 113. 路径总和II 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。
本篇通过leetcode上112. 路径总和 和 113. 路径总和ii 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。
这两道题目是掌握这一知识点非常好的题目,大家看完本篇文章再去做题,就会感受到搜索整棵树和搜索某一路径的差别。
@ -299,31 +298,30 @@ public:
# 其他语言版本
## java
## 其他语言版本
Java
```Java
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
lc112
```java
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if (root == null) {
return false;
}
targetSum -= root.val;
targetsum -= root.val;
// 叶子结点
if (root.left == null && root.right == null) {
return targetSum == 0;
return targetsum == 0;
}
if (root.left != null) {
boolean left = hasPathSum(root.left, targetSum);
boolean left = haspathsum(root.left, targetsum);
if (left) {// 已经找到
return true;
}
}
if (root.right != null) {
boolean right = hasPathSum(root.right, targetSum);
boolean right = haspathsum(root.right, targetsum);
if (right) {// 已经找到
return true;
}
@ -332,34 +330,34 @@ class Solution {
}
}
// LC112 简洁方法
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// lc112 简洁方法
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if (root == null) return false; // 为空退出
// 叶子节点判断是否符合
if (root.left == null && root.right == null) return root.val == targetSum;
if (root.left == null && root.right == null) return root.val == targetsum;
// 求两侧分支的路径和
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
}
}
```
迭代
```java
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if(root==null)return false;
Stack<TreeNode> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
stack<treenode> stack1 = new stack<>();
stack<integer> stack2 = new stack<>();
stack1.push(root);stack2.push(root.val);
while(!stack1.isEmpty()){
while(!stack1.isempty()){
int size = stack1.size();
for(int i=0;i<size;i++){
TreeNode node = stack1.pop();int sum=stack2.pop();
treenode node = stack1.pop();int sum=stack2.pop();
// 如果该节点是叶子节点了同时该节点的路径数值等于sum那么就返回true
if(node.left==null && node.right==null && sum==targetSum)return true;
if(node.left==null && node.right==null && sum==targetsum)return true;
// 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
if(node.right!=null){
stack1.push(node.right);stack2.push(sum+node.right.val);
@ -380,209 +378,251 @@ class Solution {
0113.路径总和-ii
```java
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
class solution {
public list<list<integer>> pathsum(treenode root, int targetsum) {
list<list<integer>> res = new arraylist<>();
if (root == null) return res; // 非空判断
List<Integer> path = new LinkedList<>();
preorderDFS(root, targetSum, res, path);
list<integer> path = new linkedlist<>();
preorderdfs(root, targetsum, res, path);
return res;
}
public void preorderDFS(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path) {
public void preorderdfs(treenode root, int targetsum, list<list<integer>> res, list<integer> path) {
path.add(root.val);
// 遇到了叶子节点
if (root.left == null && root.right == null) {
// 找到了和为 targetSum 的路径
if (targetSum - root.val == 0) {
res.add(new ArrayList<>(path));
// 找到了和为 targetsum 的路径
if (targetsum - root.val == 0) {
res.add(new arraylist<>(path));
}
return; // 如果和不为 targetSum返回
return; // 如果和不为 targetsum返回
}
if (root.left != null) {
preorderDFS(root.left, targetSum - root.val, res, path);
preorderdfs(root.left, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preorderDFS(root.right, targetSum - root.val, res, path);
preorderdfs(root.right, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}
```
Python
```java
// 解法2
class Solution {
List<List<Integer>> result;
LinkedList<Integer> path;
public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
result = new LinkedList<>();
path = new LinkedList<>();
travesal(root, targetSum);
return result;
}
private void travesal(TreeNode root, int count) {
if (root == null) return;
path.offer(root.val);
count -= root.val;
if (root.left == null && root.right == null && count == 0) {
result.add(new LinkedList<>(path));
}
travesal(root.left, count);
travesal(root.right, count);
path.removeLast(); // 回溯
}
}
```
## python
0112.路径总和
**递归**
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
// 递归法
class Solution:
def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
def isornot(root,targetSum)->bool:
if (not root.left) and (not root.right) and targetSum == 0:return True // 遇到叶子节点并且计数为0
if (not root.left) and (not root.right):return False //遇到叶子节点计数不为0
class solution:
def haspathsum(self, root: treenode, targetsum: int) -> bool:
def isornot(root, targetsum) -> bool:
if (not root.left) and (not root.right) and targetsum == 0:
return true # 遇到叶子节点并且计数为0
if (not root.left) and (not root.right):
return false # 遇到叶子节点计数不为0
if root.left:
targetSum -= root.left.val //左节点
if isornot(root.left,targetSum):return True //递归,处理左节点
targetSum += root.left.val //回溯
targetsum -= root.left.val # 左节点
if isornot(root.left, targetsum): return true # 递归,处理左节点
targetsum += root.left.val # 回溯
if root.right:
targetSum -= root.right.val //右节点
if isornot(root.right,targetSum):return True //递归,处理右节点
targetSum += root.right.val //回溯
return False
if root == None:return False //别忘记处理空TreeNode
else:return isornot(root,targetSum-root.val)
targetsum -= root.right.val # 右节点
if isornot(root.right, targetsum): return true # 递归,处理右节点
targetsum += root.right.val # 回溯
return false
if root == none:
return false # 别忘记处理空treenode
else:
return isornot(root, targetsum - root.val)
```
**迭代 - 层序遍历**
```python
class solution:
def haspathsum(self, root: treenode, targetsum: int) -> bool:
if not root:
return false
stack = [] # [(当前节点,路径数值), ...]
stack.append((root, root.val))
while stack:
cur_node, path_sum = stack.pop()
if not cur_node.left and not cur_node.right and path_sum == targetsum:
return true
if cur_node.right:
stack.append((cur_node.right, path_sum + cur_node.right.val))
if cur_node.left:
stack.append((cur_node.left, path_sum + cur_node.left.val))
return false
```
0113.路径总和-ii
**递归**
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
//递归法
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]:
path=[]
res=[]
def pathes(root,targetSum):
if (not root.left) and (not root.right) and targetSum == 0: // 遇到叶子节点并且计数为0
res.append(path[:]) //找到一种路径记录到res中注意必须是path[:]而不是path
return
if (not root.left) and (not root.right):return // 遇到叶子节点直接返回
if root.left: //左
targetSum -= root.left.val
path.append(root.left.val) //递归前记录节点
pathes(root.left,targetSum) //递归
targetSum += root.left.val //回溯
path.pop() //回溯
if root.right: //右
targetSum -= root.right.val
path.append(root.right.val) //递归前记录节点
pathes(root.right,targetSum) //递归
targetSum += root.right.val //回溯
path.pop() //回溯
return
if root == None:return [] //处理空TreeNode
else:
path.append(root.val) //首先处理根节点
pathes(root,targetSum-root.val)
return res
class solution:
def pathsum(self, root: treenode, targetsum: int) -> list[list[int]]:
def traversal(cur_node, remain):
if not cur_node.left and not cur_node.right and remain == 0:
result.append(path[:])
return
if not cur_node.left and not cur_node.right: return
if cur_node.left:
path.append(cur_node.left.val)
remain -= cur_node.left.val
traversal(cur_node.left, remain)
path.pop()
remain += cur_node.left.val
if cur_node.right:
path.append(cur_node.right.val)
remain -= cur_node.right.val
traversal(cur_node.right, remain)
path.pop()
remain += cur_node.right.val
result, path = [], []
if not root:
return []
path.append(root.val)
traversal(root, targetsum - root.val)
return result
```
Go
## go
> 112. 路径总和
112. 路径总和
```go
//递归法
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* definition for a binary tree node.
* type treenode struct {
* val int
* left *treenode
* right *treenode
* }
*/
func hasPathSum(root *TreeNode, targetSum int) bool {
func haspathsum(root *treenode, targetsum int) bool {
var flage bool //找没找到的标志
if root==nil{
return flage
}
pathSum(root,0,targetSum,&flage)
pathsum(root,0,targetsum,&flage)
return flage
}
func pathSum(root *TreeNode, sum int,targetSum int,flage *bool){
sum+=root.Val
if root.Left==nil&&root.Right==nil&&sum==targetSum{
func pathsum(root *treenode, sum int,targetsum int,flage *bool){
sum+=root.val
if root.left==nil&&root.right==nil&&sum==targetsum{
*flage=true
return
}
if root.Left!=nil&&!(*flage){//左节点不为空且还没找到
pathSum(root.Left,sum,targetSum,flage)
if root.left!=nil&&!(*flage){//左节点不为空且还没找到
pathsum(root.left,sum,targetsum,flage)
}
if root.Right!=nil&&!(*flage){//右节点不为空且没找到
pathSum(root.Right,sum,targetSum,flage)
if root.right!=nil&&!(*flage){//右节点不为空且没找到
pathsum(root.right,sum,targetsum,flage)
}
}
```
> 113 递归法
113 递归法
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* definition for a binary tree node.
* type treenode struct {
* val int
* left *treenode
* right *treenode
* }
*/
func pathSum(root *TreeNode, targetSum int) [][]int {
func pathsum(root *treenode, targetsum int) [][]int {
var result [][]int//最终结果
if root==nil{
return result
}
var sumNodes []int//经过路径的节点集合
hasPathSum(root,&sumNodes,targetSum,&result)
var sumnodes []int//经过路径的节点集合
haspathsum(root,&sumnodes,targetsum,&result)
return result
}
func hasPathSum(root *TreeNode,sumNodes *[]int,targetSum int,result *[][]int){
*sumNodes=append(*sumNodes,root.Val)
if root.Left==nil&&root.Right==nil{//叶子节点
fmt.Println(*sumNodes)
func haspathsum(root *treenode,sumnodes *[]int,targetsum int,result *[][]int){
*sumnodes=append(*sumnodes,root.val)
if root.left==nil&&root.right==nil{//叶子节点
fmt.println(*sumnodes)
var sum int
var number int
for k,v:=range *sumNodes{//求该路径节点的和
for k,v:=range *sumnodes{//求该路径节点的和
sum+=v
number=k
}
tempNodes:=make([]int,number+1)//新的nodes接受指针里的值防止最终指针里的值发生变动导致最后的结果都是最后一个sumNodes的值
for k,v:=range *sumNodes{
tempNodes[k]=v
tempnodes:=make([]int,number+1)//新的nodes接受指针里的值防止最终指针里的值发生变动导致最后的结果都是最后一个sumnodes的值
for k,v:=range *sumnodes{
tempnodes[k]=v
}
if sum==targetSum{
*result=append(*result,tempNodes)
if sum==targetsum{
*result=append(*result,tempnodes)
}
}
if root.Left!=nil{
hasPathSum(root.Left,sumNodes,targetSum,result)
*sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯
if root.left!=nil{
haspathsum(root.left,sumnodes,targetsum,result)
*sumnodes=(*sumnodes)[:len(*sumnodes)-1]//回溯
}
if root.Right!=nil{
hasPathSum(root.Right,sumNodes,targetSum,result)
*sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯
if root.right!=nil{
haspathsum(root.right,sumnodes,targetsum,result)
*sumnodes=(*sumnodes)[:len(*sumnodes)-1]//回溯
}
}
```
JavaScript
## javascript
0112.路径总和
```javascript
/**
* @param {TreeNode} root
* @param {number} targetSum
* @param {treenode} root
* @param {number} targetsum
* @return {boolean}
*/
let hasPathSum = function (root, targetSum) {
let haspathsum = function (root, targetsum) {
// 递归法
const traversal = (node, cnt) => {
// 遇到叶子节点并且计数为0
@ -597,19 +637,19 @@ let hasPathSum = function (root, targetSum) {
return false;
};
if (!root) return false;
return traversal(root, targetSum - root.val);
return traversal(root, targetsum - root.val);
// 精简代码:
// if (!root) return false;
// if (!root.left && !root.right && targetSum === root.val) return true;
// return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
// if (!root.left && !root.right && targetsum === root.val) return true;
// return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
};
```
0113.路径总和-ii
```javascript
let pathSum = function (root, targetSum) {
let pathsum = function (root, targetsum) {
// 递归法
// 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同
const res = [];
@ -635,62 +675,29 @@ let pathSum = function (root, targetSum) {
return;
};
if (!root) return res;
travelsal(root, targetSum - root.val, [root.val]); // 把根节点放进路径
travelsal(root, targetsum - root.val, [root.val]); // 把根节点放进路径
return res;
};
```
0112 路径总和
113 路径总和 精简版
```javascript
var hasPathSum = function(root, targetSum) {
var pathsum = function(root, targetsum) {
//递归方法
// 1. 确定函数参数
const traversal = function(node,count){
// 2. 确定终止条件
if(node.left===null&&node.right===null&&count===0){
return true;
}
if(node.left===null&&node.right===null){
return false;
}
//3. 单层递归逻辑
if(node.left){
if(traversal(node.left,count-node.left.val)){
return true;
}
}
if(node.right){
if(traversal(node.right,count-node.right.val)){
return true;
}
}
return false;
}
if(root===null){
return false;
}
return traversal(root,targetSum-root.val);
};
```
113 路径总和
```javascript
var pathSum = function(root, targetSum) {
//递归方法
let resPath = [],curPath = [];
let respath = [],curpath = [];
// 1. 确定递归函数参数
const travelTree = function(node,count){
curPath.push(node.val);
const traveltree = function(node,count){
curpath.push(node.val);
count-=node.val;
if(node.left===null&&node.right===null&&count===0){
resPath.push([...curPath]);
respath.push([...curpath]);
}
node.left&&travelTree(node.left,count);
node.right&&travelTree(node.right,count);
let cur = curPath.pop();
node.left&&traveltree(node.left,count);
node.right&&traveltree(node.right,count);
let cur = curpath.pop();
count-=cur;
}
if(root===null){
return resPath;
return respath;
}
travelTree(root,targetSum);
return resPath;
@ -699,8 +706,9 @@ var pathSum = function(root, targetSum) {
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 115.不同的子序列
题目链接:https://leetcode-cn.com/problems/distinct-subsequences/
[力扣题目链接](https://leetcode-cn.com/problems/distinct-subsequences/)
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
@ -29,7 +29,7 @@ s 和 t 由英文字母组成
这道题目相对于72. 编辑距离,简单了不少,因为本题相当于只有删除操作,不用考虑替换增加之类的。
但相对于刚讲过的[动态规划392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下:
但相对于刚讲过的[动态规划392.判断子序列](https://programmercarl.com/0392.判断子序列.html)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下:
1. 确定dp数组dp table以及下标的含义
@ -82,7 +82,7 @@ dp[0][0]应该是1空字符串s可以删除0个元素变成空字符串
初始化分析完毕,代码如下:
```C++
```CPP
vector<vector<long long>> dp(s.size() + 1, vector<long long>(t.size() + 1));
for (int i = 0; i <= s.size(); i++) dp[i][0] = 1;
for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 其实这行代码可以和dp数组初始化的时候放在一起但我为了凸显初始化的逻辑所以还是加上了。
@ -97,7 +97,7 @@ for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 其实这行代码可以和
代码如下:
```C++
```CPP
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) {
@ -120,7 +120,7 @@ for (int i = 1; i <= s.size(); i++) {
动规五部曲分析完毕,代码如下:
```C++
```CPP
class Solution {
public:
int numDistinct(string s, string t) {
@ -221,6 +221,30 @@ class SolutionDP2:
```
Go
```go
func numDistinct(s string, t string) int {
dp:= make([][]int,len(s)+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,len(t)+1)
}
// 初始化
for i:=0;i<len(dp);i++{
dp[i][0] = 1
}
// dp[0][j] 为 0默认值因此不需要初始化
for i:=1;i<len(dp);i++{
for j:=1;j<len(dp[i]);j++{
if s[i-1] == t[j-1]{
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
}else{
dp[i][j] = dp[i-1][j]
}
}
}
return dp[len(dp)-1][len(dp[0])-1]
}
```
Javascript:
```javascript
@ -250,4 +274,4 @@ const numDistinct = (s, t) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 116. 填充每个节点的下一个右侧节点指针
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/)
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
@ -52,7 +52,7 @@ struct Node {
图中cur节点为元素4那么搭线的逻辑代码**注意注释中操作1和操作2和图中的对应关系**
```C++
```CPP
if (cur->left) cur->left->next = cur->right; // 操作1
if (cur->right) {
if (cur->next) cur->right->next = cur->next->left; // 操作2
@ -63,7 +63,7 @@ if (cur->right) {
理解到这里,使用前序遍历,那么不难写出如下代码:
```C++
```CPP
class Solution {
private:
void traversal(Node* cur) {
@ -87,13 +87,13 @@ public:
## 迭代(层序遍历)
本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)。
本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)。
层序遍历本来就是一层一层的去遍历记录一层的头结点nodePre然后让nodePre指向当前遍历的节点就可以了。
代码如下:
```C++
```CPP
class Solution {
public:
Node* connect(Node* root) {
@ -130,26 +130,134 @@ public:
## Java
```java
// 递归法
class Solution {
public void traversal(Node cur) {
if (cur == null) return;
if (cur.left != null) cur.left.next = cur.right; // 操作1
if (cur.right != null) {
if(cur.next != null) cur.right.next = cur.next.left; //操作2
else cur.right.next = null;
}
traversal(cur.left); // 左
traversal(cur.right); //右
}
public Node connect(Node root) {
traversal(root);
return root;
}
}
```
```java
// 迭代法
class Solution {
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> que = new LinkedList<Node>();
que.offer(root);
Node nodePre = null;
Node node = null;
while (!que.isEmpty()) {
int size = que.size();
for (int i=0; i<size; i++) { // 开始每一层的遍历
if (i == 0) {
nodePre = que.peek(); // 记录一层的头结点
que.poll();
node = nodePre;
} else {
node = que.peek();
que.poll();
nodePre.next = node; // 本层前一个节点next指向本节点
nodePre = nodePre.next;
}
if (node.left != null) que.offer(node.left);
if (node.right != null) que.offer(node.right);
}
nodePre.next = null; // 本层最后一个节点指向null
}
return root;
}
}
```
## Python
```python
# 递归法
class Solution:
def connect(self, root: 'Node') -> 'Node':
def traversal(cur: 'Node') -> 'Node':
if not cur: return []
if cur.left: cur.left.next = cur.right # 操作1
if cur.right:
if cur.next:
cur.right.next = cur.next.left # 操作2
else:
cur.right.next = None
traversal(cur.left) # 左
traversal(cur.right) # 右
traversal(root)
return root
```
```python
# 迭代法
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root: return
res = []
queue = [root]
while queue:
size = len(queue)
for i in range(size): # 开始每一层的遍历
if i==0:
nodePre = queue.pop(0) # 记录一层的头结点
node = nodePre
else:
node = queue.pop(0)
nodePre.next = node # 本层前一个节点next指向本节点
nodePre = nodePre.next
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
nodePre.next = None # 本层最后一个节点指向None
return root
```
## Go
```go
```
## JavaScript
```js
const connect = root => {
if (!root) return root;
// 根节点入队
const Q = [root];
while (Q.length) {
const len = Q.length;
// 遍历这一层的所有节点
for (let i = 0; i < len; i++) {
// 队头出队
const node = Q.shift();
// 连接
if (i < len - 1) {
// 新的队头是node的右边元素
node.next = Q[0];
}
// 队头左节点有值,放入队列
node.left && Q.push(node.left);
// 队头右节点有值,放入队列
node.right && Q.push(node.right);
}
}
return root;
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 121. 买卖股票的最佳时机
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)
给定一个数组 prices 它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
@ -33,7 +33,7 @@
这道题目最直观的想法,就是暴力,找最优间距了。
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -59,7 +59,7 @@ public:
C++代码如下:
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -139,7 +139,7 @@ dp[5][1]就是最终结果。
以上分析完毕C++代码如下:
```C++
```CPP
// 版本一
class Solution {
public:
@ -169,7 +169,7 @@ dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了可以使用滚动数组来节省空间代码如下
```C++
```CPP
// 版本二
class Solution {
public:
@ -214,6 +214,26 @@ class Solution {
}
}
```
```java
// 解法1
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int length = prices.length;
// dp[i][0]代表第i天持有股票的最大收益
// dp[i][1]代表第i天不持有股票的最大收益
int[][] dp = new int[length][2];
int result = 0;
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
}
return dp[length - 1][1];
}
}
```
``` java
class Solution { // 动态规划解法
@ -313,6 +333,26 @@ func max(a,b int)int {
}
```
JavaScript
```javascript
const maxProfit = prices => {
const len = prices.length;
// 创建dp数组
const dp = new Array(len).fill([0, 0]);
// dp数组初始化
dp[0] = [-prices[0], 0];
for (let i = 1; i < len; i++) {
// 更新dp[i]
dp[i] = [
Math.max(dp[i - 1][0], -prices[i]),
Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]),
];
}
return dp[len - 1][1];
};
```
@ -320,4 +360,4 @@ func max(a,b int)int {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 122.买卖股票的最佳时机II
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
给定一个数组它的第 i 个元素是一支给定股票第 i 天的价格。
@ -80,7 +80,7 @@
对应C++代码如下:
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -99,7 +99,7 @@ public:
动态规划将在下一个系列详细讲解本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -139,17 +139,11 @@ Java
// 贪心思路
class Solution {
public int maxProfit(int[] prices) {
int sum = 0;
int profit = 0;
int buy = prices[0];
int result = 0;
for (int i = 1; i < prices.length; i++) {
profit = prices[i] - buy;
if (profit > 0) {
sum += profit;
}
buy = prices[i];
result += Math.max(prices[i] - prices[i - 1], 0);
}
return sum;
return result;
}
}
```
@ -235,8 +229,23 @@ var maxProfit = function(prices) {
};
```
C:
```c
int maxProfit(int* prices, int pricesSize){
int result = 0;
int i;
//从第二个元素开始遍历数组,与之前的元素进行比较
for(i = 1; i < pricesSize; ++i) {
//若该元素比前面元素大,则说明有利润。代表买入
if(prices[i] > prices[i-1])
result+= prices[i]-prices[i-1];
}
return result;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 122.买卖股票的最佳时机II
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
给定一个数组它的第 i 个元素是一支给定股票第 i 天的价格。
@ -38,12 +38,12 @@
## 思路
本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。
本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。
本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的唯一区别本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)
本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的唯一区别本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)
**在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)一样一样的**。
**在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)一样一样的**。
所以我们重点讲一讲递推公式。
@ -57,10 +57,9 @@
* 第i-1天就持有股票那么就保持现状所得现金就是昨天持有股票的所得现金 即dp[i - 1][0]
* 第i天买入股票所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即dp[i - 1][1] - prices[i]
**注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)唯一不同的地方就是推导dp[i][0]的时候第i天买入股票的情况**。
**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)唯一不同的地方就是推导dp[i][0]的时候第i天买入股票的情况**。
在[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)中因为股票全程只能买卖一次所以如果买入股票那么第i天持有股票即dp[i][0]一定就是 -prices[i]。
在[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)中因为股票全程只能买卖一次所以如果买入股票那么第i天持有股票即dp[i][0]一定就是 -prices[i]。
而本题因为一只股票可以买卖多次所以当第i天买入股票的时候所持有的现金可能有之前买卖过的利润。
@ -70,11 +69,11 @@
* 第i-1天就不持有股票那么就保持现状所得现金就是昨天不持有股票的所得现金 即dp[i - 1][1]
* 第i天卖出股票所得现金就是按照今天股票佳价格卖出后所得现金即prices[i] + dp[i - 1][0]
**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!**
**注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!**
代码如下注意代码中的注释标记了和121.买卖股票的最佳时机唯一不同的地方)
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -94,7 +93,7 @@ public:
* 时间复杂度O(n)
* 空间复杂度O(n)
大家可以本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的代码几乎一样,唯一的区别在:
大家可以本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在:
```
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
@ -106,7 +105,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
这里我依然给出滚动数组的版本C++代码如下:
```C++
```CPP
// 版本二
class Solution {
public:
@ -122,7 +121,7 @@ public:
return dp[(len - 1) % 2][1];
}
};
```
```
* 时间复杂度O(n)
* 空间复杂度O(1)
@ -231,4 +230,4 @@ const maxProfit = (prices) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 123.买卖股票的最佳时机III
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
@ -44,7 +44,7 @@
## 思路
这道题目相对 [121.买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) 和 [122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 难了不少。
这道题目相对 [121.买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) 和 [122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html) 难了不少。
关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。
@ -127,7 +127,7 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
以上五部都分析完了,不难写出如下代码:
```C++
```CPP
// 版本一
class Solution {
public:
@ -153,7 +153,7 @@ public:
当然大家可以看到力扣官方题解里的一种优化空间写法我这里给出对应的C++版本:
```C++
```CPP
// 版本二
class Solution {
public:
@ -278,7 +278,44 @@ class Solution:
return dp[4]
```
Go
JavaScript
> 版本一:
```javascript
const maxProfit = prices => {
const len = prices.length;
const dp = new Array(len).fill(0).map(x => new Array(5).fill(0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (let i = 1; i < len; i++) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[len - 1][4];
};
```
> 版本二:
```javascript
const maxProfit = prices => {
const len = prices.length;
const dp = new Array(5).fill(0);
dp[1] = -prices[0];
dp[3] = -prices[0];
for (let i = 1; i < len; i++) {
dp[1] = Math.max(dp[1], dp[0] - prices[i]);
dp[2] = Math.max(dp[2], dp[1] + prices[i]);
dp[3] = Math.max(dp[3], dp[2] - prices[i]);
dp[4] = Math.max(dp[4], dp[3] + prices[i]);
}
return dp[4];
};
```
@ -287,4 +324,4 @@ Go
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -0,0 +1,150 @@
<p align="center">
<a href="https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ"><img src="https://img.shields.io/badge/PDF下载-代码随想录-blueviolet" alt=""></a>
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
</p>
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 127. 单词接龙
[力扣题目链接](https://leetcode-cn.com/problems/word-ladder/)
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
* 序列中第一个单词是 beginWord 。
* 序列中最后一个单词是 endWord 。
* 每次转换只能改变一个字母。
* 转换过程中的中间单词必须是字典 wordList 中的单词。
* 给你两个单词 beginWord 和 endWord 和一个字典 wordList 找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
 
示例 1
* 输入beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
* 输出5
* 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2
* 输入beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
* 输出0
* 解释endWord "cog" 不在字典中,所以无法进行转换。
# 思路
以示例1为例从这个图中可以看出 hit 到 cog的路线不止一条有三条两条是最短的长度为5一条长度为6。
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210827175432.png)
本题只需要求出最短长度就可以了,不用找出路径。
所以这道题要解决两个问题:
* 图中的线是如何连在一起的
* 起点和终点的最短路径长度
首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。
然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
本题如果用深搜,会非常麻烦。
另外需要有一个注意点:
* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
* 本题给出集合是数组型的可以转成set结构查找更快一些
C++代码如下:(详细注释)
```CPP
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
// 将vector转成unordered_set提高查询速度
unordered_set<string> wordSet(wordList.begin(), wordList.end());
// 如果endWord没有在wordSet出现直接返回0
if (wordSet.find(endWord) == wordSet.end()) return 0;
// 记录word是否访问过
unordered_map<string, int> visitMap; // <word, 查询到这个word路径长度>
// 初始化队列
queue<string> que;
que.push(beginWord);
// 初始化visitMap
visitMap.insert(pair<string, int>(beginWord, 1));
while(!que.empty()) {
string word = que.front();
que.pop();
int path = visitMap[word]; // 这个word的路径长度
for (int i = 0; i < word.size(); i++) {
string newWord = word; // 用一个新单词替换word因为每次置换一个字母
for (int j = 0 ; j < 26; j++) {
newWord[i] = j + 'a';
if (newWord == endWord) return path + 1; // 找到了end返回path+1
// wordSet出现了newWord并且newWord没有被访问过
if (wordSet.find(newWord) != wordSet.end()
&& visitMap.find(newWord) == visitMap.end()) {
// 添加访问信息
visitMap.insert(pair<string, int>(newWord, path + 1));
que.push(newWord);
}
}
}
}
return 0;
}
};
```
# 其他语言版本
## Java
```java
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
HashSet<String> wordSet = new HashSet<>(wordList); //转换为hashset 加快速度
if (wordSet.size() == 0 || !wordSet.contains(endWord)) { //特殊情况判断
return 0;
}
Queue<String> queue = new LinkedList<>(); //bfs 队列
queue.offer(beginWord);
Map<String, Integer> map = new HashMap<>(); //记录单词对应路径长度
map.put(beginWord, 1);
while (!queue.isEmpty()) {
String word = queue.poll(); //取出队头单词
int path = map.get(word); //获取到该单词的路径长度
for (int i = 0; i < word.length(); i++) { //遍历单词的每个字符
char[] chars = word.toCharArray(); //将单词转换为char array方便替换
for (char k = 'a'; k <= 'z'; k++) { //从'a' 到 'z' 遍历替换
chars[i] = k; //替换第i个字符
String newWord = String.valueOf(chars); //得到新的字符串
if (newWord.equals(endWord)) { //如果新的字符串值与endWord一致返回当前长度+1
return path + 1;
}
if (wordSet.contains(newWord) && !map.containsKey(newWord)) { //如果新单词在set中但是没有访问过
map.put(newWord, path + 1); //记录单词对应的路径长度
queue.offer(newWord);//加入队尾
}
}
}
}
return 0; //未找到
}
```
## Python
## Go
## JavaScript
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -1,13 +1,13 @@
## 链接
https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
[力扣题目链接](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/)
## 思路
本题和[113.路径总和II](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)是类似的思路,做完这道题,可以顺便把[113.路径总和II](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 和 [112.路径总和](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 做了。
本题和[113.路径总和II](https://programmercarl.com/0112.路径总和.html#_113-路径总和ii)是类似的思路,做完这道题,可以顺便把[113.路径总和II](https://programmercarl.com/0112.路径总和.html#_113-路径总和ii) 和 [112.路径总和](https://programmercarl.com/0112.路径总和.html#_112-路径总和) 做了。
结合112.路径总和 和 113.路径总和II我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。
结合112.路径总和 和 113.路径总和II我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。
接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,我详细讲解了二叉树的递归中,如何使用了回溯。
接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)中,我详细讲解了二叉树的递归中,如何使用了回溯。
接下来我们来看题:
@ -17,11 +17,11 @@ https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
### 递归三部曲
如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://programmercarl.com/二叉树的递归遍历.html)
* 确定递归函数返回值及其参数
这里我们要遍历整个二叉树且需要要返回值做逻辑处理所有返回值为void在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中,详细讲解了返回值问题。
这里我们要遍历整个二叉树且需要要返回值做逻辑处理所有返回值为void在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中,详细讲解了返回值问题。
参数只需要把根节点传入此时还需要定义两个全局遍历一个是result记录最终结果一个是vector<int> path。
@ -52,7 +52,7 @@ if (!cur->left && !cur->right) { // 遇到了叶子节点
这里vectorToInt函数就是把数组转成int代码如下
```C++
```CPP
int vectorToInt(const vector<int>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
@ -78,7 +78,7 @@ int vectorToInt(const vector<int>& vec) {
代码如下:
```C++
```CPP
// 中
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
@ -94,7 +94,7 @@ if (cur->right) { // 右 (空节点不遍历)
这里要注意回溯和递归要永远在一起,一个递归,对应一个回溯,是一对一的关系,有的同学写成如下代码:
```C++
```CPP
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
traversal(cur->left); // 递归
@ -111,7 +111,7 @@ path.pop_back(); // 回溯
关键逻辑分析完了整体C++代码如下:
```C++
```CPP
class Solution {
private:
int result;
@ -164,8 +164,79 @@ public:
Java
Python
```java
class Solution {
List<Integer> path = new ArrayList<>();
int res = 0;
public int sumNumbers(TreeNode root) {
// 如果节点为0那么就返回0
if (root == null) return 0;
// 首先将根节点放到集合中
path.add(root.val);
// 开始递归
recur(root);
return res;
}
public void recur(TreeNode root){
if (root.left == null && root.right == null) {
// 当是叶子节点的时候,开始处理
res += listToInt(path);
return;
}
if (root.left != null){
// 注意有回溯
path.add(root.left.val);
recur(root.left);
path.remove(path.size() - 1);
}
if (root.right != null){
// 注意有回溯
path.add(root.right.val);
recur(root.right);
path.remove(path.size() - 1);
}
return;
}
public int listToInt(List<Integer> path){
int sum = 0;
for (Integer num:path){
// sum * 10 表示进位
sum = sum * 10 + num;
}
return sum;
}
}
```
Python
```python3
class Solution:
def sumNumbers(self, root: TreeNode) -> int:
res = 0
path = []
def backtrace(root):
nonlocal res
if not root: return # 节点空则返回
path.append(root.val)
if not root.left and not root.right: # 遇到了叶子节点
res += get_sum(path)
if root.left: # 左子树不空
backtrace(root.left)
if root.right: # 右子树不空
backtrace(root.right)
path.pop()
def get_sum(arr):
s = 0
for i in range(len(arr)):
s = s * 10 + arr[i]
return s
backtrace(root)
return res
```
Go
JavaScript
@ -176,4 +247,4 @@ JavaScript
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 131.分割回文串
题目链接:https://leetcode-cn.com/problems/palindrome-partitioning/
[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning/)
给定一个字符串 s将 s 分割成一些子串,使每个子串都是回文串。
@ -66,11 +66,11 @@
本题递归函数参数还需要startIndex因为切割过的地方不能重复切割和组合问题也是保持一致的。
在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中我们深入探讨了组合问题什么时候需要startIndex什么时候不需要startIndex。
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中我们深入探讨了组合问题什么时候需要startIndex什么时候不需要startIndex。
代码如下:
```C++
```CPP
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
@ -88,7 +88,7 @@ void backtracking (const string& s, int startIndex) {
所以终止条件代码如下:
```C++
```CPP
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
@ -108,7 +108,7 @@ void backtracking (const string& s, int startIndex) {
代码如下:
```C++
```CPP
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
@ -132,7 +132,7 @@ for (int i = startIndex; i < s.size(); i++) {
那么判断回文的C++代码如下:
```C++
```CPP
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
@ -143,7 +143,7 @@ for (int i = startIndex; i < s.size(); i++) {
}
```
如果大家对双指针法有生疏了,传送门:[双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)
如果大家对双指针法有生疏了,传送门:[双指针法:总结篇!](https://programmercarl.com/双指针总结.html)
此时关键代码已经讲解完毕,整体代码如下(详细注释了)
@ -151,7 +151,7 @@ for (int i = startIndex; i < s.size(); i++) {
根据Carl给出的回溯算法模板
```C++
```CPP
void backtracking(参数) {
if (终止条件) {
存放结果;
@ -169,7 +169,7 @@ void backtracking(参数) {
不难写出如下代码:
```C++
```CPP
class Solution {
private:
vector<vector<string>> result;
@ -292,7 +292,8 @@ class Solution {
```
Python
```py
```python
# 版本一
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
@ -310,7 +311,36 @@ class Solution:
return res
```
```python
# 版本二
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
path = [] #放已经回文的子串
# 双指针法判断是否是回文串
def isPalindrome(s):
n = len(s)
i, j = 0, n - 1
while i < j:
if s[i] != s[j]:return False
i += 1
j -= 1
return True
def backtrack(s, startIndex):
if startIndex >= len(s): # 如果起始位置已经大于s的大小说明已经找到了一组分割方案了
res.append(path[:])
return
for i in range(startIndex, len(s)):
p = s[startIndex:i+1] # 获取[startIndex,i+1]在s中的子串
if isPalindrome(p): # 是回文子串
path.append(p)
else: continue #不是回文,跳过
backtrack(s, i + 1)
path.pop() #回溯过程弹出本次已经填在path的子串
backtrack(s, 0)
return res
```
Go
> 注意切片go切片是披着值类型外衣的引用类型
@ -395,4 +425,4 @@ var partition = function(s) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
# 132. 分割回文串 II
链接:https://leetcode-cn.com/problems/palindrome-partitioning-ii/
[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning-ii/)
给你一个字符串 s请你将 s 分割成一些子串,使每个子串都是回文。
@ -29,7 +29,7 @@
示例 3
输入s = "ab"
输出1
 
提示:
@ -38,7 +38,7 @@
# 思路
我们在讲解回溯法系列的时候,讲过了这道题目[回溯算法131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)。
我们在讲解回溯法系列的时候,讲过了这道题目[回溯算法131.分割回文串](https://programmercarl.com/0131.分割回文串.html)。
本题呢其实也可以使用回溯法,只不过会超时!(通过记忆化回溯,也可以过,感兴趣的同学可以自行研究一下)
@ -46,7 +46,7 @@
关于回文子串,两道题目题目大家是一定要掌握的。
* [动态规划647. 回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw)
* [动态规划647. 回文子串](https://programmercarl.com/0647.回文子串.html)
* 5.最长回文子串 和 647.回文子串基本一样的
这两道题目是回文子串的基础题目,本题也要用到相关的知识点。
@ -101,7 +101,7 @@ dp[i] 范围是[0, i]的回文子串最少分割次数是dp[i]。
代码如下:
```C++
```CPP
vector<int> dp(s.size(), INT_MAX);
dp[0] = 0;
```
@ -109,7 +109,7 @@ dp[0] = 0;
其实也可以这样初始化更具dp[i]的定义dp[i]的最大值其实就是i也就是把每个字符分割出来。
所以初始化代码也可以为:
```C++
```CPP
vector<int> dp(s.size());
for (int i = 0; i < s.size(); i++) dp[i] = i;
```
@ -122,7 +122,7 @@ j是在[0i]之间所以遍历i的for循环一定在外层这里遍历j
代码如下:
```C++
```CPP
for (int i = 1; i < s.size(); i++) {
if (isPalindromic[0][i]) { // 判断是不是回文子串
dp[i] = 0;
@ -149,7 +149,7 @@ for (int i = 1; i < s.size(); i++) {
代码如下:
```C++
```CPP
vector<vector<bool>> isPalindromic(s.size(), vector<bool>(s.size(), false));
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j < s.size(); j++) {
@ -168,7 +168,7 @@ for (int i = s.size() - 1; i >= 0; i--) {
以上分析完毕,代码如下:
```C++
```CPP
class Solution {
public:
int minCut(string s) {
@ -252,5 +252,5 @@ class Solution:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 134. 加油站
题目链接https://leetcode-cn.com/problems/gas-station/
[力扣题目链接](https://leetcode-cn.com/problems/gas-station/)
在一条环路上有 N 个加油站其中第 i 个加油站有汽油 gas[i] 升。
@ -65,7 +65,7 @@ cost = [3,4,3]
C++代码如下:
```C++
```CPP
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
@ -99,7 +99,7 @@ C++暴力解法在leetcode上提交也可以过。
C++代码如下:
```C++
```CPP
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
@ -160,7 +160,7 @@ i从0开始累加rest[i]和记为curSum一旦curSum小于零说明[0, i
C++代码如下:
```C++
```CPP
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
@ -200,6 +200,7 @@ public:
Java
```java
// 解法1
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
@ -221,7 +222,26 @@ class Solution {
}
}
```
```java
// 解法2
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int totalSum = 0;
int index = 0;
for (int i = 0; i < gas.length; i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) {
index = (i + 1) % gas.length ;
curSum = 0;
}
}
if (totalSum < 0) return -1;
return index;
}
}
```
Python
```python
class Solution:
@ -261,6 +281,48 @@ func canCompleteCircuit(gas []int, cost []int) int {
```
Javascript:
暴力:
```js
var canCompleteCircuit = function(gas, cost) {
for(let i = 0; i < cost.length; i++) {
let rest = gas[i] - cost[i] //记录剩余油量
// 以i为起点行驶一圈index为下一个目的地
let index = (i + 1) % cost.length
while(rest > 0 && index !== i) {
rest += gas[index] - cost[index]
index = (index + 1) % cost.length
}
if(rest >= 0 && index === i) return i
}
return -1
};
```
解法一:
```js
var canCompleteCircuit = function(gas, cost) {
let curSum = 0
let min = Infinity
for(let i = 0; i < gas.length; i++) {
let rest = gas[i] - cost[i]
curSum += rest
if(curSum < min) {
min = curSum
}
}
if(curSum < 0) return -1 //1.总油量 小于 总消耗量
if(min >= 0) return 0 //2. 说明油箱里油没断过
//3. 从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点
for(let i = gas.length -1; i >= 0; i--) {
let rest = gas[i] - cost[i]
min += rest
if(min >= 0) {
return i
}
}
return -1
}
```
解法二:
```Javascript
var canCompleteCircuit = function(gas, cost) {
const gasLen = gas.length
@ -283,9 +345,38 @@ var canCompleteCircuit = function(gas, cost) {
};
```
C:
```c
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
int curSum = 0;
int i;
int min = INT_MAX;
//遍历整个数组。计算出每站的用油差。并将其与最小累加量比较
for(i = 0; i < gasSize; i++) {
int diff = gas[i] - cost[i];
curSum += diff;
if(curSum < min)
min = curSum;
}
//若汽油总数为负数,代表无法跑完一环。返回-1
if(curSum < 0)
return -1;
//若min大于等于0说明每一天加油量比用油量多。因此从0出发即可
if(min >= 0)
return 0;
//若累加最小值为负,则找到一个非零元素(加油量大于出油量)出发。返回坐标
for(i = gasSize - 1; i >= 0; i--) {
min+=(gas[i]-cost[i]);
if(min >= 0)
return i;
}
//逻辑上不会返回这个0
return 0;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 135. 分发糖果
链接https://leetcode-cn.com/problems/candy/
[力扣题目链接](https://leetcode-cn.com/problems/candy/)
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线老师会根据每个孩子的表现预先给他们评分。
@ -47,7 +47,7 @@
代码如下:
```C++
```CPP
// 从前向后
for (int i = 1; i < ratings.size(); i++) {
if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
@ -80,7 +80,7 @@ for (int i = 1; i < ratings.size(); i++) {
所以该过程代码如下:
```C++
```CPP
// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1] ) {
@ -90,7 +90,7 @@ for (int i = ratings.size() - 2; i >= 0; i--) {
```
整体代码如下:
```C++
```CPP
class Solution {
public:
int candy(vector<int>& ratings) {
@ -242,4 +242,4 @@ var candy = function(ratings) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
## 139.单词拆分
题目链接https://leetcode-cn.com/problems/word-break/
[力扣题目链接](https://leetcode-cn.com/problems/word-break/)
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
@ -37,15 +37,15 @@
## 思路
看到这道题目的时候,大家应该回想起我们之前讲解回溯法专题的时候,讲过的一道题目[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q),就是枚举字符串的所有分割情况。
看到这道题目的时候,大家应该回想起我们之前讲解回溯法专题的时候,讲过的一道题目[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html),就是枚举字符串的所有分割情况。
[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q):是枚举分割后的所有子串,判断是否回文。
[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html):是枚举分割后的所有子串,判断是否回文。
本道是枚举分割所有字符串,判断是否在字典里出现过。
那么这里我也给出回溯法C++代码:
```C++
```CPP
class Solution {
private:
bool backtracking (const string& s, const unordered_set<string>& wordSet, int startIndex) {
@ -86,7 +86,7 @@ public:
C++代码如下:
```C++
```CPP
class Solution {
private:
bool backtracking (const string& s,
@ -161,11 +161,11 @@ dp[0]表示如果字符串为空的话,说明出现在字典里。
**如果求排列数就是外层for遍历背包内层for循环遍历物品**。
对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://mp.weixin.qq.com/s/znj-9j8mWymRFaPjJN2Qnw),这篇本周小节中,我做了如下总结:
对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://programmercarl.com/%E5%91%A8%E6%80%BB%E7%BB%93/20210204动规周末总结.html),这篇本周小节中,我做了如下总结:
求组合数:[动态规划518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)
求排列数:[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)
求最小数:[动态规划322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)
求组合数:[动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)
求排列数:[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和.html)、[动态规划70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html)
求最小数:[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划279.完全平方数](https://programmercarl.com/0279.完全平方数.html)
本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!
@ -190,7 +190,7 @@ dp[s.size()]就是最终结果。
动规五部曲分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
@ -215,7 +215,7 @@ public:
## 总结
本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)非常像,所以我也给出了对应的回溯解法。
本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)非常像,所以我也给出了对应的回溯解法。
稍加分析,便可知道本题是完全背包,而且是求能否组成背包,所以遍历顺序理论上来讲 两层for循环谁先谁后都可以
@ -319,4 +319,4 @@ const wordBreak = (s, wordDict) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -45,7 +45,7 @@ fast和slow各自再走一步 fast和slow就相遇了
C++代码如下
```C++
```CPP
class Solution {
public:
bool hasCycle(ListNode *head) {
@ -66,7 +66,7 @@ public:
做完这道题目可以在做做142.环形链表II不仅仅要找环还要找环的入口。
142.环形链表II题解[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)
142.环形链表II题解[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)
# 其他语言版本
@ -120,5 +120,5 @@ class Solution:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -13,7 +13,7 @@
## 142.环形链表II
https://leetcode-cn.com/problems/linked-list-cycle-ii/
[力扣题目链接](https://leetcode-cn.com/problems/linked-list-cycle-ii/)
题意:
给定一个链表返回链表开始入环的第一个节点。 如果链表无环则返回 null。
@ -109,7 +109,7 @@ fast指针走过的节点数` x + y + n (y + z)`n为fast指针在环内走
代码如下:
```C++
```CPP
/**
* Definition for singly-linked list.
* struct ListNode {
@ -146,7 +146,7 @@ public:
在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?**
即文章[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中如下的地方:
即文章[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中如下的地方:
![142环形链表5](https://img-blog.csdnimg.cn/20210318165123581.png)
@ -175,7 +175,7 @@ public:
那有同学又说了为什么fast不能跳过去呢 在刚刚已经说过一次了,**fast相对于slow是一次移动一个节点所以不可能跳过去**。
好了这次把为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)的补充。
好了这次把为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)的补充。
## 总结
@ -296,9 +296,42 @@ var detectCycle = function(head) {
};
```
Swift:
```swift
class Solution {
func detectCycle(_ head: ListNode?) -> ListNode? {
var slow: ListNode? = head
var fast: ListNode? = head
while fast != nil && fast?.next != nil {
slow = slow?.next
fast = fast?.next?.next
if slow == fast {
// 环内相遇
var list1: ListNode? = slow
var list2: ListNode? = head
while list1 != list2 {
list1 = list1?.next
list2 = list2?.next
}
return list2
}
}
return nil
}
}
extension ListNode: Equatable {
public func hash(into hasher: inout Hasher) {
hasher.combine(val)
hasher.combine(ObjectIdentifier(self))
}
public static func == (lhs: ListNode, rhs: ListNode) -> Bool {
return lhs === rhs
}
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -1,4 +1,3 @@
<p align="center">
<a href="https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ"><img src="https://img.shields.io/badge/PDF下载-代码随想录-blueviolet" alt=""></a>
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
@ -25,7 +24,7 @@
代码如下:
```C++
```CPP
class Solution {
public:
void reorderList(ListNode* head) {
@ -63,7 +62,8 @@ public:
## 方法二
把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了
```C++
```CPP
class Solution {
public:
void reorderList(ListNode* head) {
@ -108,7 +108,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
private:
// 反转链表
@ -176,15 +176,187 @@ public:
Java
Python
```java
// 方法三
public class ReorderList {
public void reorderList(ListNode head) {
ListNode fast = head, slow = head;
//求出中点
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
//right就是右半部分 12345 就是45 1234 就是34
ListNode right = slow.next;
//断开左部分和右部分
slow.next = null;
//反转右部分 right就是反转后右部分的起点
right = reverseList(right);
//左部分的起点
ListNode left = head;
//进行左右部分来回连接
//这里左部分的节点个数一定大于等于右部分的节点个数 因此只判断right即可
while (right != null) {
ListNode curLeft = left.next;
left.next = right;
left = curLeft;
ListNode curRight = right.next;
right.next = left;
right = curRight;
}
}
public ListNode reverseList(ListNode head) {
ListNode headNode = new ListNode(0);
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = headNode.next;
headNode.next = cur;
cur = next;
}
return headNode.next;
}
}
-------------------------------------------------------------------------
// 方法一 Java实现使用数组存储节点
class Solution {
public void reorderList(ListNode head) {
// 双指针的做法
ListNode cur = head;
// ArrayList底层是数组可以使用下标随机访问
List<ListNode> list = new ArrayList<>();
while (cur != null){
list.add(cur);
cur = cur.next;
}
cur = head; // 重新回到头部
int l = 1, r = list.size() - 1; // 注意左边是从1开始
int count = 0;
while (l <= r){
if (count % 2 == 0){
// 偶数
cur.next = list.get(r);
r--;
}else {
// 奇数
cur.next = list.get(l);
l++;
}
// 每一次指针都需要移动
cur = cur.next;
count++;
}
// 当是偶数的话,需要做额外处理
if (list.size() % 2== 0){
cur.next = list.get(l);
cur = cur.next;
}
// 注意结尾要结束一波
cur.next = null;
}
}
-------------------------------------------------------------------------
// 方法二:使用双端队列,简化了数组的操作,代码相对于前者更简洁(避免一些边界条件)
class Solution {
public void reorderList(ListNode head) {
// 使用双端队列的方法来解决
Deque<ListNode> de = new LinkedList<>();
// 这里是取head的下一个节点head不需要再入队了避免造成重复
ListNode cur = head.next;
while (cur != null){
de.offer(cur);
cur = cur.next;
}
cur = head; // 回到头部
int count = 0;
while (!de.isEmpty()){
if (count % 2 == 0){
// 偶数,取出队列右边尾部的值
cur.next = de.pollLast();
}else {
// 奇数,取出队列左边头部的值
cur.next = de.poll();
}
cur = cur.next;
count++;
}
cur.next = null;
}
}
```
Python
```python3
# 方法二 双向队列
class Solution:
def reorderList(self, head: ListNode) -> None:
"""
Do not return anything, modify head in-place instead.
"""
d = collections.deque()
tmp = head
while tmp.next: # 链表除了首元素全部加入双向队列
d.append(tmp.next)
tmp = tmp.next
tmp = head
while len(d): # 一后一前加入链表
tmp.next = d.pop()
tmp = tmp.next
if len(d):
tmp.next = d.popleft()
tmp = tmp.next
tmp.next = None # 尾部置空
# 方法三 反转链表
class Solution:
def reorderList(self, head: ListNode) -> None:
if head == None or head.next == None:
return True
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
right = slow.next # 分割右半边
slow.next = None # 切断
right = self.reverseList(right) #反转右半边
left = head
# 左半边一定比右半边长, 因此判断右半边即可
while right:
curLeft = left.next
left.next = right
left = curLeft
curRight = right.next
right.next = left
right = curRight
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while(cur!=None):
temp = cur.next # 保存一下cur的下一个节点
cur.next = pre # 反转
pre = cur
cur = temp
return pre
```
Go
JavaScript
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -13,7 +13,7 @@
# 150. 逆波兰表达式求值
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
[力扣题目链接](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/)
根据 逆波兰表示法,求表达式的值。
@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
 
示例 1
* 输入: ["2", "1", "+", "3", " * "]
@ -37,16 +37,21 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
示例 3
* 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]
* 输出: 22
* 解释:该算式转化为常见的中缀算术表达式为:
```
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
 
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
```
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
@ -62,7 +67,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
# 思路
在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)提到了 递归就是用栈来实现的。
在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。
所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。
@ -70,17 +75,17 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中的对对碰游戏是不是就非常像了。**
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。**
如动画所示:
![150.逆波兰表达式求值](https://code-thinking.cdn.bcebos.com/gifs/150.逆波兰表达式求值.gif)
相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)是差不错的,只不过本题不要相邻元素做消除了,而是做运算!
相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)是差不错的,只不过本题不要相邻元素做消除了,而是做运算!
C++代码如下:
```C++
```CPP
class Solution {
public:
int evalRPN(vector<string>& tokens) {
@ -223,17 +228,19 @@ var evalRPN = function(tokens) {
python3
```python
def evalRPN(tokens) -> int:
stack = list()
for i in range(len(tokens)):
if tokens[i] not in ["+", "-", "*", "/"]:
stack.append(tokens[i])
else:
tmp1 = stack.pop()
tmp2 = stack.pop()
res = eval(tmp2+tokens[i]+tmp1)
stack.append(str(int(res)))
return stack[-1]
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for item in tokens:
if item not in {"+", "-", "*", "/"}:
stack.append(item)
else:
first_num, second_num = stack.pop(), stack.pop()
stack.append(
int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面
)
return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的
```
@ -241,4 +248,4 @@ def evalRPN(tokens) -> int:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 151.翻转字符串里的单词
https://leetcode-cn.com/problems/reverse-words-in-a-string/
[力扣题目链接](https://leetcode-cn.com/problems/reverse-words-in-a-string/)
给定一个字符串,逐个翻转字符串中的每个单词。
@ -61,7 +61,7 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/
思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码:
```C++
```CPP
void removeExtraSpaces(string& s) {
for (int i = s.size() - 1; i > 0; i--) {
if (s[i] == s[i - 1] && s[i] == ' ') {
@ -83,17 +83,17 @@ void removeExtraSpaces(string& s) {
如果不仔细琢磨一下erase的时间复杂读还以为以上的代码是O(n)的时间复杂度呢。
想一下真正的时间复杂度是多少一个erase本来就是O(n)的操作erase实现原理题目[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)最优的算法来移除元素也要O(n)。
想一下真正的时间复杂度是多少一个erase本来就是O(n)的操作erase实现原理题目[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)最优的算法来移除元素也要O(n)。
erase操作上面还套了一个for循环那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。
那么使用双指针法来去移除空格最后resize重新设置一下字符串的大小就可以做到O(n)的时间复杂度。
如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)是如何移除元素的。
如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)是如何移除元素的。
那么使用双指针来移除冗余空格代码如下: fastIndex走的快slowIndex走的慢最后slowIndex就标记着移除多余空格后新字符串的长度。
```C++
```CPP
void removeExtraSpaces(string& s) {
int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
// 去掉字符串前面的空格
@ -125,7 +125,7 @@ void removeExtraSpaces(string& s) {
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)和[541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)里已经讲过了。
还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。
代码如下:
@ -141,7 +141,7 @@ void reverse(string& s, int start, int end) {
本题C++整体代码
```C++
```CPP
// 版本一
class Solution {
public:
@ -467,6 +467,85 @@ function reverse(strArr, start, end) {
}
```
Swift:
```swift
func reverseWords(_ s: String) -> String {
var stringArr = removeSpace(s)
reverseString(&stringArr, startIndex: 0, endIndex: stringArr.count - 1)
reverseWord(&stringArr)
return String(stringArr)
}
/// 1、移除多余的空格前后所有的空格中间只留一个空格
func removeSpace(_ s: String) -> [Character] {
let ch = Array(s)
var left = 0
var right = ch.count - 1
// 忽略字符串前面的所有空格
while ch[left] == " " {
left += 1
}
// 忽略字符串后面的所有空格
while ch[right] == " " {
right -= 1
}
// 接下来就是要处理中间的多余空格
var lastArr = Array<Character>()
while left <= right {
// 准备加到新字符串当中的字符
let char = ch[left]
// 新的字符串的最后一个字符;或者原字符串中,准备加到新字符串的那个字符;这两个字符当中,只要有一个不是空格,就可以加到新的字符串当中
if char != " " || lastArr[lastArr.count - 1] != " " {
lastArr.append(char)
}
left += 1
}
return lastArr
}
/// 2、反转整个字符串
func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) {
var start = startIndex
var end = endIndex
while start < end {
(s[start], s[end]) = (s[end], s[start])
start += 1
end -= 1
}
}
/// 3、再次将字符串里面的单词反转
func reverseWord(_ s: inout [Character]) {
var start = 0
var end = 0
var entry = false
for i in 0..<s.count {
if !entry {
start = i
entry = true
}
if entry && s[i] == " " && s[i - 1] != " " {
end = i - 1
entry = false
reverseString(&s, startIndex: start, endIndex: end)
}
if entry && (i == s.count - 1) && s[i] != " " {
end = i
entry = false
reverseString(&s, startIndex: start, endIndex: end)
}
}
}
```
@ -474,4 +553,4 @@ function reverse(strArr, start, end) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -1,2 +1,3 @@
同:[链表:链表相交](./面试题02.07.链表相交.md)
同:[链表:链表相交](https://programmercarl.com/面试题02.07.链表相交.html)
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 188.买卖股票的最佳时机IV
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
@ -35,13 +35,13 @@
## 思路
这道题目可以说是[动态规划123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)的进阶版这里要求至多有k次交易。
这道题目可以说是[动态规划123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)的进阶版这里要求至多有k次交易。
动规五部曲,分析如下:
1. 确定dp数组以及下标的含义
在[动态规划123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)中我是定义了一个二维dp数组本题其实依然可以用一个二维dp数组。
在[动态规划123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)中我是定义了一个二维dp数组本题其实依然可以用一个二维dp数组。
使用二维数组 dp[i][j] 第i天的状态为j所剩下的最大现金是dp[i][j]
@ -84,14 +84,14 @@ vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
同理可以类比剩下的状态,代码如下:
```C++
```CPP
for (int j = 0; j < 2 * k - 1; j += 2) {
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
```
**本题和[动态规划123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)最大的区别就是这里要类比j为奇数是买偶数是卖剩的状态**。
**本题和[动态规划123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)最大的区别就是这里要类比j为奇数是买偶数是卖剩的状态**。
3. dp数组如何初始化
@ -117,7 +117,7 @@ for (int j = 0; j < 2 * k - 1; j += 2) {
代码如下:
```C++
```CPP
for (int j = 1; j < 2 * k; j += 2) {
dp[0][j] = -prices[0];
}
@ -139,7 +139,7 @@ for (int j = 1; j < 2 * k; j += 2) {
以上分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
@ -196,7 +196,7 @@ class Solution {
}
}
// 版本二: 空间优化
// 版本二: 二维 dp数组
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices.length == 0) return 0;
@ -220,6 +220,25 @@ class Solution {
return dp[len - 1][k*2];
}
}
//版本三:一维 dp数组
class Solution {
public int maxProfit(int k, int[] prices) {
//在版本二的基础上,由于我们只关心前一天的股票买入情况,所以只存储前一天的股票买入情况
if(prices.length==0)return 0;
int[] dp=new int[2*k+1];
for (int i = 1; i <2*k ; i+=2) {
dp[i]=-prices[0];
}
for (int i = 0; i <prices.length ; i++) {
for (int j = 1; j <2*k ; j+=2) {
dp[j]=Math.max(dp[j],dp[j-1]-prices[i]);
dp[j+1]=Math.max(dp[j+1],dp[j]+prices[i]);
}
}
return dp[2*k];
}
}
```
@ -287,4 +306,4 @@ const maxProfit = (k,prices) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -37,17 +37,17 @@
这道题目在字符串里其实很常见,我把字符串反转相关的题目列一下:
* [字符串力扣541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)
* [字符串力扣151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)
* [字符串剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)
* [字符串力扣541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)
* [字符串力扣151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)
* [字符串剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)
本题其实和[字符串剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)就非常像了剑指offer上左旋转本题是右旋转。
本题其实和[字符串剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)就非常像了剑指offer上左旋转本题是右旋转。
注意题目要求是**要求使用空间复杂度为 O(1) 的 原地 算法**
那么我来提供一种旋转的方式哈。
在[字符串剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们提到,如下步骤就可以坐旋转字符串:
在[字符串剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)中,我们提到,如下步骤就可以坐旋转字符串:
1. 反转区间为前n的子串
2. 反转区间为n到末尾的子串
@ -69,7 +69,7 @@
C++代码如下:
```C++
```CPP
class Solution {
public:
void rotate(vector<int>& nums, int k) {
@ -131,12 +131,28 @@ class Solution:
## JavaScript
```js
var rotate = function (nums, k) {
function reverse(nums, i, j) {
while (i < j) {
[nums[i],nums[j]] = [nums[j],nums[i]]; // 解构赋值
i++;
j--;
}
}
let n = nums.length;
k %= n;
if (k) {
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
}
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 198.打家劫舍
题目链接https://leetcode-cn.com/problems/house-robber/
[力扣题目链接](https://leetcode-cn.com/problems/house-robber/)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
@ -25,7 +25,7 @@
输出12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
  偷窃到的最高金额 = 2 + 9 + 1 = 12 。
 
提示:
@ -59,7 +59,7 @@
代码如下:
```C++
```CPP
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
@ -70,7 +70,7 @@ dp[1] = max(nums[0], nums[1]);
dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!
代码如下:
```C++
```CPP
for (int i = 2; i < nums.size(); i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
@ -86,7 +86,7 @@ for (int i = 2; i < nums.size(); i++) {
以上分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int rob(vector<int>& nums) {
@ -175,6 +175,22 @@ func max(a, b int) int {
}
```
JavaScript
```javascript
const rob = nums => {
// 数组长度
const len = nums.length;
// dp数组初始化
const dp = [nums[0], Math.max(nums[0], nums[1])];
// 从下标2开始遍历
for (let i = 2; i < len; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[len - 1];
};
```
@ -182,4 +198,4 @@ func max(a, b int) int {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 第202题. 快乐数
https://leetcode-cn.com/problems/happy-number/
[力扣题目链接](https://leetcode-cn.com/problems/happy-number/)
编写一个算法来判断一个数 n 是不是快乐数。
@ -36,7 +36,7 @@ https://leetcode-cn.com/problems/happy-number/
题目中说了会 **无限循环**,那么也就是说**求和的过程中sum会重复出现这对解题很重要**
正如:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。**
正如:[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。**
所以这道题目使用哈希法来判断这个sum是否重复出现如果重复了就是return false 否则一直找到sum为1为止。
@ -46,7 +46,7 @@ https://leetcode-cn.com/problems/happy-number/
C++代码如下:
```C++
```CPP
class Solution {
public:
// 取数值各个位上的单数之和
@ -111,25 +111,29 @@ Python
```python
class Solution:
def isHappy(self, n: int) -> bool:
set_ = set()
while 1:
sum_ = self.getSum(n)
if sum_ == 1:
def calculate_happy(num):
sum_ = 0
# 从个位开始依次取,平方求和
while num:
sum_ += (num % 10) ** 2
num = num // 10
return sum_
# 记录中间结果
record = set()
while True:
n = calculate_happy(n)
if n == 1:
return True
#如果这个sum曾经出现过说明已经陷入了无限循环了立刻return false
if sum_ in set_:
# 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
if n in record:
return False
else:
set_.add(sum_)
n = sum_
#取数值各个位上的单数之和
def getSum(self, n):
sum_ = 0
while n > 0:
sum_ += (n%10) * (n%10)
n //= 10
return sum_
record.add(n)
```
Go
@ -187,10 +191,71 @@ var isHappy = function(n) {
};
```
Swift
```swift
// number 每个位置上的数字的平方和
func getSum(_ number: Int) -> Int {
var sum = 0
var num = number
while num > 0 {
let temp = num % 10
sum += (temp * temp)
num /= 10
}
return sum
}
func isHappy(_ n: Int) -> Bool {
var set = Set<Int>()
var num = n
while true {
let sum = self.getSum(num)
if sum == 1 {
return true
}
// 如果这个sum曾经出现过说明已经陷入了无限循环了
if set.contains(sum) {
return false
} else {
set.insert(sum)
}
num = sum
}
}
```
PHP:
```php
class Solution {
/**
* @param Integer $n
* @return Boolean
*/
function isHappy($n) {
// use a set to record sum
// whenever there is a duplicated, stop
// == 1 return true, else false
$table = [];
while ($n != 1 && !isset($table[$n])) {
$table[$n] = 1;
$n = self::getNextN($n);
}
return $n == 1;
}
function getNextN(int $n) {
$res = 0;
while ($n > 0) {
$temp = $n % 10;
$res += $temp * $temp;
$n = floor($n / 10);
}
return $res;
}
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 203.移除链表元素
https://leetcode-cn.com/problems/remove-linked-list-elements/
[力扣题目链接](https://leetcode-cn.com/problems/remove-linked-list-elements/)
题意:删除链表中等于给定值 val 的所有节点。
@ -89,7 +89,7 @@ https://leetcode-cn.com/problems/remove-linked-list-elements/
**直接使用原来的链表来进行移除节点操作:**
```C++
```CPP
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
@ -118,7 +118,7 @@ public:
**设置一个虚拟头结点在进行移除节点操作:**
```C++
```CPP
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
@ -304,11 +304,62 @@ var removeElements = function(head, val) {
};
```
Swift
```swift
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/
func removeElements(_ head: ListNode?, _ val: Int) -> ListNode? {
let dummyNode = ListNode()
dummyNode.next = head
var currentNode = dummyNode
while let curNext = currentNode.next {
if curNext.val == val {
currentNode.next = curNext.next
} else {
currentNode = curNext
}
}
return dummyNode.next
}
```
PHP:
```php
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
// 虚拟头+双指针
func removeElements(head *ListNode, val int) *ListNode {
dummyHead := &ListNode{}
dummyHead.Next = head
pred := dummyHead
cur := head
for cur != nil {
if cur.Val == val {
pred.Next = cur.Next
} else {
pred = cur
}
cur = cur.Next
}
return dummyHead.Next
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 205. 同构字符串
题目地址:https://leetcode-cn.com/problems/isomorphic-strings/
[力扣题目链接](https://leetcode-cn.com/problems/isomorphic-strings/)
给定两个字符串 s  t判断它们是否是同构的。
@ -39,7 +39,7 @@
C++代码 如下:
```C++
```CPP
class Solution {
public:
bool isIsomorphic(string s, string t) {
@ -68,6 +68,25 @@ public:
## Java
```java
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character, Character> map1 = new HashMap<>();
Map<Character, Character> map2 = new HashMap<>();
for (int i = 0, j = 0; i < s.length(); i++, j++) {
if (!map1.containsKey(s.charAt(i))) {
map1.put(s.charAt(i), t.charAt(j)); // map1保存 s[i] 到 t[j]的映射
}
if (!map2.containsKey(t.charAt(j))) {
map2.put(t.charAt(j), s.charAt(i)); // map2保存 t[j] 到 s[i]的映射
}
// 无法映射,返回 false
if (map1.get(s.charAt(i)) != t.charAt(j) || map2.get(t.charAt(j)) != s.charAt(i)) {
return false;
}
}
return true;
}
}
```
## Python
@ -78,6 +97,23 @@ public:
## Go
```go
func isIsomorphic(s string, t string) bool {
map1 := make(map[byte]byte)
map2 := make(map[byte]byte)
for i := range s {
if _, ok := map1[s[i]]; !ok {
map1[s[i]] = t[i] // map1保存 s[i] 到 t[j]的映射
}
if _, ok := map2[t[i]]; !ok {
map2[t[i]] = s[i] // map2保存 t[i] 到 s[j]的映射
}
// 无法映射,返回 false
if (map1[s[i]] != t[i]) || (map2[t[i]] != s[i]) {
return false
}
}
return true
}
```
## JavaScript
@ -89,5 +125,5 @@ public:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 206.反转链表
https://leetcode-cn.com/problems/reverse-linked-list/
[力扣题目链接](https://leetcode-cn.com/problems/reverse-linked-list/)
题意:反转一个单链表。
@ -48,7 +48,7 @@ https://leetcode-cn.com/problems/reverse-linked-list/
# C++代码
## 双指针法
```C++
```CPP
class Solution {
public:
ListNode* reverseList(ListNode* head) {
@ -74,7 +74,7 @@ public:
关键是初始化的地方,可能有的同学会不理解, 可以看到双指针法中初始化 cur = headpre = NULL在递归法中可以从如下代码看出初始化的逻辑也是一样的只不过写法变了。
具体可以看代码(已经详细注释),**双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。**
```C++
```CPP
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
@ -275,11 +275,104 @@ var reverseList = function(head) {
};
```
Ruby:
```ruby
# 双指针
# Definition for singly-linked list.
# class ListNode
# attr_accessor :val, :next
# def initialize(val = 0, _next = nil)
# @val = val
# @next = _next
# end
# end
def reverse_list(head)
# return nil if head.nil? # 循环判断条件亦能起到相同作用因此不必单独判断
cur, per = head, nil
until cur.nil?
tem = cur.next
cur.next = per
per = cur
cur = tem
end
per
end
# 递归
# Definition for singly-linked list.
# class ListNode
# attr_accessor :val, :next
# def initialize(val = 0, _next = nil)
# @val = val
# @next = _next
# end
# end
def reverse_list(head)
reverse(nil, head)
end
def reverse(pre, cur)
return pre if cur.nil?
tem = cur.next
cur.next = pre
reverse(cur, tem) # 通过递归实现双指针法中的更新操作
end
```
Kotlin:
```Kotlin
fun reverseList(head: ListNode?): ListNode? {
var pre: ListNode? = null
var cur = head
while (cur != null) {
val temp = cur.next
cur.next = pre
pre = cur
cur = temp
}
return pre
}
```
Swift
```swift
/// 双指针法 (迭代)
/// - Parameter head: 头结点
/// - Returns: 翻转后的链表头结点
func reverseList(_ head: ListNode?) -> ListNode? {
if head == nil || head?.next == nil {
return head
}
var pre: ListNode? = nil
var cur: ListNode? = head
var temp: ListNode? = nil
while cur != nil {
temp = cur?.next
cur?.next = pre
pre = cur
cur = temp
}
return pre
}
/// 递归
/// - Parameter head: 头结点
/// - Returns: 翻转后的链表头结点
func reverseList2(_ head: ListNode?) -> ListNode? {
return reverse(pre: nil, cur: head)
}
func reverse(pre: ListNode?, cur: ListNode?) -> ListNode? {
if cur == nil {
return pre
}
let temp: ListNode? = cur?.next
cur?.next = pre
return reverse(pre: cur, cur: temp)
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 209.长度最小的子数组
题目链接: https://leetcode-cn.com/problems/minimum-size-subarray-sum/
[力扣题目链接](https://leetcode-cn.com/problems/minimum-size-subarray-sum/)
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
@ -26,7 +26,7 @@
代码如下:
```C++
```CPP
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
@ -86,7 +86,7 @@ public:
C++代码如下:
```C++
```CPP
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
@ -216,8 +216,85 @@ var minSubArrayLen = function(target, nums) {
};
```
Swift:
```swift
func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int {
var result = Int.max
var sum = 0
var starIndex = 0
for endIndex in 0..<nums.count {
sum += nums[endIndex]
while sum >= target {
result = min(result, endIndex - starIndex + 1)
sum -= nums[starIndex]
starIndex += 1
}
}
return result == Int.max ? 0 : result
}
```
Rust:
```rust
impl Solution {
pub fn min_sub_array_len(target: i32, nums: Vec<i32>) -> i32 {
let (mut result, mut subLength): (i32, i32) = (i32::MAX, 0);
let (mut sum, mut i) = (0, 0);
for (pos, val) in nums.iter().enumerate() {
sum += val;
while sum >= target {
subLength = (pos - i + 1) as i32;
if result > subLength {
result = subLength;
}
sum -= nums[i];
i += 1;
}
}
if result == i32::MAX {
return 0;
}
result
}
}
```
PHP:
```php
// 双指针 - 滑动窗口
class Solution {
/**
* @param Integer $target
* @param Integer[] $nums
* @return Integer
*/
function minSubArrayLen($target, $nums) {
if (count($nums) < 1) {
return 0;
}
$sum = 0;
$res = PHP_INT_MAX;
$left = 0;
for ($right = 0; $right < count($nums); $right++) {
$sum += $nums[$right];
while ($sum >= $target) {
$res = min($res, $right - $left + 1);
$sum -= $nums[$left];
$left++;
}
}
return $res == PHP_INT_MAX ? 0 : $res;
}
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,7 +8,7 @@
## 213.打家劫舍II
题目链接https://leetcode-cn.com/problems/house-robber-ii/
[力扣题目链接](https://leetcode-cn.com/problems/house-robber-ii/)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
@ -28,14 +28,14 @@
示例 3
输入nums = [0]
输出0
 
提示:
* 1 <= nums.length <= 100
* 0 <= nums[i] <= 1000
## 思路
这道题目和[198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)是差不多的,唯一区别就是成环了。
这道题目和[198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html)是差不多的,唯一区别就是成环了。
对于一个数组,成环的话主要有如下三种情况:
@ -55,11 +55,11 @@
**而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了**。
分析到这里,本题其实比较简单了。 剩下的和[198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)就是一样的了。
分析到这里,本题其实比较简单了。 剩下的和[198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html)就是一样的了。
代码如下:
```C++
```CPP
// 注意注释中的情况二情况三以及把198.打家劫舍的代码抽离出来了
class Solution {
public:
@ -174,4 +174,4 @@ Go
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -13,7 +13,7 @@
# 216.组合总和III
链接:https://leetcode-cn.com/problems/combination-sum-iii/
[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-iii/)
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
@ -34,9 +34,9 @@
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
相对于[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。
相对于[77. 组合](https://programmercarl.com/0077.组合.html)无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。
想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。
想到这一点了,做过[77. 组合](https://programmercarl.com/0077.组合.html)之后,本题是简单一些了。
本题k相当于了树的深度9因为整个集合就是9个数就是树的宽度。
@ -53,7 +53,7 @@
* **确定递归函数参数**
和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。
和[77. 组合](https://programmercarl.com/0077.组合.html)一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。
这里我依然定义path 和 result为全局变量。
@ -94,7 +94,7 @@ void backtracking(int targetSum, int k, int sum, int startIndex)
所以 终止代码如下:
```C++
```CPP
if (path.size() == k) {
if (sum == targetSum) result.push_back(path);
return; // 如果path.size() == k 但sum != targetSum 直接返回
@ -103,7 +103,7 @@ if (path.size() == k) {
* **单层搜索过程**
本题和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9
本题和[77. 组合](https://programmercarl.com/0077.组合.html)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9
如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png)
@ -112,7 +112,7 @@ if (path.size() == k) {
代码如下:
```C++
```CPP
for (int i = startIndex; i <= 9; i++) {
sum += i;
path.push_back(i);
@ -124,9 +124,9 @@ for (int i = startIndex; i <= 9; i++) {
**别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!**
参照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的模板不难写出如下C++代码:
参照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中的模板不难写出如下C++代码:
```C++
```CPP
class Solution {
private:
vector<vector<int>> result; // 存放结果集
@ -176,7 +176,7 @@ if (sum > targetSum) { // 剪枝操作
}
```
和[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) 一样for循环的范围也可以剪枝i <= 9 - (k - path.size()) + 1就可以了。
和[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html) 一样for循环的范围也可以剪枝i <= 9 - (k - path.size()) + 1就可以了。
最后C++代码如下:
@ -214,7 +214,7 @@ public:
# 总结
开篇就介绍了本题与[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)再做本题在合适不过。
开篇就介绍了本题与[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)再做本题在合适不过。
分析完区别,依然把问题抽象为树形结构,按照回溯三部曲进行讲解,最后给出剪枝的优化。
@ -392,10 +392,66 @@ var combinationSum3 = function(k, n) {
};
```
C:
```c
int* path;
int pathTop;
int** ans;
int ansTop;
int getPathSum() {
int i;
int sum = 0;
for(i = 0; i < pathTop; i++) {
sum += path[i];
}
return sum;
}
void backtracking(int targetSum, int k, int sum, int startIndex) {
if(pathTop == k) {
if(sum == targetSum) {
int* tempPath = (int*)malloc(sizeof(int) * k);
int j;
for(j = 0; j < k; j++)
tempPath[j] = path[j];
ans[ansTop++] = tempPath;
}
// 如果path.size() == k 但sum != targetSum 直接返回
return;
}
int i;
//从startIndex开始遍历一直遍历到9
for (i = startIndex; i <= 9; i++) {
sum += i; // 处理
path[pathTop++] = i; // 处理
backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
sum -= i; // 回溯
pathTop--;; // 回溯
}
}
int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){
//初始化辅助变量
path = (int*)malloc(sizeof(int) * k);
ans = (int**)malloc(sizeof(int*) * 20);
pathTop = ansTop = 0;
backtracking(n, k, 0, 1);
//设置返回的二维数组中元素个数为ansTop
*returnSize = ansTop;
//设置二维数组中每个元素个数的大小为k
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; i++) {
(*returnColumnSizes)[i] = k;
}
return ans;
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -7,24 +7,23 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 222.完全二叉树的节点个数
# 222.完全二叉树的节点个数
题目地址:https://leetcode-cn.com/problems/count-complete-tree-nodes/
[力扣题目链接](https://leetcode-cn.com/problems/count-complete-tree-nodes/)
给出一个完全二叉树,求出该树的节点个数。
示例:
示例 1
输入root = [1,2,3,4,5,6]
输出6
* 输入root = [1,2,3,4,5,6]
* 输出6
示例 2
输入root = []
输出0
* 输入root = []
* 输出0
示例 3
输入root = [1]
输出1
* 输入root = [1]
* 输出1
提示:
@ -33,21 +32,22 @@
* 题目数据保证输入的树是 完全二叉树
## 思路
# 思路
本篇给出按照普通二叉树的求法以及利用完全二叉树性质的求法。
## 普通二叉树
首先按照普通二叉树的逻辑来求。
这道题目的递归法和求二叉树的深度写法类似, 而迭代法,[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。
这道题目的递归法和求二叉树的深度写法类似, 而迭代法,[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。
递归遍历的顺序依然是后序(左右中)。
### 递归
如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)。
如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)。
1. 确定递归函数的参数和返回值参数就是传入树的根节点返回就返回以该节点为根节点二叉树的节点数量所以返回值为int类型。
@ -77,7 +77,7 @@ return treeNum;
所以整体C++代码如下:
```C++
```CPP
// 版本一
class Solution {
private:
@ -96,7 +96,7 @@ public:
```
代码精简之后C++代码如下:
```C++
```CPP
// 版本二
class Solution {
public:
@ -107,19 +107,19 @@ public:
};
```
时间复杂度O(n)
空间复杂度O(logn),算上了递归系统栈占用的空间
* 时间复杂度O(n)
* 空间复杂度O(logn),算上了递归系统栈占用的空间
**网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础**。
### 迭代法
如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)。
如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)。
那么只要模板少做改动加一个变量result统计节点数量就可以了
```C++
```CPP
class Solution {
public:
int countNodes(TreeNode* root) {
@ -140,12 +140,12 @@ public:
}
};
```
时间复杂度O(n)
空间复杂度O(n)
* 时间复杂度O(n)
* 空间复杂度O(n)
## 完全二叉树
以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A),这篇详细介绍了各种二叉树的特性。
以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html),这篇详细介绍了各种二叉树的特性。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
@ -163,7 +163,7 @@ public:
C++代码如下:
```C++
```CPP
class Solution {
public:
int countNodes(TreeNode* root) {
@ -187,13 +187,12 @@ public:
};
```
时间复杂度O(logn * logn)
空间复杂度O(logn)
* 时间复杂度O(logn * logn)
* 空间复杂度O(logn)
## 其他语言版本
# 其他语言版本
Java
## Java
```java
class Solution {
// 通用递归解法
@ -205,7 +204,27 @@ class Solution {
}
}
```
```java
class Solution {
// 迭代法
public int countNodes(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int result = 0;
while (!queue.isEmpty()) {
int size = queue.size();
while (size -- > 0) {
TreeNode cur = queue.poll();
result++;
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
}
}
return result;
}
}
```
```java
class Solution {
/**
@ -238,9 +257,9 @@ class Solution {
}
```
Python
## Python
> 递归法:
递归法:
```python
class Solution:
def countNodes(self, root: TreeNode) -> int:
@ -255,7 +274,7 @@ class Solution:
return treeNum
```
> 递归法:精简版
递归法:精简版
```python
class Solution:
def countNodes(self, root: TreeNode) -> int:
@ -264,7 +283,7 @@ class Solution:
return 1 + self.countNodes(root.left) + self.countNodes(root.right)
```
> 迭代法:
迭代法:
```python
import collections
class Solution:
@ -285,7 +304,7 @@ class Solution:
return result
```
> 完全二叉树
完全二叉树
```python
class Solution:
def countNodes(self, root: TreeNode) -> int:
@ -306,7 +325,7 @@ class Solution:
return self.countNodes(root.left) + self.countNodes(root.right) + 1
```
Go
## Go
递归版本
@ -361,7 +380,7 @@ func countNodes(root *TreeNode) int {
JavaScript:
## JavaScript:
递归版本
```javascript
@ -436,4 +455,4 @@ var countNodes = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 225. 用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/
[力扣题目链接](https://leetcode-cn.com/problems/implement-stack-using-queues/)
使用队列实现栈的下列操作:
@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/implement-stack-using-queues/
有的同学可能疑惑这种题目有什么实际工程意义,**其实很多算法题目主要是对知识点的考察和教学意义远大于其工程实践的意义,所以面试题也是这样!**
刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/Cj6R0qu8rFA7Et9V_ZMjCA)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!
刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://programmercarl.com/0232.用栈实现队列.html)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!
**队列模拟栈,其实一个队列就够了**,那么我们先说一说两个队列来实现栈的思路。
@ -65,7 +65,7 @@ queue.empty();
详细如代码注释所示:
```C++
```CPP
class MyStack {
public:
queue<int> que1;
@ -118,7 +118,7 @@ public:
C++优化代码
```C++
```CPP
class MyStack {
public:
queue<int> que;
@ -294,58 +294,136 @@ Python
```python
from collections import deque
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
Python普通的Queue或SimpleQueue没有类似于peek的功能
也无法用索引访问在实现top的时候较为困难。
用list可以但是在使用pop(0)的时候时间复杂度为O(n)
因此这里使用双向队列我们保证只执行popleft()和append()因为deque可以用索引访问可以实现和peek相似的功能
in - 存所有数据
out - 仅在pop的时候会用到
"""
#使用两个队列来实现
self.que1 = deque()
self.que2 = deque()
self.queue_in = deque()
self.queue_out = deque()
def push(self, x: int) -> None:
"""
Push element x onto stack.
直接append即可
"""
self.que1.append(x)
self.queue_in.append(x)
def pop(self) -> int:
"""
Removes the element on top of the stack and returns that element.
1. 首先确认不空
2. 因为队列的特殊性FIFO所以我们只有在pop()的时候才会使用queue_out
3. 先把queue_in中的所有元素除了最后一个依次出列放进queue_out
4. 交换in和out此时out里只有一个元素
5. 把out中的pop出来即是原队列的最后一个
tip这不能像栈实现队列一样因为另一个queue也是FIFO如果执行pop()它不能像
stack一样从另一个pop()所以干脆in只用来存数据pop()的时候两个进行交换
"""
size = len(self.que1)
size -= 1#这里先减一是为了保证最后面的元素
while size > 0:
size -= 1
self.que2.append(self.que1.popleft())
if self.empty():
return None
result = self.que1.popleft()
self.que1, self.que2= self.que2, self.que1#将que2和que1交换 que1经过之前的操作应该是空了
#一定注意不能直接使用que1 = que2 这样que2的改变会影响que1 可以用浅拷贝
return result
for i in range(len(self.queue_in) - 1):
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in # 交换in和out这也是为啥in只用来存
return self.queue_out.popleft()
def top(self) -> int:
"""
Get the top element.
1. 首先确认不空
2. 我们仅有in会存放数据所以返回第一个即可
"""
return self.que1[-1]
if self.empty():
return None
return self.queue_in[-1]
def empty(self) -> bool:
"""
Returns whether the stack is empty.
因为只有in存了数据只要判断in是不是有数即可
"""
#print(self.que1)
if len(self.que1) == 0:
return True
else:
return False
return len(self.queue_in) == 0
```
Go
```go
type MyStack struct {
queue []int//创建一个队列
}
/** Initialize your data structure here. */
func Constructor() MyStack {
return MyStack{ //初始化
queue:make([]int,0),
}
}
/** Push element x onto stack. */
func (this *MyStack) Push(x int) {
//添加元素
this.queue=append(this.queue,x)
}
/** Removes the element on top of the stack and returns that element. */
func (this *MyStack) Pop() int {
n:=len(this.queue)-1//判断长度
for n!=0{ //除了最后一个,其余的都重新添加到队列里
val:=this.queue[0]
this.queue=this.queue[1:]
this.queue=append(this.queue,val)
n--
}
//弹出元素
val:=this.queue[0]
this.queue=this.queue[1:]
return val
}
/** Get the top element. */
func (this *MyStack) Top() int {
//利用Pop函数弹出来的元素重新添加
val:=this.Pop()
this.queue=append(this.queue,val)
return val
}
/** Returns whether the stack is empty. */
func (this *MyStack) Empty() bool {
return len(this.queue)==0
}
/**
* Your MyStack object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Top();
* param_4 := obj.Empty();
*/
```
javaScript:
使用数组push, shift模拟队列
@ -460,4 +538,4 @@ MyStack.prototype.empty = function() {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 226.翻转二叉树
题目地址:https://leetcode-cn.com/problems/invert-binary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/invert-binary-tree/)
翻转一棵二叉树。
@ -43,7 +43,7 @@
**注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果**
**这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了**
**这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了**
那么层序遍历可以不可以呢?**依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!**
@ -51,7 +51,7 @@
对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)详细讲解了。
对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://programmercarl.com/二叉树的递归遍历.html)详细讲解了。
我们下文以前序遍历为例,通过动画来看一下翻转的过程:
@ -89,7 +89,7 @@ invertTree(root->right);
基于这递归三步法代码基本写完C++代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -106,12 +106,11 @@ public:
### 深度优先遍历
[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码:
[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码:
C++代码迭代法(前序遍历)
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -129,14 +128,14 @@ public:
}
};
```
如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)。
如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)。
我们在[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
我们在[二叉树:前中后序迭代方式的统一写法](https://programmercarl.com/二叉树的统一迭代法.html)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
C++代码如下迭代法(前序遍历)
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -162,13 +161,13 @@ public:
};
```
如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)。
如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://programmercarl.com/二叉树的统一迭代法.html)。
### 广度优先遍历
也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -188,7 +187,7 @@ public:
}
};
```
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
## 拓展
@ -196,7 +195,7 @@ public:
如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -215,7 +214,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
@ -364,7 +363,9 @@ class Solution:
return root
```
### Go
### Go
递归版本的前序遍历
```Go
func invertTree(root *TreeNode) *TreeNode {
@ -382,6 +383,96 @@ func invertTree(root *TreeNode) *TreeNode {
}
```
递归版本的后序遍历
```go
func invertTree(root *TreeNode) *TreeNode {
if root==nil{
return root
}
invertTree(root.Left)//遍历左节点
invertTree(root.Right)//遍历右节点
root.Left,root.Right=root.Right,root.Left//交换
return root
}
```
迭代版本的前序遍历
```go
func invertTree(root *TreeNode) *TreeNode {
stack:=[]*TreeNode{}
node:=root
for node!=nil||len(stack)>0{
for node!=nil{
node.Left,node.Right=node.Right,node.Left//交换
stack=append(stack,node)
node=node.Left
}
node=stack[len(stack)-1]
stack=stack[:len(stack)-1]
node=node.Right
}
return root
}
```
迭代版本的后序遍历
```go
func invertTree(root *TreeNode) *TreeNode {
stack:=[]*TreeNode{}
node:=root
var prev *TreeNode
for node!=nil||len(stack)>0{
for node!=nil{
stack=append(stack,node)
node=node.Left
}
node=stack[len(stack)-1]
stack=stack[:len(stack)-1]
if node.Right==nil||node.Right==prev{
node.Left,node.Right=node.Right,node.Left//交换
prev=node
node=nil
}else {
stack=append(stack,node)
node=node.Right
}
}
return root
}
```
层序遍历
```go
func invertTree(root *TreeNode) *TreeNode {
if root==nil{
return root
}
queue:=list.New()
node:=root
queue.PushBack(node)
for queue.Len()>0{
length:=queue.Len()
for i:=0;i<length;i++{
e:=queue.Remove(queue.Front()).(*TreeNode)
e.Left,e.Right=e.Right,e.Left//交换
if e.Left!=nil{
queue.PushBack(e.Left)
}
if e.Right!=nil{
queue.PushBack(e.Right)
}
}
}
return root
}
```
### JavaScript
使用递归版本的前序遍历
@ -478,4 +569,4 @@ var invertTree = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
# 232.用栈实现队列
https://leetcode-cn.com/problems/implement-queue-using-stacks/
[力扣题目链接](https://leetcode-cn.com/problems/implement-queue-using-stacks/)
使用栈实现队列的下列操作:
@ -67,7 +67,7 @@ queue.empty();
C++代码如下:
```C++
```CPP
class MyQueue {
public:
stack<int> stIn;
@ -129,101 +129,6 @@ public:
Java
使用Stack(堆栈)同名方法:
```java
class MyQueue {
// java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack
Deque<Integer> stIn;
Deque<Integer> stOut;
/** Initialize your data structure here. */
public MyQueue() {
stIn = new ArrayDeque<>();
stOut = new ArrayDeque<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
stIn.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
// 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中
if (stOut.isEmpty()) {
while (!stIn.isEmpty()) {
stOut.push(stIn.pop());
}
}
// 再返回 stOut 中的元素
return stOut.pop();
}
/** Get the front element. */
public int peek() {
// 直接使用已有的pop函数
int res = this.pop();
// 因为pop函数弹出了元素res所以再添加回去
stOut.push(res);
return res;
}
/** Returns whether the queue is empty. */
public boolean empty() {
// 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了
// 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了
return stIn.isEmpty() && stOut.isEmpty();
}
}
```
个人习惯写法使用Deque通用api
```java
class MyQueue {
// java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack
// Deque 中的 addFirst、removeFirst、peekFirst 等方法等效于 Stack(堆栈) 中的 push、pop、peek
Deque<Integer> stIn;
Deque<Integer> stOut;
/** Initialize your data structure here. */
public MyQueue() {
stIn = new ArrayDeque<>();
stOut = new ArrayDeque<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
stIn.addLast(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
// 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中
if (stOut.isEmpty()) {
while (!stIn.isEmpty()) {
stOut.addLast(stIn.pollLast());
}
}
// 再返回 stOut 中的元素
return stOut.pollLast();
}
/** Get the front element. */
public int peek() {
// 直接使用已有的pop函数
int res = this.pop();
// 因为pop函数弹出了元素res所以再添加回去
stOut.addLast(res);
return res;
}
/** Returns whether the queue is empty. */
public boolean empty() {
// 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了
// 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了
return stIn.isEmpty() && stOut.isEmpty();
}
}
```
```java
class MyQueue {
@ -281,48 +186,53 @@ class MyQueue {
Python
```python
# 使用两个栈实现先进先出的队列
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
in主要负责pushout主要负责pop
"""
self.stack1 = list()
self.stack2 = list()
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
有新元素进来就往in里面push
"""
# self.stack1用于接受元素
self.stack1.append(x)
self.stack_in.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
# self.stack2用于弹出元素如果self.stack2为[],则将self.stack1中元素全部弹出给self.stack2
if self.stack2 == []:
while self.stack1:
tmp = self.stack1.pop()
self.stack2.append(tmp)
return self.stack2.pop()
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
"""
Get the front element.
"""
if self.stack2 == []:
while self.stack1:
tmp = self.stack1.pop()
self.stack2.append(tmp)
return self.stack2[-1]
ans = self.pop()
self.stack_out.append(ans)
return ans
def empty(self) -> bool:
"""
Returns whether the queue is empty.
只要in或者out有元素说明队列不为空
"""
return self.stack1 == [] and self.stack2 == []
return not (self.stack_in or self.stack_out)
```
@ -384,7 +294,7 @@ func (this *MyQueue) Peek() int {
func (this *MyQueue) Empty() bool {
return len(this.stack) == 0 && len(this.back) == 0
}
```
javaScript:
@ -442,10 +352,8 @@ MyQueue.prototype.empty = function() {
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
# 234.回文链表
题目链接:https://leetcode-cn.com/problems/palindrome-linked-list/
[力扣题目链接](https://leetcode-cn.com/problems/palindrome-linked-list/)
请判断一个链表是否为回文链表。
@ -30,7 +30,7 @@
代码也比较简单。如下:
```C++
```CPP
class Solution {
public:
bool isPalindrome(ListNode* head) {
@ -51,7 +51,7 @@ public:
上面代码可以在优化就是先求出链表长度然后给定vector的初始长度这样避免vector每次添加节点重新开辟空间
```C++
```CPP
class Solution {
public:
bool isPalindrome(ListNode* head) {
@ -95,7 +95,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
bool isPalindrome(ListNode* head) {
@ -144,21 +144,147 @@ public:
## Java
```java
// 方法一,使用数组
class Solution {
public boolean isPalindrome(ListNode head) {
int len = 0;
// 统计链表长度
ListNode cur = head;
while (cur != null) {
len++;
cur = cur.next;
}
cur = head;
int[] res = new int[len];
// 将元素加到数组之中
for (int i = 0; i < res.length; i++){
res[i] = cur.val;
cur = cur.next;
}
// 比较回文
for (int i = 0, j = len - 1; i < j; i++, j--){
if (res[i] != res[j]){
return false;
}
}
return true;
}
}
// 方法二,快慢指针
class Solution {
public boolean isPalindrome(ListNode head) {
// 如果为空或者仅有一个节点返回true
if (head == null && head.next == null) return true;
ListNode slow = head;
ListNode fast = head;
ListNode pre = head;
while (fast != null && fast.next != null){
pre = slow; // 记录slow的前一个结点
slow = slow.next;
fast = fast.next.next;
}
pre.next = null; // 分割两个链表
// 前半部分
ListNode cur1 = head;
// 后半部分。这里使用了反转链表
ListNode cur2 = reverseList(slow);
while (cur1 != null){
if (cur1.val != cur2.val) return false;
// 注意要移动两个结点
cur1 = cur1.next;
cur2 = cur2.next;
}
return true;
}
ListNode reverseList(ListNode head){
// 反转链表
ListNode tmp = null;
ListNode pre = null;
while (head != null){
tmp = head.next;
head.next = pre;
pre = head;
head = tmp;
}
return pre;
}
}
```
## Python
```python
```python3
#数组模拟
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
length = 0
tmp = head
while tmp: #求链表长度
length += 1
tmp = tmp.next
result = [0] * length
tmp = head
index = 0
while tmp: #链表元素加入数组
result[index] = tmp.val
index += 1
tmp = tmp.next
i, j = 0, length - 1
while i < j: # 判断回文
if result[i] != result[j]:
return False
i += 1
j -= 1
return True
#反转后半部分链表
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if head == None or head.next == None:
return True
slow, fast = head, head
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
pre.next = None # 分割链表
cur1 = head # 前半部分
cur2 = self.reverseList(slow) # 反转后半部分总链表长度如果是奇数cur2比cur1多一个节点
while cur1:
if cur1.val != cur2.val:
return False
cur1 = cur1.next
cur2 = cur2.next
return True
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while(cur!=None):
temp = cur.next # 保存一下cur的下一个节点
cur.next = pre # 反转
pre = cur
cur = temp
return pre
```
## Go
```go
```
## JavaScript
```js
```
@ -166,5 +292,5 @@ public:
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -7,9 +7,9 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 235. 二叉搜索树的最近公共祖先
# 235. 二叉搜索树的最近公共祖先
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/)
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
@ -21,14 +21,15 @@
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
* 输出: 6
* 解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
* 输出: 2
* 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
@ -36,9 +37,11 @@
* 所有节点的值都是唯一的。
* p、q 为不同节点且均存在于给定的二叉搜索树中。
## 思路
# 思路
做过[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)题目的同学应该知道利用回溯从底向上搜索遇到一个节点的左子树里有p右子树里有q那么当前节点就是最近公共祖先。
做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道利用回溯从底向上搜索遇到一个节点的左子树里有p右子树里有q那么当前节点就是最近公共祖先。
那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
@ -48,7 +51,7 @@
理解这一点,本题就很好解了。
和[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。
和[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。
那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。
@ -58,6 +61,7 @@
可以看出直接按照指定的方向就可以找到节点4为最近公共祖先而且不需要遍历整棵树找到结果直接返回
## 递归法
递归三部曲如下:
@ -93,7 +97,7 @@ if (cur == NULL) return cur;
代码如下:
```C++
```CPP
if (cur->val > p->val && cur->val > q->val) {
TreeNode* left = traversal(cur->left, p, q);
if (left != NULL) {
@ -105,13 +109,12 @@ if (cur->val > p->val && cur->val > q->val) {
**细心的同学会发现在这里调用递归函数的地方把递归函数的返回值left直接return**。
在[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。
在[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。
搜索一条边的写法:
```
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
```
@ -128,7 +131,7 @@ left与right的逻辑处理;
如果 cur->val 小于 p->val同时 cur->val 小于 q->val那么就应该向右遍历目标区间在右子树
```
```CPP
if (cur->val < p->val && cur->val < q->val) {
TreeNode* right = traversal(cur->right, p, q);
if (right != NULL) {
@ -140,14 +143,14 @@ if (cur->val < p->val && cur->val < q->val) {
剩下的情况就是cur节点在区间p->val <= cur->val && cur->val <= q->val或者 q->val <= cur->val && cur->val <= p->val那么cur就是最近公共祖先了直接返回cur。
代码如下:
```
return cur;
```
那么整体递归代码如下:
```C++
```CPP
class Solution {
private:
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {
@ -177,7 +180,7 @@ public:
精简后代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
@ -192,13 +195,13 @@ public:
## 迭代法
对于二叉搜索树的迭代法,大家应该在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)就了解了。
对于二叉搜索树的迭代法,大家应该在[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)就了解了。
利用其有序性,迭代的方式还是比较简单的,解题思路在递归中已经分析了。
迭代代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
@ -216,19 +219,32 @@ public:
灵魂拷问:是不是又被简单的迭代法感动到痛哭流涕?
## 总结
# 总结
对于二叉搜索树的最近祖先问题,其实要比[普通二叉树公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)简单的多。
对于二叉搜索树的最近祖先问题,其实要比[普通二叉树公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)简单的多。
不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。
## 其他语言版本
# 其他语言版本
Java
## Java
递归法:
```java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
}
```
迭代法:
```java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
@ -246,15 +262,11 @@ class Solution {
}
```
Python
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
## Python
递归法:
```python
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root: return root //中
@ -264,18 +276,14 @@ class Solution:
return self.lowestCommonAncestor(root.right,p,q) //右
else: return root
```
Go
> BSL法
迭代法:
## Go
递归法:
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//利用BSL的性质前序遍历有序
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root==nil{return nil}
@ -287,34 +295,10 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
}
```
> 普通法
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//递归会将值层层返回
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
//终止条件
if root==nil||root.Val==p.Val||root.Val==q.Val{return root}//最后为空或者找到一个值时,就返回这个值
//后序遍历
findLeft:=lowestCommonAncestor(root.Left,p,q)
findRight:=lowestCommonAncestor(root.Right,p,q)
//处理单层逻辑
if findLeft!=nil&&findRight!=nil{return root}//说明在root节点的两边
if findLeft==nil{//左边没找到,就说明在右边找到了
return findRight
}else {return findLeft}
}
```
## JavaScript
JavaScript版本
1. 使用递归的方法
递归法:
```javascript
var lowestCommonAncestor = function(root, p, q) {
// 使用递归的方法
@ -336,7 +320,8 @@ var lowestCommonAncestor = function(root, p, q) {
return root;
};
```
2. 使用迭代的方法
迭代法
```javascript
var lowestCommonAncestor = function(root, p, q) {
// 使用迭代的方法
@ -355,9 +340,8 @@ var lowestCommonAncestor = function(root, p, q) {
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,9 +9,9 @@
> 本来是打算将二叉树和二叉搜索树的公共祖先问题一起讲,后来发现篇幅过长了,只能先说一说二叉树的公共祖先问题。
## 236. 二叉树的最近公共祖先
# 236. 二叉树的最近公共祖先
题目链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
@ -35,7 +35,7 @@
* 所有节点的值都是唯一的。
* p、q 为不同节点且均存在于给定的二叉树中。
## 思路
# 思路
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
@ -79,7 +79,7 @@ if (root == q || root == p || root == NULL) return root;
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中说了 递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值!
我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中说了 递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值!
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
@ -150,7 +150,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q);
代码如下:
```C++
```CPP
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else { // (left == NULL && right == NULL)
@ -167,7 +167,7 @@ else { // (left == NULL && right == NULL)
整体代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
@ -188,7 +188,7 @@ public:
稍加精简,代码如下:
```C++
```CPP
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
@ -202,7 +202,7 @@ public:
};
```
## 总结
# 总结
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
@ -219,10 +219,10 @@ public:
本题没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了。
## 其他语言版本
# 其他语言版本
Java
## Java
```Java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
@ -249,7 +249,6 @@ class Solution {
```java
// 代码精简版
class Solution {
TreeNode pre;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root.val == p.val ||root.val == q.val) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
@ -262,14 +261,9 @@ class Solution {
}
```
Python
## Python
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
//递归
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
@ -281,7 +275,9 @@ class Solution:
elif not left and right: return right //目标节点是通过right返回的
else: return None //没找到
```
Go
## Go
```Go
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
// check
@ -311,7 +307,8 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
}
```
JavaScript版本
## JavaScript
```javascript
var lowestCommonAncestor = function(root, p, q) {
// 使用递归的方法
@ -343,4 +340,4 @@ var lowestCommonAncestor = function(root, p, q) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 239. 滑动窗口最大值
https://leetcode-cn.com/problems/sliding-window-maximum/
[力扣题目链接](https://leetcode-cn.com/problems/sliding-window-maximum/)
给定一个数组 nums有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
@ -21,7 +21,7 @@ https://leetcode-cn.com/problems/sliding-window-maximum/
进阶:
你能在线性时间复杂度内解决此题吗?
 
<img src='https://code-thinking.cdn.bcebos.com/pics/239.滑动窗口最大值.png' width=600> </img></div>
提示:
@ -104,11 +104,11 @@ public:
那么我们用什么数据结构来实现这个单调队列呢?
使用deque最为合适在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/HCXfQ_Bhpi63YaX0ZRSnAQ)中我们就提到了常用的queue在没有指定容器的情况下deque就是默认底层容器。
使用deque最为合适在文章[栈与队列:来看看栈和队列不为人知的一面](https://programmercarl.com/栈与队列理论基础.html)中我们就提到了常用的queue在没有指定容器的情况下deque就是默认底层容器。
基于刚刚说过的单调队列pop和push的规则代码不难实现如下
```C++
```CPP
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
@ -140,7 +140,7 @@ public:
C++代码如下:
```C++
```CPP
class Solution {
private:
class MyQueue { //单调队列(从大到小)
@ -425,4 +425,4 @@ var maxSlidingWindow = function (nums, k) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 242.有效的字母异位词
https://leetcode-cn.com/problems/valid-anagram/
[力扣题目链接](https://leetcode-cn.com/problems/valid-anagram/)
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
@ -35,7 +35,7 @@ https://leetcode-cn.com/problems/valid-anagram/
**数组其实就是一个简单哈希表**而且这道题目中字符串只有小写字符那么就可以定义一个数组来记录字符串s里字符出现的次数。
如果对哈希表的理论基础关于数组setmap不了解的话可以看这篇[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)
如果对哈希表的理论基础关于数组setmap不了解的话可以看这篇[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)
需要定义一个多大的数组呢定一个数组叫做record大小为26 就可以了初始化为0因为字符a到字符z的ASCII也是26个连续的数值。
@ -61,7 +61,7 @@ https://leetcode-cn.com/problems/valid-anagram/
C++ 代码如下:
```C++
```CPP
class Solution {
public:
bool isAnagram(string s, string t) {
@ -198,6 +198,83 @@ var isAnagram = function(s, t) {
};
```
Swift
```Swift
func isAnagram(_ s: String, _ t: String) -> Bool {
if s.count != t.count {
return false
}
var record = Array(repeating: 0, count: 26)
let aUnicodeScalar = "a".unicodeScalars.first!.value
for c in s.unicodeScalars {
record[Int(c.value - aUnicodeScalar)] += 1
}
for c in t.unicodeScalars {
record[Int(c.value - aUnicodeScalar)] -= 1
}
for value in record {
if value != 0 {
return false
}
}
return true
}
```
PHP
```php
class Solution {
/**
* @param String $s
* @param String $t
* @return Boolean
*/
function isAnagram($s, $t) {
if (strlen($s) != strlen($t)) {
return false;
}
$table = [];
for ($i = 0; $i < strlen($s); $i++) {
if (!isset($table[$s[$i]])) {
$table[$s[$i]] = 1;
} else {
$table[$s[$i]]++;
}
if (!isset($table[$t[$i]])) {
$table[$t[$i]] = -1;
} else {
$table[$t[$i]]--;
}
}
foreach ($table as $record) {
if ($record != 0) {
return false;
}
}
return true;
}
}
```
Rust
```rust
impl Solution {
pub fn is_anagram(s: String, t: String) -> bool {
let mut record = vec![0; 26];
let baseChar = 'a';
for byte in s.bytes() {
record[byte as usize - baseChar as usize] += 1;
}
for byte in t.bytes() {
record[byte as usize - baseChar as usize] -= 1;
}
record.iter().filter(|x| **x != 0).count() == 0
}
}
```
## 相关题目
* 383.赎金信
@ -209,4 +286,4 @@ var isAnagram = function(s, t) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,9 +9,9 @@
> 以为只用了递归,其实还用了回溯
## 257. 二叉树的所有路径
# 257. 二叉树的所有路径
题目地址:https://leetcode-cn.com/problems/binary-tree-paths/
[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-paths/)
给定一个二叉树,返回所有从根节点到叶子节点的路径。
@ -20,7 +20,7 @@
示例:
![257.二叉树的所有路径1](https://img-blog.csdnimg.cn/2021020415161576.png)
## 思路
# 思路
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
@ -77,7 +77,7 @@ if (cur->left == NULL && cur->right == NULL) {
这里我们先使用vector<int>结构的path容器来记录路径那么终止处理逻辑如下
```C++
```CPP
if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点
string sPath;
for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
@ -113,7 +113,7 @@ if (cur->right) {
那么回溯要怎么回溯呢,一些同学会这么写,如下:
```C++
```CPP
if (cur->left) {
traversal(cur->left, path, result);
}
@ -129,7 +129,7 @@ path.pop_back();
那么代码应该这么写:
```C++
```CPP
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯
@ -142,7 +142,7 @@ if (cur->right) {
那么本题整体代码如下:
```C++
```CPP
class Solution {
private:
@ -183,7 +183,7 @@ public:
那么如上代码可以精简成如下代码:
```C++
```CPP
class Solution {
private:
@ -215,8 +215,52 @@ public:
那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完path依然是没有加上"->" 的,这就是回溯了。
**如果这里还不理解的话,可以看这篇[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA),我这这篇中详细的解释了递归中如何隐藏着回溯。 **
为了把这份精简代码的回溯过程展现出来,大家可以试一试把:
```CPP
if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里
```
改成如下代码:
```CPP
path += "->";
traversal(cur->left, path, result); // 左
```
即:
```CPP
if (cur->left) {
path += "->";
traversal(cur->left, path, result); // 左
}
if (cur->right) {
path += "->";
traversal(cur->right, path, result); // 右
}
```
此时就没有回溯了,这个代码就是通过不了的了。
如果想把回溯加上,就要 在上面代码的基础上加上回溯就可以AC了。
```CPP
if (cur->left) {
path += "->";
traversal(cur->left, path, result); // 左
path.pop_back(); // 回溯
path.pop_back();
}
if (cur->right) {
path += "->";
traversal(cur->right, path, result); // 右
path.pop_back(); // 回溯
path.pop_back();
}
```
**大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的因为并有没有改变path的数值执行完递归函数之后path依然是之前的数值相当于回溯了**
**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现了出来了。**
@ -225,13 +269,14 @@ public:
## 迭代法
至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。
至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)和[二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html)。
这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。
C++代码如下:
```C++
```CPP
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
@ -262,7 +307,7 @@ public:
```
当然使用java的同学可以直接定义一个成员变量为object的栈`Stack<Object> stack = new Stack<>();`,这样就不用定义两个栈了,都放到一个栈里就可以了。
## 总结
# 总结
**本文我们开始初步涉及到了回溯,很多同学过了这道题目,可能都不知道自己其实使用了回溯,回溯和递归都是相伴相生的。**
@ -278,7 +323,7 @@ public:
## 其他语言版本
# 其他语言版本
Java
@ -321,83 +366,100 @@ class Solution {
}
}
}
//解法二(常规前序遍历,不用回溯),更容易理解
```
```java
// 解法2
class Solution {
/**
* 迭代法
*/
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
helper(root, new StringBuilder(), res);
return res;
}
public void helper(TreeNode root, StringBuilder sb, List<String> res) {
if (root == null) {return;}
// 遇到叶子结点就放入当前路径到res集合中
if (root.left == null && root.right ==null) {
sb.append(root.val);
res.add(sb.toString());
// 记得结束当前方法
return;
List<String> result = new ArrayList<>();
if (root == null)
return result;
Stack<Object> stack = new Stack<>();
// 节点和路径同时入栈
stack.push(root);
stack.push(root.val + "");
while (!stack.isEmpty()) {
// 节点和路径同时出栈
String path = (String) stack.pop();
TreeNode node = (TreeNode) stack.pop();
// 若找到叶子节点
if (node.left == null && node.right == null) {
result.add(path);
}
//右子节点不为空
if (node.right != null) {
stack.push(node.right);
stack.push(path + "->" + node.right.val);
}
//左子节点不为空
if (node.left != null) {
stack.push(node.left);
stack.push(path + "->" + node.left.val);
}
}
helper(root.left,new StringBuilder(sb).append(root.val + "->"),res);
helper(root.right,new StringBuilder(sb).append(root.val + "->"),res);
return result;
}
}
//针对解法二优化,思路本质是一样的
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
helper(root, "", res);
return res;
}
public void helper(TreeNode root, String path, List<String> res) {
if (root == null) {return;}
// 由原始解法二可以知道root的值肯定会下面某一个条件加入到path中那么干脆直接在这一步加入即可
StringBuilder sb = new StringBuilder(path);
sb.append(root.val);
if (root.left == null && root.right ==null) {
res.add(sb.toString());
}else{
// 如果是非叶子结点则还需要跟上一个 “->”
sb.append("->");
helper(root.left,sb.toString(),res);
helper(root.right,sb.toString(),res);
}
}
}
```
Python
```Python
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
"""二叉树的所有路径 递归法"""
def binaryTreePaths(self, root: TreeNode) -> List[str]:
path=[]
res=[]
def backtrace(root, path):
if not root:return
path.append(root.val)
if (not root.left)and (not root.right):
res.append(path[:])
ways=[]
if root.left:ways.append(root.left)
if root.right:ways.append(root.right)
for way in ways:
backtrace(way,path)
path.pop()
backtrace(root,path)
return ["->".join(list(map(str,i))) for i in res]
path, result = '', []
self.traversal(root, path, result)
return result
def traversal(self, cur: TreeNode, path: List, result: List):
path += str(cur.val)
# 如果当前节点为叶子节点,添加路径到结果中
if not (cur.left or cur.right):
result.append(path)
return
if cur.left:
self.traversal(cur.left, path + '->', result)
if cur.right:
self.traversal(cur.right, path + '->', result)
```
Go
```python
from collections import deque
class Solution:
"""二叉树的所有路径 迭代法"""
def binaryTreePaths(self, root: TreeNode) -> List[str]:
# 题目中节点数至少为1
stack, path_st, result = deque([root]), deque(), []
path_st.append(str(root.val))
while stack:
cur = stack.pop()
path = path_st.pop()
# 如果当前节点为叶子节点,添加路径到结果中
if not (cur.left or cur.right):
result.append(path)
if cur.right:
stack.append(cur.right)
path_st.append(path + '->' + str(cur.right.val))
if cur.left:
stack.append(cur.left)
path_st.append(path + '->' + str(cur.left.val))
return result
```
Go
```go
func binaryTreePaths(root *TreeNode) []string {
res := make([]string, 0)
@ -422,7 +484,9 @@ func binaryTreePaths(root *TreeNode) []string {
```
JavaScript:
1.递归版本
```javascript
var binaryTreePaths = function(root) {
//递归遍历+递归三部曲
@ -452,4 +516,4 @@ var binaryTreePaths = function(root) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 279.完全平方数
题目地址https://leetcode-cn.com/problems/perfect-squares/
[力扣题目链接](https://leetcode-cn.com/problems/perfect-squares/)
给定正整数 n找到若干个完全平方数比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
@ -36,7 +36,7 @@
**我来把题目翻译一下完全平方数就是物品可以无限件使用凑个正整数n就是背包问凑满这个背包最少有多少物品**
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)就是一样一样的!
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)就是一样一样的!
动规五部曲分析如下:
@ -70,13 +70,13 @@ dp[0]表示 和为0的完全平方数的最小数量那么dp[0]一定是0。
如果求排列数就是外层for遍历背包内层for循环遍历物品。
在[动态规划322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
在[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
**所以本题外层for遍历背包里层for遍历物品还是外层for遍历物品内层for遍历背包都是可以的**
我这里先给出外层遍历背包,里层遍历物品的代码:
```C++
```CPP
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 0; i <= n; i++) { // 遍历背包
@ -106,7 +106,7 @@ dp[5] = min(dp[4] + 1, dp[1] + 1) = 2
以上动规五部曲分析完毕C++代码如下:
```C++
```CPP
// 版本一
class Solution {
public:
@ -125,7 +125,7 @@ public:
同样我在给出先遍历物品在遍历背包的代码一样的可以AC的。
```C++
```CPP
// 版本二
class Solution {
public:
@ -146,7 +146,7 @@ public:
## 总结
如果大家认真做了昨天的题目[动态规划322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ),今天这道就非常简单了,一样的套路一样的味道。
如果大家认真做了昨天的题目[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html),今天这道就非常简单了,一样的套路一样的味道。
但如果没有按照「代码随想录」的题目顺序来做的话,做动态规划或者做背包问题,上来就做这道题,那还是挺难的!
@ -161,6 +161,7 @@ public:
Java
```Java
class Solution {
// 版本一,先遍历物品, 再遍历背包
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
@ -170,7 +171,9 @@ class Solution {
}
//当和为0时组合的个数为0
dp[0] = 0;
// 遍历物品
for (int i = 1; i * i <= n; i++) {
// 遍历背包
for (int j = i * i; j <= n; j++) {
if (dp[j - i * i] != max) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
@ -180,6 +183,28 @@ class Solution {
return dp[n];
}
}
class Solution {
// 版本二, 先遍历背包, 再遍历物品
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
// 初始化
for (int j = 0; j <= n; j++) {
dp[j] = max;
}
// 当和为0时组合的个数为0
dp[0] = 0;
// 遍历背包
for (int j = 1; j <= n; j++) {
// 遍历物品
for (int i = 1; i * i <= j; i++) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
}
```
Python
@ -187,7 +212,7 @@ Python
```python3
class Solution:
def numSquares(self, n: int) -> int:
'''版本一'''
'''版本一,先遍历背包, 再遍历物品'''
# 初始化
nums = [i**2 for i in range(1, n + 1) if i**2 <= n]
dp = [10**4]*(n + 1)
@ -201,7 +226,7 @@ class Solution:
return dp[n]
def numSquares1(self, n: int) -> int:
'''版本二'''
'''版本二 先遍历物品, 再遍历背包'''
# 初始化
nums = [i**2 for i in range(1, n + 1) if i**2 <= n]
dp = [10**4]*(n + 1)
@ -217,6 +242,22 @@ class Solution:
Python3:
```python
class Solution:
'''版本一,先遍历背包, 再遍历物品'''
def numSquares(self, n: int) -> int:
dp = [n] * (n + 1)
dp[0] = 0
# 遍历背包
for j in range(1, n+1):
for i in range(1, n):
num = i ** 2
if num > j: break
# 遍历物品
if j - num >= 0:
dp[j] = min(dp[j], dp[j - num] + 1)
return dp[n]
class Solution:
'''版本二, 先遍历物品, 再遍历背包'''
def numSquares(self, n: int) -> int:
# 初始化
# 组成和的完全平方数的最多个数就是只用1构成
@ -286,10 +327,38 @@ func min(a, b int) int {
}
```
Javascript:
```Javascript
// 先遍历物品,再遍历背包
var numSquares1 = function(n) {
let dp = new Array(n + 1).fill(Infinity)
dp[0] = 0
for(let i = 0; i <= n; i++) {
let val = i * i
for(let j = val; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - val] + 1)
}
}
return dp[n]
};
// 先遍历背包,再遍历物品
var numSquares2 = function(n) {
let dp = new Array(n + 1).fill(Infinity)
dp[0] = 0
for(let i = 1; i <= n; i++) {
for(let j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i - j * j] + 1, dp[i])
}
}
return dp[n]
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -10,7 +10,7 @@
# 283. 移动零
题目链接https://leetcode-cn.com/problems/move-zeroes/
[力扣题目链接](https://leetcode-cn.com/problems/move-zeroes/)
给定一个数组 nums编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
@ -26,13 +26,13 @@
# 思路
做这道题目之前,大家可以做一做[27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)
做这道题目之前,大家可以做一做[27.移除元素](https://programmercarl.com/0027.移除元素.html)
这道题目使用暴力的解法可以两层for循环模拟数组删除元素也就是向前覆盖的过程。
好了,我们说一说双指针法,大家如果对双指针还不熟悉,可以看我的这篇总结[双指针法:总结篇!](https://mp.weixin.qq.com/s/PLfYLuUIGDR6xVRQ_jTrmg)。
好了,我们说一说双指针法,大家如果对双指针还不熟悉,可以看我的这篇总结[双指针法:总结篇!](https://programmercarl.com/双指针总结.html)。
双指针法在数组移除元素中可以达到O(n)的时间复杂度,在[27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)里已经详细讲解了,那么本题和移除元素其实是一个套路。
双指针法在数组移除元素中可以达到O(n)的时间复杂度,在[27.移除元素](https://programmercarl.com/0027.移除元素.html)里已经详细讲解了,那么本题和移除元素其实是一个套路。
**相当于对整个数组移除元素0然后slowIndex之后都是移除元素0的冗余元素把这些元素都赋值为0就可以了**。
@ -42,7 +42,7 @@
C++代码如下:
```C++
```CPP
class Solution {
public:
void moveZeroes(vector<int>& nums) {
@ -100,5 +100,5 @@ JavaScript
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,13 +8,13 @@
## 300.最长递增子序列
题目链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
[力扣题目链接](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
 
示例 1
输入nums = [10,9,2,5,3,7,101,18]
输出4
@ -27,20 +27,19 @@
示例 3
输入nums = [7,7,7,7,7,7,7]
输出1
 
提示:
* 1 <= nums.length <= 2500
* -10^4 <= nums[i] <= 104
## 思路
最长上升子序列是动规的经典题目这里dp[i]是可以根据dp[j] j < i推导出来的那么依然用动规五部曲来分析详细一波
1. dp[i]的定义
**dp[i]表示i之前包括i的最长上升子序列**。
**dp[i]表示i之前包括i的最长上升子序列的长度**。
2. 状态转移方程
@ -60,7 +59,7 @@ dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍
j其实就是0到i-1遍历i的循环里外层遍历j则在内层代码如下
```C++
```CPP
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
@ -80,7 +79,7 @@ for (int i = 1; i < nums.size(); i++) {
以上五部分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
@ -189,13 +188,18 @@ const lengthOfLIS = (nums) => {
return result;
};
```
*复杂度分析*
- 时间复杂度O(nlogn)。数组 nums 的长度为 n我们依次用数组中的元素去更新 dp 数组,相当于插入最后递增的元素,而更新 dp 数组时需要进行 O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)。
- 空间复杂度O(n),需要额外使用长度为 n 的 dp 数组。
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,9 +8,11 @@
## 309.最佳买卖股票时机含冷冻期
题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
给定一个整数数组其中第 i 个元素代表了第 i 天的股票价格 。​
[https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
给定一个整数数组其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
@ -25,12 +27,12 @@
## 思路
> 之前我们在[动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/IgC0iWWCDpYL9ZbTHGHgfw)讲过一次这道题目,讲解的过程感觉不是很严谨,和录友们也聊过这个问题,本着对大家负责的态度,有问题的地方我都会及时纠正,所以重新发文讲解一下。
> 之前我们在[动态规划:最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html)讲过一次这道题目,讲解的过程感觉不是很严谨,和录友们也聊过这个问题,本着对大家负责的态度,有问题的地方我都会及时纠正,所以重新发文讲解一下。
相对于[动态规划122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题加上了一个冷冻期
相对于[动态规划122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II动态规划.html),本题加上了一个冷冻期
在[动态规划122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。
在[动态规划122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II动态规划.html) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。
动规五部曲,分析如下:
@ -95,7 +97,7 @@ p[i][3] = dp[i - 1][2];
综上分析,递推代码如下:
```C++
```CPP
dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i];
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
@ -129,7 +131,7 @@ dp[i][3] = dp[i - 1][2];
代码如下:
```C++
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices) {
@ -236,4 +238,4 @@ const maxProfit = (prices) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 322. 零钱兑换
题目链接https://leetcode-cn.com/problems/coin-change/
[力扣题目链接](https://leetcode-cn.com/problems/coin-change/)
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额返回 -1。
@ -44,7 +44,7 @@
## 思路
在[动态规划518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中我们已经兑换一次零钱了,这次又要兑换,套路不一样!
在[动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)中我们已经兑换一次零钱了,这次又要兑换,套路不一样!
题目中说每种硬币的数量是无限的,可以看出是典型的完全背包问题。
@ -91,7 +91,7 @@ dp[0] = 0;
**如果求排列数就是外层for遍历背包内层for循环遍历物品**。
在动态规划专题我们讲过了求组合数是[动态规划518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ),求排列数是[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)。
在动态规划专题我们讲过了求组合数是[动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html),求排列数是[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)。
**所以本题的两个for循环的关系是外层for循环遍历物品内层for遍历背包或者外层for遍历背包内层for循环遍历物品都是可以的**
@ -112,7 +112,7 @@ dp[amount]为最终结果。
## C++代码
以上分析完毕C++ 代码如下:
```C++
```CPP
// 版本一
class Solution {
public:
@ -134,7 +134,7 @@ public:
对于遍历方式遍历背包放在外循环,遍历物品放在内循环也是可以的,我就直接给出代码了
```C++
```CPP
// 版本二
class Solution {
public:
@ -166,7 +166,7 @@ public:
那么这篇文章就把遍历顺序分析的清清楚楚。
[动态规划518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中求的是组合数,[动态规划377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中求的是排列数。
[动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)中求的是组合数,[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中求的是排列数。
**而本题是要求最少硬币数量硬币是组合数还是排列数都无所谓所以两个for循环先后顺序怎样都可以**
@ -330,4 +330,4 @@ const coinChange = (coins, amount) => {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -11,7 +11,7 @@
## 332.重新安排行程
题目地址:https://leetcode-cn.com/problems/reconstruct-itinerary/
[力扣题目链接](https://leetcode-cn.com/problems/reconstruct-itinerary/)
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK肯尼迪国际机场出发的先生所以该行程必须从 JFK 开始。
@ -20,7 +20,7 @@
* 所有的机场都用三个大写字母表示(机场代码)。
* 假定所有机票至少存在一种合理的行程。
* 所有的机票必须都用一次 且 只能用一次。
 
示例 1
输入:[["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
@ -36,7 +36,7 @@
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。
这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://programmercarl.com/0077.组合.html)[分割问题](https://programmercarl.com/0093.复原IP地址.html)[子集问题](https://programmercarl.com/0078.子集.html)[排列问题](https://programmercarl.com/0046.全排列.html)。
直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。
@ -69,7 +69,7 @@
一个机场映射多个机场机场之间要靠字母序排列一个机场映射多个机场可以使用std::unordered_map如果让多个机场之间再有顺序的话就是用std::map 或者std::multimap 或者 std::multiset。
如果对map 和 set 的实现机制不太了解,也不清楚为什么 map、multimap就是有序的同学可以看这篇文章[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)。
如果对map 和 set 的实现机制不太了解,也不清楚为什么 map、multimap就是有序的同学可以看这篇文章[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)。
这样存放映射关系可以定义为 `unordered_map<string, multiset<string>> targets` 或者 `unordered_map<string, map<string, int>> targets`
@ -140,7 +140,7 @@ bool backtracking(int ticketNum, vector<string>& result) {
![332.重新安排行程1](https://img-blog.csdnimg.cn/2020111518065555.png)
所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)详细介绍过。
所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)详细介绍过。
当然本题的targets和result都需要初始化代码如下
```
@ -164,7 +164,7 @@ if (result.size() == ticketNum + 1) {
}
```
已经看习惯回溯法代码的同学到叶子节点了习惯性的想要收集结果但发现并不需要本题的result相当于 [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的path也就是本题的result就是记录路径的就一条在如下单层搜索的逻辑中result就添加元素了。
已经看习惯回溯法代码的同学到叶子节点了习惯性的想要收集结果但发现并不需要本题的result相当于 [回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中的path也就是本题的result就是记录路径的就一条在如下单层搜索的逻辑中result就添加元素了。
* 单层搜索的逻辑
@ -178,7 +178,7 @@ if (result.size() == ticketNum + 1) {
遍历过程如下:
```C++
```CPP
for (pair<const string, int>& target : targets[result[result.size() - 1]]) {
if (target.second > 0 ) { // 记录到达机场是否飞过了
result.push_back(target.first);
@ -194,7 +194,7 @@ for (pair<const string, int>& target : targets[result[result.size() - 1]]) {
分析完毕此时完整C++代码如下:
```C++
```CPP
class Solution {
private:
// unordered_map<出发机场, map<到达机场, 航班次数>> targets
@ -450,4 +450,4 @@ var findItinerary = function(tickets) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -9,7 +9,7 @@
## 337.打家劫舍 III
题目链接:https://leetcode-cn.com/problems/house-robber-iii/
[力扣题目链接](https://leetcode-cn.com/problems/house-robber-iii/)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
@ -19,7 +19,7 @@
## 思路
这道题目和 [198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)[213.打家劫舍II](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ)也是如出一辙,只不过这个换成了树。
这道题目和 [198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html)[213.打家劫舍II](https://programmercarl.com/0213.打家劫舍II.html)也是如出一辙,只不过这个换成了树。
如果对树的遍历不够熟悉的话,那本题就有难度了。
@ -29,13 +29,13 @@
与198.打家劫舍213.打家劫舍II一样关键是要讨论当前节点抢还是不抢。
如果抢了当前节点,两个孩子就不动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**
如果抢了当前节点,两个孩子就不动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**
### 暴力递归
代码如下:
```C++
```CPP
class Solution {
public:
int rob(TreeNode* root) {
@ -65,7 +65,7 @@ public:
代码如下:
```C++
```CPP
class Solution {
public:
unordered_map<TreeNode* , int> umap; // 记录计算过的结果
@ -91,7 +91,7 @@ public:
### 动态规划
在上面两种方法,其实对一个节点 投与不投得到的最大金钱都没有做记录,而是需要实时计算。
在上面两种方法,其实对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算。
而动态规划其实就是使用状态转移容器来记录状态的变化这里可以使用一个长度为2的数组记录当前节点偷与不偷所得到的的最大金钱。
@ -103,7 +103,7 @@ public:
参数为当前节点,代码如下:
```C++
```CPP
vector<int> robTree(TreeNode* cur) {
```
@ -121,7 +121,7 @@ vector<int> robTree(TreeNode* cur) {
2. 确定终止条件
在遍历的过程中,如果遇到空点的话很明显无论偷还是不偷都是0所以就返回
在遍历的过程中,如果遇到空点的话很明显无论偷还是不偷都是0所以就返回
```
if (cur == NULL) return vector<int>{0, 0};
```
@ -138,7 +138,7 @@ if (cur == NULL) return vector<int>{0, 0};
代码如下:
```C++
```CPP
// 下标0不偷下标1
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右
@ -156,7 +156,7 @@ vector<int> right = robTree(cur->right); // 右
代码如下:
```C++
```CPP
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右
@ -179,7 +179,7 @@ return {val2, val1};
递归三部曲与动规五部曲分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int rob(TreeNode* root) {
@ -210,7 +210,7 @@ public:
只不过平时我们习惯了在一维数组或者二维数组上推导公式,一下子换成了树,就需要对树的遍历方式足够了解!
大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈
大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈
“树形贪心”词汇从此诞生,来自「代码随想录」
@ -368,7 +368,32 @@ class Solution:
return (val1, val2)
```
Go
JavaScript
> 动态规划
```javascript
const rob = root => {
// 后序遍历函数
const postOrder = node => {
// 递归出口
if (!node) return [0, 0];
// 遍历左子树
const left = postOrder(node.left);
// 遍历右子树
const right = postOrder(node.right);
// 不偷当前节点,左右子节点都可以偷或不偷,取最大值
const DoNot = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
// 偷当前节点,左右子节点只能不偷
const Do = node.val + left[0] + right[0];
// [不偷,偷]
return [DoNot, Do];
};
const res = postOrder(root);
// 返回最大值
return Math.max(...res);
};
```
@ -377,4 +402,4 @@ Go
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -8,14 +8,15 @@
## 343. 整数拆分
题目链接https://leetcode-cn.com/problems/integer-break/
[力扣题目链接](https://leetcode-cn.com/problems/integer-break/)
给定一个正整数 n将其拆分为至少两个正整数的和并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
\解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
@ -59,6 +60,10 @@ j是从1开始遍历拆分j的情况在遍历j的过程中其实都计算
所以递推公式dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});
那么在取最大值的时候为什么还要比较dp[i]呢?
因为在递推公式推导的过程中每次计算dp[i],取最大的而已。
3. dp的初始化
@ -101,7 +106,7 @@ for (int i = 3; i <= n ; i++) {
以上动规五部曲分析完毕C++代码如下:
```C++
```CPP
class Solution {
public:
int integerBreak(int n) {
@ -128,7 +133,7 @@ public:
给出我的C++代码如下:
```C++
```CPP
class Solution {
public:
int integerBreak(int n) {
@ -154,7 +159,7 @@ public:
其实这道题目的递推公式并不好想,而且初始化的地方也很有讲究,我在写本题的时候一开始写的代码是这样的:
```C++
```CPP
class Solution {
public:
int integerBreak(int n) {
@ -223,7 +228,34 @@ class Solution:
return dp[n]
```
Go
```golang
func integerBreak(n int) int {
/**
动态五部曲
1.确定dp下标及其含义
2.确定递推公式
3.确定dp初始化
4.确定遍历顺序
5.打印dp
**/
dp:=make([]int,n+1)
dp[1]=1
dp[2]=1
for i:=3;i<n+1;i++{
for j:=1;j<i-1;j++{
// i可以差分为i-j和j。由于需要最大值故需要通过j遍历所有存在的值取其中最大的值作为当前i的最大值在求最大值的时候一个是j与i-j相乘一个是j与dp[i-j].
dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]))
}
}
return dp[n]
}
func max(a,b int) int{
if a>b{
return a
}
return b
}
```
Javascript:
```Javascript
@ -244,4 +276,4 @@ var integerBreak = function(n) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -12,7 +12,7 @@
# 344.反转字符串
https://leetcode-cn.com/problems/reverse-string/
[力扣题目链接](https://leetcode-cn.com/problems/reverse-string/)
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
@ -55,7 +55,7 @@ https://leetcode-cn.com/problems/reverse-string/
接下来再来讲一下如何解决反转字符串的问题。
大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)。
大家应该还记得,我们已经讲过了[206.反转链表](https://programmercarl.com/0206.翻转链表.html)。
在反转链表中,使用了双指针的方法。
@ -63,7 +63,7 @@ https://leetcode-cn.com/problems/reverse-string/
因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html)[必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。
对于字符串,我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
@ -74,7 +74,7 @@ https://leetcode-cn.com/problems/reverse-string/
不难写出如下C++代码:
```C++
```CPP
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
swap(s[i],s[j]);
@ -90,7 +90,7 @@ swap可以有两种实现。
一种就是常见的交换数值:
```C++
```CPP
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
@ -99,7 +99,7 @@ s[j] = tmp;
一种就是通过位运算:
```C++
```CPP
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
@ -120,7 +120,7 @@ s[i] ^= s[j];
C++代码如下:
```C++
```CPP
class Solution {
public:
void reverseString(vector<char>& s) {
@ -162,21 +162,14 @@ class Solution:
Do not return anything, modify s in-place instead.
"""
left, right = 0, len(s) - 1
while(left < right):
# 该方法已经不需要判断奇偶数,经测试后时间空间复杂度比用 for i in range(right//2)更低
# 推荐该写法,更加通俗易懂
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
# 下面的写法更加简洁,但是都是同样的算法
# class Solution:
# def reverseString(self, s: List[str]) -> None:
# """
# Do not return anything, modify s in-place instead.
# """
# 不需要判别是偶数个还是奇数个序列,因为奇数个的时候,中间那个不需要交换就可
# for i in range(len(s)//2):
# s[i], s[len(s)-1-i] = s[len(s)-1-i], s[i]
# return s
```
Go
@ -210,13 +203,34 @@ var reverseString = function(s) {
};
```
Swift:
```swift
// 双指针 - 元组
func reverseString(_ s: inout [Character]) {
var l = 0
var r = s.count - 1
while l < r {
// 使用元祖
(s[l], s[r]) = (s[r], s[l])
l += 1
r -= 1
}
}
// 双指针法 - 库函数
func reverseString(_ s: inout [Character]) {
var j = s.count - 1
for i in 0 ..< Int(Double(s.count) * 0.5) {
s.swapAt(i, j)
j -= 1
}
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -13,7 +13,7 @@
# 347.前 K 个高频元素
https://leetcode-cn.com/problems/top-k-frequent-elements/
[力扣题目链接](https://leetcode-cn.com/problems/top-k-frequent-elements/)
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
@ -76,7 +76,7 @@ https://leetcode-cn.com/problems/top-k-frequent-elements/
我们来看一下C++代码:
```C++
```CPP
// 时间复杂度O(nlogk)
// 空间复杂度O(n)
class Solution {
@ -189,6 +189,174 @@ class Solution:
Go
```go
//方法一:小顶堆
func topKFrequent(nums []int, k int) []int {
map_num:=map[int]int{}
//记录每个元素出现的次数
for _,item:=range nums{
map_num[item]++
}
h:=&IHeap{}
heap.Init(h)
//所有元素入堆堆的长度为k
for key,value:=range map_num{
heap.Push(h,[2]int{key,value})
if h.Len()>k{
heap.Pop(h)
}
}
res:=make([]int,k)
//按顺序返回堆中的元素
for i:=0;i<k;i++{
res[k-i-1]=heap.Pop(h).([2]int)[0]
}
return res
}
//构建小顶堆
type IHeap [][2]int
func (h IHeap) Len()int {
return len(h)
}
func (h IHeap) Less (i,j int) bool {
return h[i][1]<h[j][1]
}
func (h IHeap) Swap(i,j int) {
h[i],h[j]=h[j],h[i]
}
func (h *IHeap) Push(x interface{}){
*h=append(*h,x.([2]int))
}
func (h *IHeap) Pop() interface{}{
old:=*h
n:=len(old)
x:=old[n-1]
*h=old[0:n-1]
return x
}
//方法二:利用O(logn)排序
func topKFrequent(nums []int, k int) []int {
ans:=[]int{}
map_num:=map[int]int{}
for _,item:=range nums {
map_num[item]++
}
for key,_:=range map_num{
ans=append(ans,key)
}
//核心思想:排序
//可以不用包函数,自己实现快排
sort.Slice(ans,func (a,b int)bool{
return map_num[ans[a]]>map_num[ans[b]]
})
return ans[:k]
}
```
javaScript:
```js
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function(nums, k) {
const map = new Map();
for(const num of nums) {
map.set(num, (map.get(num) || 0) + 1);
}
// 创建小顶堆
const priorityQueue = new PriorityQueue((a, b) => a[1] - b[1]);
// entry 是一个长度为2的数组0位置存储key1位置存储value
for (const entry of map.entries()) {
priorityQueue.push(entry);
if (priorityQueue.size() > k) {
priorityQueue.pop();
}
}
const ret = [];
for(let i = priorityQueue.size() - 1; i >= 0; i--) {
ret[i] = priorityQueue.pop()[0];
}
return ret;
};
function PriorityQueue(compareFn) {
this.compareFn = compareFn;
this.queue = [];
}
// 添加
PriorityQueue.prototype.push = function(item) {
this.queue.push(item);
let index = this.queue.length - 1;
let parent = Math.floor((index - 1) / 2);
// 上浮
while(parent >= 0 && this.compare(parent, index) > 0) {
// 交换
[this.queue[index], this.queue[parent]] = [this.queue[parent], this.queue[index]];
index = parent;
parent = Math.floor((index - 1) / 2);
}
}
// 获取堆顶元素并移除
PriorityQueue.prototype.pop = function() {
const ret = this.queue[0];
// 把最后一个节点移到堆顶
this.queue[0] = this.queue.pop();
let index = 0;
// 左子节点下标left + 1 就是右子节点下标
let left = 1;
let selectedChild = this.compare(left, left + 1) > 0 ? left + 1 : left;
// 下沉
while(selectedChild !== undefined && this.compare(index, selectedChild) > 0) {
// 交换
[this.queue[index], this.queue[selectedChild]] = [this.queue[selectedChild], this.queue[index]];
index = selectedChild;
left = 2 * index + 1;
selectedChild = this.compare(left, left + 1) > 0 ? left + 1 : left;
}
return ret;
}
PriorityQueue.prototype.size = function() {
return this.queue.length;
}
// 使用传入的 compareFn 比较两个位置的元素
PriorityQueue.prototype.compare = function(index1, index2) {
if (this.queue[index1] === undefined) {
return 1;
}
if (this.queue[index2] === undefined) {
return -1;
}
return this.compareFn(this.queue[index1], this.queue[index2]);
}
```
@ -196,4 +364,4 @@ Go
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

View File

@ -14,7 +14,7 @@
## 349. 两个数组的交集
https://leetcode-cn.com/problems/intersection-of-two-arrays/
[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays/)
题意:给定两个数组,编写一个函数来计算它们的交集。
@ -32,7 +32,7 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/
这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)
但是要注意,**使用数组来做哈希的题目,是因为题目都限制了数值的大小。**
@ -54,7 +54,7 @@ std::set和std::multiset底层实现都是红黑树std::unordered_set的底
C++代码如下:
```C++
```CPP
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
@ -121,13 +121,7 @@ Python
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
result_set = set()
set1 = set(nums1)
for num in nums2:
if num in set1:
result_set.add(num) # set1里出现的nums2元素 存放到结果
return result_set
return list(set(nums1) & set(nums2)) # 两个数组先变成集合,求交集后还原为数组
```
@ -149,6 +143,26 @@ func intersection(nums1 []int, nums2 []int) []int {
return res
}
```
```golang
//优化版利用set减少count统计
func intersection(nums1 []int, nums2 []int) []int {
set:=make(map[int]struct{},0)
res:=make([]int,0)
for _,v:=range nums1{
if _,ok:=set[v];!ok{
set[v]=struct{}{}
}
}
for _,v:=range nums2{
//如果存在于上一个数组中则加入结果集并清空该set值
if _,ok:=set[v];ok{
res=append(res,v)
delete(set, v)
}
}
return res
}
```
javaScript:
@ -178,7 +192,71 @@ var intersection = function(nums1, nums2) {
};
```
Swift
```swift
func intersection(_ nums1: [Int], _ nums2: [Int]) -> [Int] {
var set1 = Set<Int>()
var set2 = Set<Int>()
for num in nums1 {
set1.insert(num)
}
for num in nums2 {
if set1.contains(num) {
set2.insert(num)
}
}
return Array(set2)
}
```
PHP:
```php
class Solution {
/**
* @param Integer[] $nums1
* @param Integer[] $nums2
* @return Integer[]
*/
function intersection($nums1, $nums2) {
if (count($nums1) == 0 || count($nums2) == 0) {
return [];
}
$counts = [];
$res = [];
foreach ($nums1 as $num) {
$counts[$num] = 1;
}
foreach ($nums2 as $num) {
if (isset($counts[$num])) {
$res[] = $num;
}
unset($counts[$num]);
}
return $res;
}
}
```
Rust:
```rust
use std::collections::HashSet;
impl Solution {
pub fn intersection(nums1: Vec<i32>, nums2: Vec<i32>) -> Vec<i32> {
let mut resultSet: HashSet<i32> = HashSet::with_capacity(1000);
let nums1Set: HashSet<i32> = nums1.into_iter().collect();
for num in nums2.iter() {
if nums1Set.contains(num) {
resultSet.insert(*num);
}
}
let ret: Vec<i32> = resultSet.into_iter().collect();
ret
}
}
```
## 相关题目
* 350.两个数组的交集 II
@ -188,4 +266,4 @@ var intersection = function(nums1, nums2) {
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>

Some files were not shown because too many files have changed in this diff Show More