Merge branch 'youngyangyang04:master' into master

This commit is contained in:
xsduan98
2021-07-27 10:14:57 +08:00
committed by GitHub
35 changed files with 1386 additions and 186 deletions

View File

@ -71,7 +71,7 @@
**这里每一篇题解,都是精品,值得仔细琢磨**
我在题目讲解中统一用C++语言但你会发现下面几乎每篇题解都配有其他语言版本Java、Python、Go、JavaScript等等正是热心小伙们的贡献的代码,当然我也会严格把控代码质量。
我在题目讲解中统一使用C++但你会发现下面几乎每篇题解都配有其他语言版本Java、Python、Go、JavaScript等等正是这些[热心小伙们](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)的贡献的代码,当然我也会严格把控代码质量。
**所以也欢迎大家参与进来,完善题解的各个语言版本,拥抱开源,让更多小伙伴们收益**
@ -133,6 +133,13 @@
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)
## 杂谈
[大半年过去了......](https://mp.weixin.qq.com/s/lubfeistPxBLSQIe5XYg5g)
## 数组
@ -161,14 +168,15 @@
1. [关于哈希表,你该了解这些!](./problems/哈希表理论基础.md)
2. [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](./problems/0242.有效的字母异位词.md)
3. [哈希表:哈希值太大了还是得用set](./problems/0349.两个数组的交集.md)
4. [哈希表用set来判断快乐数](./problems/0202.快乐数.md)
5. [哈希表:map等候多时了](./problems/0001.两数之和.md)
6. [哈希表:其实需要哈希的地方都能找到map的身影](./problems/0454.四数相加II.md)
7. [哈希表:这道题目我做过?](./problems/0383.赎金信.md)
8. [哈希表:解决了两数之和,那么能解决三数之和么](./problems/0015.三数之和.md)
9. [双指针法:一样的道理,能解决数之和](./problems/0018.四数之和.md)
10. [哈希表:总结篇!(每逢总结必经典)](./problems/哈希表总结.md)
3. [哈希表:查找常用字符](./problems/1002.查找常用字符.md)
4. [哈希表:哈希值太大了,还是得用set](./problems/0349.两个数组的交集.md)
5. [哈希表:用set来判断快乐数](./problems/0202.快乐数.md)
6. [哈希表:map等候多时了](./problems/0001.两数之和.md)
7. [哈希表:其实需要哈希的地方都能找到map的身影](./problems/0454.四数相加II.md)
8. [哈希表:这道题目我做过](./problems/0383.赎金信.md)
9. [哈希表:解决了两数之和,那么能解决数之和么?](./problems/0015.三数之和.md)
10. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md)
11. [哈希表:总结篇!(每逢总结必经典)](./problems/哈希表总结.md)
## 字符串
@ -444,7 +452,7 @@
# 贡献者
你可以[点此链接](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)查看LeetCode-Master的所有贡献者。感谢们补充了LeetCode-Master的其他语言版本让更多的读者收益于此项目。
[点此这里](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)查看LeetCode-Master的所有贡献者。感谢们补充了LeetCode-Master的其他语言版本让更多的读者收益于此项目。
# 关于作者
@ -459,7 +467,7 @@
# 公众号
更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:666可以获得所有算法专题原创PDF。
更多精彩文章持续更新微信搜索「代码随想录」第一时间围观关注后回复666可以获得我的所有算法专题原创PDF。
**「代码随想录」每天准时为你推送一篇经典面试题目,帮你梳理算法知识体系,轻松学习算法!**,并且公众号里有大量学习资源,也有我自己的学习心得和方法总结,更有上万录友们在这里打卡学习。

View File

@ -651,7 +651,7 @@ class Solution {
}
```
Python
Python3
```python
// 方法一

View File

@ -1,4 +1,13 @@
<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>
> 这个图就是大厂面试经典题目,接雨水! 最常青藤的一道题,面试官百出不厌!
# 42. 接雨水
@ -355,3 +364,38 @@ public:
## 其他语言版本
Java:
Python:
双指针法
```python3
class Solution:
def trap(self, height: List[int]) -> int:
res = 0
for i in range(len(height)):
if i == 0 or i == len(height)-1: continue
lHight = height[i-1]
rHight = height[i+1]
for j in range(i-1):
if height[j] > lHight:
lHight = height[j]
for k in range(i+2,len(height)):
if height[k] > rHight:
rHight = height[k]
res1 = min(lHight,rHight) - height[i]
if res1 > 0:
res += res1
return res
```
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>

View File

@ -148,8 +148,6 @@ class Solution {
// 定义中间位置
int mid = n / 2;
while (loop > 0) {
int i = startX;
int j = startY;
@ -182,7 +180,6 @@ class Solution {
offset += 2;
}
if (n % 2 == 1) {
res[mid][mid] = count;
}

View File

@ -7,7 +7,7 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 101. 对称二叉树
# 101. 对称二叉树
题目地址https://leetcode-cn.com/problems/symmetric-tree/
@ -15,7 +15,7 @@
![101. 对称二叉树](https://img-blog.csdnimg.cn/20210203144607387.png)
## 思路
# 思路
**首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!**
@ -73,7 +73,7 @@ bool compare(TreeNode* left, TreeNode* right)
此时左右节点不为空,且数值也不相同的情况我们也处理了。
代码如下:
```
```C++
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
@ -84,7 +84,7 @@ else if (left->val != right->val) return false; // 注意这里我没有
3. 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。
* 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
@ -93,7 +93,7 @@ else if (left->val != right->val) return false; // 注意这里我没有
代码如下:
```
```C++
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++
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
@ -137,7 +137,7 @@ public:
**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。**
当然我可以把如上代码整理如下:
```
```C++
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
@ -177,7 +177,7 @@ public:
代码如下:
```
```C++
class Solution {
public:
bool isSymmetric(TreeNode* root) {
@ -212,7 +212,7 @@ public:
只要把队列原封不动的改成栈就可以了,我下面也给出了代码。
```
```C++
class Solution {
public:
bool isSymmetric(TreeNode* root) {
@ -239,7 +239,7 @@ public:
};
```
## 总结
# 总结
这次我们又深度剖析了一道二叉树的“简单题”大家会发现真正的把题目搞清楚其实并不简单leetcode上accept了和真正掌握了还是有距离的。
@ -249,11 +249,14 @@ public:
如果已经做过这道题目的同学,读完文章可以再去看看这道题目,思考一下,会有不一样的发现!
# 相关题目推荐
* 100.相同的树
* 572.另一个树的子树
## 其他语言版本
# 其他语言版本
Java
## Java
```Java
/**
@ -358,9 +361,9 @@ Java
```
Python
## Python
> 递归法
递归法:
```python
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
@ -384,7 +387,7 @@ class Solution:
return isSame
```
> 迭代法: 使用队列
迭代法: 使用队列
```python
import collections
class Solution:
@ -410,7 +413,7 @@ class Solution:
return True
```
> 迭代法:使用栈
迭代法:使用栈
```python
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
@ -433,7 +436,7 @@ class Solution:
return True
```
Go
## Go
```go
/**
@ -484,22 +487,7 @@ func isSymmetric(root *TreeNode) bool {
```
JavaScript
```javascript
var isSymmetric = function(root) {
return check(root, root)
};
const check = (leftPtr, rightPtr) => {
// 如果只有根节点返回true
if (!leftPtr && !rightPtr) return true
// 如果左右节点只存在一个则返回false
if (!leftPtr || !rightPtr) return false
return leftPtr.val === rightPtr.val && check(leftPtr.left, rightPtr.right) && check(leftPtr.right, rightPtr.left)
}
```
JavaScript:
## JavaScript
递归判断是否为对称二叉树:
```javascript
@ -526,6 +514,7 @@ var isSymmetric = function(root) {
return compareNode(root.left,root.right);
};
```
队列实现迭代判断是否为对称二叉树:
```javascript
var isSymmetric = function(root) {
@ -554,6 +543,7 @@ var isSymmetric = function(root) {
return true;
};
```
栈实现迭代判断是否为对称二叉树:
```javascript
var isSymmetric = function(root) {

View File

@ -34,9 +34,10 @@
我们之前讲过了三篇关于二叉树的深度优先遍历的文章:
* [二叉树:前中后序递归法](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
* [二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)
* [二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)
* [二叉树:前中后序递归法](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)
* [二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)
* [二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)
接下来我们再来介绍二叉树的另一种遍历方式:层序遍历。

View File

@ -30,9 +30,13 @@
### 递归法
本题其实也要后序遍历(左右中),依然是因为要通过递归函数的返回值做计算树的高度。
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
按照递归三部曲,来看看如何来写
**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度
这一点其实是很多同学没有想清楚的,很多题解同样没有讲清楚。
我先用后序遍历(左右中)来计算树的高度。
1. 确定递归函数的参数和返回值参数就是传入树的根节点返回就返回这棵树的深度所以返回值为int类型。
@ -92,6 +96,66 @@ public:
**精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。**
本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**)
```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) { // 左
depth++; // 深度+1
getDepth(node->left, depth);
depth--; // 回溯,深度-1
}
if (node->right) { // 右
depth++; // 深度+1
getDepth(node->right, depth);
depth--; // 回溯,深度-1
}
return ;
}
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);
return result;
}
};
```
### 迭代法
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。

View File

@ -188,7 +188,40 @@ class Solution:
```
Go
```golang
//贪心算法
func maxProfit(prices []int) int {
var sum int
for i := 1; i < len(prices); i++ {
// 累加每次大于0的交易
if prices[i]-prices[i-1] > 0 {
sum += prices[i]-prices[i-1]
}
}
return sum
}
```
```golang
//确定售卖点
func maxProfit(prices []int) int {
var result,buy int
prices=append(prices,0)//在price末尾加个0防止price一直递增
/**
思路:检查后一个元素是否大于当前元素,如果小于,则表明这是一个售卖点,当前元素的值减去购买时候的值
如果不小于,说明后面有更好的售卖点,
**/
for i:=0;i<len(prices)-1;i++{
if prices[i]>prices[i+1]{
result+=prices[i]-prices[buy]
buy=i+1
}else if prices[buy]>prices[i]{//更改最低购买点
buy=i
}
}
return result
}
```
Javascript:
```Javascript

View File

@ -0,0 +1,179 @@
## 链接
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) 做了。
结合112.路径总和 和 113.路径总和II我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。
接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,我详细讲解了二叉树的递归中,如何使用了回溯。
接下来我们来看题:
首先思路很明确,就是要遍历整个树把更节点到叶子节点组成的数字相加。
那么先按递归三部曲来分析:
### 递归三部曲
如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)
* 确定递归函数返回值及其参数
这里我们要遍历整个二叉树且需要要返回值做逻辑处理所有返回值为void在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中,详细讲解了返回值问题。
参数只需要把根节点传入此时还需要定义两个全局遍历一个是result记录最终结果一个是vector<int> path。
**为什么用vector类型就是数组 因为用vector方便我们做回溯**
所以代码如下:
```
int result;
vector<int> path;
void traversal(TreeNode* cur)
```
* 确定终止条件
递归什么时候终止呢?
当然是遇到叶子节点此时要收集结果了通知返回本层递归因为单条路径的结果使用vector我们需要一个函数vectorToInt把vector转成int。
终止条件代码如下:
```
if (!cur->left && !cur->right) { // 遇到了叶子节点
result += vectorToInt(path);
return;
}
```
这里vectorToInt函数就是把数组转成int代码如下
```C++
int vectorToInt(const vector<int>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
sum = sum * 10 + vec[i];
}
return sum;
}
```
* 确定递归单层逻辑
本题其实采用前中后序都不无所谓, 因为也没有中间几点的处理逻辑。
这里主要是当左节点不为空path收集路径并递归左孩子右节点同理。
**但别忘了回溯**。
如图:
<img src='https://code-thinking.cdn.bcebos.com/pics/129.求根到叶子节点数字之和.png' width=600> </img></div>
代码如下:
```C++
// 中
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
traversal(cur->left); // 递归
path.pop_back(); // 回溯
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
traversal(cur->right); // 递归
path.pop_back(); // 回溯
}
```
这里要注意回溯和递归要永远在一起,一个递归,对应一个回溯,是一对一的关系,有的同学写成如下代码:
```C++
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
traversal(cur->left); // 递归
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
traversal(cur->right); // 递归
}
path.pop_back(); // 回溯
```
**把回溯放在花括号外面了,世界上最遥远的距离,是你在花括号里,而我在花括号外!** 这就不对了。
### 整体C++代码
关键逻辑分析完了整体C++代码如下:
```C++
class Solution {
private:
int result;
vector<int> path;
// 把vector转化为int
int vectorToInt(const vector<int>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
sum = sum * 10 + vec[i];
}
return sum;
}
void traversal(TreeNode* cur) {
if (!cur->left && !cur->right) { // 遇到了叶子节点
result += vectorToInt(path);
return;
}
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val); // 处理节点
traversal(cur->left); // 递归
path.pop_back(); // 回溯,撤销
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val); // 处理节点
traversal(cur->right); // 递归
path.pop_back(); // 回溯,撤销
}
return ;
}
public:
int sumNumbers(TreeNode* root) {
path.clear();
if (root == nullptr) return 0;
path.push_back(root->val);
traversal(root);
return result;
}
};
```
# 总结
过于简洁的代码,很容易让初学者忽视了本题中回溯的精髓,甚至作者本身都没有想清楚自己用了回溯。
**我这里提供的代码把整个回溯过程充分体现出来,希望可以帮助大家看的明明白白!**
## 其他语言版本
Java
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=../pics/公众号.png width=450 alt=> </img></div>

View File

@ -300,8 +300,9 @@ class Solution {
}
```
python:
```Python3
```Python
class Solution:
#1.去除多余的空格
def trim_spaces(self,s):
@ -349,7 +350,7 @@ class Solution:
return ''.join(l) #输出blue is sky the
'''
```
Go
@ -407,6 +408,65 @@ func reverse(b *[]byte, left, right int) {
javaScript:
```js
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function(s) {
// 字符串转数组
const strArr = Array.from(s);
// 移除多余空格
removeExtraSpaces(strArr);
// 翻转
reverse(strArr, 0, strArr.length - 1);
let start = 0;
for(let i = 0; i <= strArr.length; i++) {
if (strArr[i] === ' ' || i === strArr.length) {
// 翻转单词
reverse(strArr, start, i - 1);
start = i + 1;
}
}
return strArr.join('');
};
// 删除多余空格
function removeExtraSpaces(strArr) {
let slowIndex = 0;
let fastIndex = 0;
while(fastIndex < strArr.length) {
// 移除开始位置和重复的空格
if (strArr[fastIndex] === ' ' && (fastIndex === 0 || strArr[fastIndex - 1] === ' ')) {
fastIndex++;
} else {
strArr[slowIndex++] = strArr[fastIndex++];
}
}
// 移除末尾空格
strArr.length = strArr[slowIndex - 1] === ' ' ? slowIndex - 1 : slowIndex;
}
// 翻转从 start 到 end 的字符
function reverse(strArr, start, end) {
let left = start;
let right = end;
while(left < right) {
// 交换
[strArr[left], strArr[right]] = [strArr[right], strArr[left]];
left++;
right--;
}
}
```

View File

@ -200,14 +200,6 @@ class MyStack {
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
```
使用两个 Deque 实现
```java
@ -350,12 +342,6 @@ class MyStack:
return False
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
```
Go

View File

@ -7,7 +7,7 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
## 226.翻转二叉树
# 226.翻转二叉树
题目地址https://leetcode-cn.com/problems/invert-binary-tree/
@ -17,7 +17,7 @@
这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell就是因为没在白板上写出翻转二叉树最后被Google拒绝了。真假不做判断权当一个乐子哈
## 题外话
# 题外话
这道题目是非常经典的题目,也是比较简单的题目(至少一看就会)。
@ -25,7 +25,7 @@
如果做过这道题的同学也建议认真看完,相信一定有所收获!
## 思路
# 思路
我们之前介绍的都是各种方式遍历二叉树,这次要翻转了,感觉还是有点懵逼。
@ -49,7 +49,9 @@
## 递归法
对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)详细讲解了。
对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)详细讲解了。
我们下文以前序遍历为例,通过动画来看一下翻转的过程:
@ -104,7 +106,8 @@ public:
### 深度优先遍历
[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码:
[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码:
C++代码迭代法(前序遍历)
@ -126,10 +129,10 @@ public:
}
};
```
如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)。
如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)。
我们在[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
我们在[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
C++代码如下迭代法(前序遍历)
@ -159,7 +162,7 @@ public:
};
```
如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。
如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)。
### 广度优先遍历
@ -185,7 +188,7 @@ public:
}
};
```
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)
## 总结
@ -202,7 +205,7 @@ public:
## 其他语言版本
Java
### Java
```Java
//DFS递归
@ -254,9 +257,9 @@ class Solution {
}
```
Python
### Python
> 递归法:前序遍历
递归法:前序遍历
```python
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
@ -268,7 +271,7 @@ class Solution:
return root
```
> 迭代法:深度优先遍历(前序遍历)
迭代法:深度优先遍历(前序遍历)
```python
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
@ -286,7 +289,7 @@ class Solution:
return root
```
> 迭代法:广度优先遍历(层序遍历)
迭代法:广度优先遍历(层序遍历)
```python
import collections
class Solution:
@ -306,7 +309,8 @@ class Solution:
return root
```
Go
### Go
```Go
func invertTree(root *TreeNode) *TreeNode {
if root ==nil{
@ -323,7 +327,7 @@ func invertTree(root *TreeNode) *TreeNode {
}
```
JavaScript:
### JavaScript
使用递归版本的前序遍历
```javascript

View File

@ -385,15 +385,6 @@ func (this *MyQueue) Empty() bool {
return len(this.stack) == 0 && len(this.back) == 0
}
/**
* Your MyQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Peek();
* param_4 := obj.Empty();
*/
```
javaScript:

View File

@ -273,8 +273,8 @@ class Solution {
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意i为nums下标是要在[i - k + 1, k] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, k]范围内,不符合则要弹出
// 根据题意i为nums下标是要在[i - k + 1, i] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}

View File

@ -151,7 +151,24 @@ class Solution:
```
Go
```golang
func wiggleMaxLength(nums []int) int {
var count,preDiff,curDiff int
count=1
if len(nums)<2{
return count
}
for i:=0;i<len(nums)-1;i++{
curDiff=nums[i+1]-nums[i]
//如果有正有负则更新下标值||或者只有前一个元素为0针对两个不等元素的序列也视作摆动序列且摆动长度为2
if (curDiff > 0 && preDiff <= 0) || (preDiff >= 0 && curDiff < 0){
preDiff=curDiff
count++
}
}
return count
}
```
Javascript:
```Javascript

View File

@ -146,6 +146,23 @@ class Solution:
return res
```
Go
```golang
//排序后,局部最优
func findContentChildren(g []int, s []int) int {
sort.Ints(g)
sort.Ints(s)
// 从小到大
child := 0
for sIdx := 0; child < len(g) && sIdx < len(s); sIdx++ {
if s[sIdx] >= g[child] {//如果饼干的大小大于或等于孩子的为空则给与,否则不给予,继续寻找选一个饼干是否符合
child++
}
}
return child
}
Javascript:
```Javascript

View File

@ -37,7 +37,7 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/
如果KMP还不够了解可以看我的B站
* [帮你把KMP算法学个通透B站(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd/)
* [帮你把KMP算法学个通透理论篇](https://www.bilibili.com/video/BV1PD4y1o7nd/)
* [帮你把KMP算法学个通透求next数组代码篇](https://www.bilibili.com/video/BV1M5411j7Xx)

View File

@ -0,0 +1,98 @@
## 题目链接
https://leetcode-cn.com/problems/island-perimeter/
## 思路
岛屿问题最容易让人想到BFS或者DFS但是这道题还真的没有必要别把简单问题搞复杂了。
### 解法一:
遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了。
如图:
<img src='https://code-thinking.cdn.bcebos.com/pics/463.岛屿的周长.png' width=600> </img></div>
C++代码如下:(详细注释)
```C++
class Solution {
public:
int direction[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int islandPerimeter(vector<vector<int>>& grid) {
int result = 0;
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[0].size(); j++) {
if (grid[i][j] == 1) {
for (int k = 0; k < 4; k++) { // 上下左右四个方向
int x = i + direction[k][0];
int y = j + direction[k][1]; // 计算周边坐标x,y
if (x < 0 // i在边界上
|| x >= grid.size() // i在边界上
|| y < 0 // j在边界上
|| y >= grid[0].size() // j在边界上
|| grid[x][y] == 0) { // x,y位置是水域
result++;
}
}
}
}
}
return result;
}
};
```
### 解法二:
计算出总的岛屿数量因为有一对相邻两个陆地边的总数就减2那么在计算出相邻岛屿的数量就可以了。
result = 岛屿数量 * 4 - cover * 2;
如图:
<img src='https://code-thinking.cdn.bcebos.com/pics/463.岛屿的周长1.png' width=600> </img></div>
C++代码如下:(详细注释)
```C++
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
int sum = 0; // 陆地数量
int cover = 0; // 相邻数量
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[0].size(); j++) {
if (grid[i][j] == 1) {
sum++;
// 统计上边相邻陆地
if(i - 1 >= 0 && grid[i - 1][j] == 1) cover++;
// 统计左边相邻陆地
if(j - 1 >= 0 && grid[i][j - 1] == 1) cover++;
// 为什么没统计下边和右边? 因为避免重复计算
}
}
}
return sum * 4 - cover * 2;
}
};
```
## 其他语言版本
Java
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=../pics/公众号.png width=450 alt=> </img></div>

View File

@ -139,7 +139,7 @@ public:
## 相关题目推荐
* [35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)
* [35.搜索插入位置](./0035.搜索插入位置.md)
* 34.在排序数组中查找元素的第一个和最后一个位置
* 69.x 的平方根
* 367.有效的完全平方数

View File

@ -28,9 +28,9 @@ https://leetcode-cn.com/problems/design-linked-list/
# 思路
如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)
如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)
如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)
如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)
删除链表节点:
![链表-删除节点](https://img-blog.csdnimg.cn/20200806195114541.png)

View File

@ -0,0 +1,43 @@
## 题目链接
https://leetcode-cn.com/problems/valid-mountain-array/
## 思路
判断是山峰,主要就是要严格的保存左边到中间,和右边到中间是递增的。
这样可以使用两个指针left和right让其按照如下规则移动如图
<img src='https://code-thinking.cdn.bcebos.com/pics/941.有效的山脉数组.png' width=600> </img></div>
**注意这里还是有一些细节,例如如下两点:**
* 因为left和right是数组下表移动的过程中注意不要数组越界
* 如果left或者right没有移动说明是一个单调递增或者递减的数组依然不是山峰
C++代码如下:
```
class Solution {
public:
bool validMountainArray(vector<int>& A) {
if (A.size() < 3) return false;
int left = 0;
int right = A.size() - 1;
// 注意防止越界
while (left < A.size() - 1 && A[left] < A[left + 1]) left++;
// 注意防止越界
while (right > 0 && A[right] < A[right - 1]) right--;
// 如果left或者right都在起始位置说明不是山峰
if (left == right && left != 0 && right != A.size() - 1) return true;
return false;
}
};
```
如果想系统学一学双指针的话, 可以看一下这篇[双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)

View File

@ -0,0 +1,177 @@
<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>
# 1002. 查找常用字符
https://leetcode-cn.com/problems/find-common-characters/
给定仅有小写字母组成的字符串数组 A返回列表中的每个字符串中都显示的全部字符包括重复字符组成的列表。例如如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。
你可以按任意顺序返回答案。
【示例一】
输入:["bella","label","roller"]
输出:["e","l","l"]
【示例二】
输入:["cool","lock","cook"]
输出:["c","o"]
 
# 思路
这道题意一起就有点绕不是那么容易懂其实就是26个小写字符中有字符 在所有字符串里都出现的话,就输出,重复的也算。
例如:
输入:["ll","ll","ll"]
输出:["l","l"]
这道题目一眼看上去,就是用哈希法,**“小写字符”,“出现频率”, 这些关键字都是为哈希法量身定做的啊**
首先可以想到的是暴力解法一个字符串一个字符串去搜时间复杂度是O(n^m)n是字符串长度m是有几个字符串。
可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。
那我们还是哈希法吧。如果对哈希法不了解,可以看这篇:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。
如果对用数组来做哈希法不了解的话,可以看这篇:[把数组当做哈希表来用,很巧妙!](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)。
了解了哈希法,理解了数组在哈希法中的应用之后,可以来看解题思路了。
整体思路就是统计出搜索字符串里26个字符的出现的频率然后取每个字符频率最小值最后转成输出格式就可以了。
如图:
![1002.查找常用字符](https://code-thinking.cdn.bcebos.com/pics/1002.查找常用字符.png)
先统计第一个字符串所有字符出现的次数,代码如下:
```
int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
hash[A[0][i] - 'a']++;
}
```
接下来把其他字符串里字符的出现次数也统计出来一次放在hashOtherStr中。
然后hash 和 hashOtherStr 取最小值,这是本题关键所在,此时取最小值,就是 一个字符在所有字符串里出现的最小次数了。
代码如下:
```
int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.size(); i++) {
memset(hashOtherStr, 0, 26 * sizeof(int));
for (int j = 0; j < A[i].size(); j++) {
hashOtherStr[A[i][j] - 'a']++;
}
// 这是关键所在
for (int k = 0; k < 26; k++) { // 更新hash保证hash里统计26个字符在所有字符串里出现的最小次数
hash[k] = min(hash[k], hashOtherStr[k]);
}
}
```
此时hash里统计着字符在所有字符串里出现的最小次数那么把hash转正题目要求的输出格式就可以了。
代码如下:
```
// 将hash统计的字符次数转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while多个重复的字符
string s(1, i + 'a'); // char -> string
result.push_back(s);
hash[i]--;
}
}
```
整体C++代码如下:
```C++
class Solution {
public:
vector<string> commonChars(vector<string>& A) {
vector<string> result;
if (A.size() == 0) return result;
int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
hash[A[0][i] - 'a']++;
}
int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.size(); i++) {
memset(hashOtherStr, 0, 26 * sizeof(int));
for (int j = 0; j < A[i].size(); j++) {
hashOtherStr[A[i][j] - 'a']++;
}
// 更新hash保证hash里统计26个字符在所有字符串里出现的最小次数
for (int k = 0; k < 26; k++) {
hash[k] = min(hash[k], hashOtherStr[k]);
}
}
// 将hash统计的字符次数转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while多个重复的字符
string s(1, i + 'a'); // char -> string
result.push_back(s);
hash[i]--;
}
}
return result;
}
};
```
## 其他语言版本
Java
```Java
class Solution {
public List<String> commonChars(String[] A) {
List<String> result = new ArrayList<>();
if (A.length == 0) return result;
int[] hash= new int[26]; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].length(); i++) { // 用第一个字符串给hash初始化
hash[A[0].charAt(i)- 'a']++;
}
// 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.length; i++) {
int[] hashOtherStr= new int[26];
for (int j = 0; j < A[i].length(); j++) {
hashOtherStr[A[i].charAt(j)- 'a']++;
}
// 更新hash保证hash里统计26个字符在所有字符串里出现的最小次数
for (int k = 0; k < 26; k++) {
hash[k] = Math.min(hash[k], hashOtherStr[k]);
}
}
// 将hash统计的字符次数转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while多个重复的字符
char c= (char) (i+'a');
result.add(String.valueOf(c));
hash[i]--;
}
}
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>

View File

@ -0,0 +1,89 @@
## 题目链接
https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/
## 思路
这道题其实是考察如何计算一个数的二进制中1的数量。
我提供两种方法:
* 方法一:
朴实无华挨个计算1的数量最多就是循环n的二进制位数32位。
```C++
int bitCount(int n) {
int count = 0; // 计数器
while (n > 0) {
if((n & 1) == 1) count++; // 当前位是1count++
n >>= 1 ; // n向右移位
}
return count;
}
```
* 方法二
这种方法只循环n的二进制中1的个数次比方法一高效的多
```C++
int bitCount(int n) {
int count = 0;
while (n) {
n &= (n - 1); // 清除最低位的1
count++;
}
return count;
}
```
以计算12的二进制1的数量为例如图所示
<img src='https://code-thinking.cdn.bcebos.com/pics/1356.根据数字二进制下1的数目排序.png' width=600> </img></div>
下面我就使用方法二,来做这道题目:
## C++代码
```C++
class Solution {
private:
static int bitCount(int n) { // 计算n的二进制中1的数量
int count = 0;
while(n) {
n &= (n -1); // 清除最低位的1
count++;
}
return count;
}
static bool cmp(int a, int b) {
int bitA = bitCount(a);
int bitB = bitCount(b);
if (bitA == bitB) return a < b; // 如果bit中1数量相同比较数值大小
return bitA < bitB; // 否则比较bit中1数量大小
}
public:
vector<int> sortByBits(vector<int>& arr) {
sort(arr.begin(), arr.end(), cmp);
return arr;
}
};
```
## 其他语言版本
Java
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=../pics/公众号.png width=450 alt=> </img></div>

View File

@ -0,0 +1,146 @@
# 1365.有多少小于当前数字的数字
题目链接https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/
给你一个数组 nums对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。
换而言之对于每个 nums[i] 你必须计算出有效的 j 的数量其中 j 满足 j != i 且 nums[j] < nums[i] 
以数组形式返回答案
 
示例 1
* 输入nums = [8,1,2,2,3]
* 输出[4,0,1,1,3]
* 解释
对于 nums[0]=8 存在四个比它小的数字122 3)。
对于 nums[1]=1 不存在比它小的数字
对于 nums[2]=2 存在一个比它小的数字1)。
对于 nums[3]=2 存在一个比它小的数字1)。
对于 nums[4]=3 存在三个比它小的数字12 2)。
示例 2
* 输入nums = [6,5,4,8]
* 输出[2,1,0,3]
示例 3
* 输入nums = [7,7,7,7]
* 输出[0,0,0,0]
 
提示
* 2 <= nums.length <= 500
* 0 <= nums[i] <= 100
# 思路
两层for循环暴力查找时间复杂度明显为O(n^2)。
那么我们来看一下如何优化
首先要找小于当前数字的数字那么从小到大排序之后该数字之前的数字就都是比它小的了
所以可以定义一个新数组将数组排个序
**排序之后,其实每一个数值的下标就代表这前面有几个比它小的了**
代码如下
```
vector<int> vec = nums;
sort(vec.begin(), vec.end()); // 从小到大排序之后,元素下标就是小于当前数字的数字
```
用一个哈希表hash本题可以就用一个数组来做数值和下标的映射这样就可以通过数值快速知道下标也就是前面有几个比它小的)。
此时有一个情况就是数值相同怎么办
例如数组1 2 3 4 4 4 第一个数值4的下标是3第二个数值4的下标是4了
这里就需要一个技巧了**在构造数组hash的时候从后向前遍历这样hash里存放的就是相同元素最左面的数值和下标了**。
代码如下
```C++
int hash[101];
for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对应的下标
hash[vec[i]] = i;
}
```
最后在遍历原数组nums用hash快速找到每一个数值 对应的 小于这个数值的个数。存放在将结果存放在另一个数组中。
代码如下:
```C++
// 此时hash里保存的每一个元素数值 对应的 小于这个数值的个数
for (int i = 0; i < nums.size(); i++) {
vec[i] = hash[nums[i]];
}
```
流程如图:
<img src='https://code-thinking.cdn.bcebos.com/pics/1365.有多少小于当前数字的数字.png' width=600> </img></div>
关键地方讲完了整体C++代码如下:
```C++
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> vec = nums;
sort(vec.begin(), vec.end()); // 从小到大排序之后,元素下标就是小于当前数字的数字
int hash[101];
for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对应的下标
hash[vec[i]] = i;
}
// 此时hash里保存的每一个元素数值 对应的 小于这个数值的个数
for (int i = 0; i < nums.size(); i++) {
vec[i] = hash[nums[i]];
}
return vec;
}
};
```
可以排序之后加哈希时间复杂度为O(nlogn)
# 其他语言版本
Java
```Java
public int[] smallerNumbersThanCurrent(int[] nums) {
Map<Integer, Integer> map = new HashMap<>(); // 记录数字 nums[i] 有多少个比它小的数字
int[] res = Arrays.copyOf(nums, nums.length);
Arrays.sort(res);
for (int i = 0; i < res.length; i++) {
if (!map.containsKey(res[i])) { // 遇到了相同的数字,那么不需要更新该 number 的情况
map.put(res[i], i);
}
}
for (int i = 0; i < nums.length; i++) {
res[i] = map.get(nums[i]);
}
return res;
}
```
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=../pics/公众号.png width=450 alt=> </img></div>

View File

@ -206,7 +206,13 @@ public class TreeNode {
Python
```python
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
```
Go
```

View File

@ -12,9 +12,9 @@
> 统一写法是一种什么感觉
此时我们在[二叉树一入递归深似海从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)中用递归的方式,实现了二叉树前中后序的遍历。
此时我们在[二叉树一入递归深似海从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)中用递归的方式,实现了二叉树前中后序的遍历。
在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中用栈实现了二叉树前后中序的迭代遍历(非递归)。
在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中用栈实现了二叉树前后中序的迭代遍历(非递归)。
之后我们发现**迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。**
@ -24,7 +24,7 @@
**重头戏来了,接下来介绍一下统一写法。**
我们以中序遍历为例,在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中提到说使用栈的话,**无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况**。
我们以中序遍历为例,在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中提到说使用栈的话,**无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况**。
**那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。**
@ -149,14 +149,13 @@ public:
## 其他语言版本
# 其他语言版本
Java
迭代法前序遍历代码如下:
```java
class Solution {
迭代法前序遍历代码如下:
```java
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
@ -180,39 +179,39 @@ Java
return result;
}
}
```
```
迭代法中序遍历代码如下:
```java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
迭代法中序遍历代码如下:
```java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
return result;
}
return result;
}
```
迭代法后序遍历代码如下:
```java
class Solution {
}
```
迭代法后序遍历代码如下:
```java
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
@ -236,11 +235,11 @@ Java
return result;
}
}
```
```
Python
> 迭代法前序遍历
迭代法前序遍历:
```python
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
@ -263,7 +262,7 @@ class Solution:
return result
```
> 迭代法中序遍历
迭代法中序遍历
```python
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
@ -288,7 +287,7 @@ class Solution:
return result
```
> 迭代法后序遍历
迭代法后序遍历
```python
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:

View File

@ -19,7 +19,7 @@
为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢?
我们在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中提到了,**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
我们在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中提到了,**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了。
@ -138,7 +138,7 @@ public:
```
## 总结
# 总结
此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不想递归写法那样代码稍做调整,就可以实现前后中序。
@ -153,7 +153,7 @@ public:
## 其他语言版本
# 其他语言版本
Java
@ -233,7 +233,8 @@ class Solution {
Python
```python3
```python
# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:

View File

@ -7,12 +7,19 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
相信每一位录友都接触过时间复杂度,但又对时间复杂度的认识处于一种朦胧的状态,所以是时候对时间复杂度来一个深度的剖析了。
Carl大胆断言这可能是你见过对时间复杂度分析最通透的一篇文章了。
本篇从如下六点进行分析:
相信每一位录友都接触过时间复杂度「代码随想录」已经也讲了一百多道经典题目了是时候对时间复杂度来一个深度的剖析了很早之前就写过一篇当时文章还没有人看Carl感觉有价值的东西值得让更多的人看到哈哈。
* 究竟什么是时间复杂度
* 什么是大O
* 不同数据规模的差异
* 复杂表达式的化简
* O(logn)中的log是以什么为底
* 举一个例子
所以重新整理的时间复杂度文章,正式和大家见面啦!
这可能是你见过对时间复杂度分析最通透的一篇文章。
## 究竟什么是时间复杂度
@ -151,7 +158,7 @@ O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),所以说最后省略掉常数项系
**当然这不是这道题目的最优解,我仅仅是用这道题目来讲解一下时间复杂度**
# 总结
## 总结
本篇讲解了什么是时间复杂度复杂度是用来干什么以及数据规模对时间复杂度的影响
@ -161,12 +168,6 @@ O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),所以说最后省略掉常数项系
相信看完本篇大家对时间复杂度的认识会深刻很多
如果感觉代码随想录很不错赶快推荐给身边的朋友同学们吧他们发现和代码随想录相见恨晚
-----------------------
* 作者微信[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)

View File

@ -10,7 +10,6 @@
# 程序员的简历应该这么写!!(附简历模板)
> Carl多年积累的简历技巧都在这里了
Carl校招社招都拿过大厂的offer同时也看过很多应聘者的简历这里把自己总结的简历技巧以及常见问题给大家梳理一下。

View File

@ -245,6 +245,45 @@ class Solution(object):
```
javaScript:
```js
/**
* @param {string} s
* @return {string}
*/
var replaceSpace = function(s) {
// 字符串转为数组
const strArr = Array.from(s);
let count = 0;
// 计算空格数量
for(let i = 0; i < strArr.length; i++) {
if (strArr[i] === ' ') {
count++;
}
}
let left = strArr.length - 1;
let right = strArr.length + count * 2 - 1;
while(left >= 0) {
if (strArr[left] === ' ') {
strArr[right--] = '0';
strArr[right--] = '2';
strArr[right--] = '%';
left--;
} else {
strArr[right--] = strArr[left--];
}
}
// 数组转字符串
return strArr.join('');
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)

View File

@ -118,6 +118,8 @@ class Solution {
}
```
python:
```python
# 方法一:可以使用切片方法
class Solution:

View File

@ -13,7 +13,7 @@
对于二叉树节点的定义C++代码如下:
```
```C++
struct TreeNode {
int val;
TreeNode *left;
@ -35,7 +35,7 @@ TreeNode* a = new TreeNode(9);
没有构造函数的话就要这么写:
```
```C++
TreeNode* a = new TreeNode();
a->val = 9;
a->left = NULL;
@ -60,11 +60,35 @@ morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以
在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中我们开始用栈来实现递归的写法,也就是所谓的迭代法。
细心的同学发现文中前后序遍历空节点是入栈的,其实空节点入不入栈都差不多,但感觉空节点不入栈确实清晰一些,符合文中动画的演示。
细心的同学发现文中前后序遍历空节点是否入栈写法是不同的
前序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别)
其实空节点不入栈都差不多,但感觉空节点不入栈确实清晰一些,符合文中动画的演示。
拿前序遍历来举例,空节点入栈:
```C++
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
if (node != NULL) result.push_back(node->val);
else continue;
st.push(node->right); // 右
st.push(node->left); // 左
}
return result;
}
};
```
前序遍历空节点不入栈的代码:(注意注释部分和上文的区别)
```C++
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
@ -82,32 +106,8 @@ public:
return result;
}
};
```
后序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别)
```
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
```
在实现迭代法的过程中,有同学问了:递归与迭代究竟谁优谁劣呢?

View File

@ -0,0 +1,209 @@
# 本周小结!(二叉树)
**周日我做一个针对本周的打卡留言疑问以及在刷题群里的讨论内容做一下梳理吧。**,这样也有助于大家补一补本周的内容,消化消化。
**注意这个周末总结和系列总结还是不一样的(二叉树还远没有结束),这个总结是针对留言疑问以及刷题群里讨论内容的归纳。**
1. [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA)
2. [二叉树一入递归深似海从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)
3. [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)
4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)
5. [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)
6. [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/jG0MgYR9DoUMYcRRF7magw)
## [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA)
有同学会把红黑树和二叉平衡搜索树弄分开了其实红黑树就是一种二叉平衡搜索树这两个树不是独立的所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树再具体一点是红黑树。
对于二叉树节点的定义C++代码如下:
```C++
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
```
对于这个定义中`TreeNode(int x) : val(x), left(NULL), right(NULL) {}` 有同学不清楚干什么的。
这是构造函数这么说吧C语言中的结构体是C++中类的祖先所以C++结构体也可以有构造函数。
构造函数也可以不写但是new一个新的节点的时候就比较麻烦。
例如有构造函数定义初始值为9的节点
```
TreeNode* a = new TreeNode(9);
```
没有构造函数的话就要这么写:
```C++
TreeNode* a = new TreeNode();
a->val = 9;
a->left = NULL;
a->right = NULL;
```
在介绍前中后序遍历的时候有递归和迭代非递归还有一种牛逼的遍历方式morris遍历。
morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以将非递归遍历中的空间复杂度降为O(1),感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
## [二叉树的递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)
在[二叉树一入递归深似海从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)中讲到了递归三要素,以及前中后序的递归写法。
文章中我给出了leetcode上三道二叉树的前中后序题目但是看完[二叉树一入递归深似海从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)依然可以解决n叉树的前后序遍历在leetcode上分别是
* 589. N叉树的前序遍历
* 590. N叉树的后序遍历
大家可以再去把这两道题目做了。
## [二叉树的非递归遍历](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)
细心的同学发现文中前后序遍历空节点是入栈的,其实空节点入不入栈都差不多,但感觉空节点不入栈确实清晰一些,符合文中动画的演示。
前序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别)
```C++
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
result.push_back(node->val);
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return result;
}
};
```
后序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别)
```C++
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
```
在实现迭代法的过程中,有同学问了:递归与迭代究竟谁优谁劣呢?
从时间复杂度上其实迭代法和递归法差不多(在不考虑函数调用开销和函数调用产生的堆栈开销),但是空间复杂度上,递归开销会大一些,因为递归需要系统堆栈存参数返回值等等。
递归更容易让程序员理解,但收敛不好,容易栈溢出。
这么说吧,递归是方便了程序员,难为了机器(各种保存参数,各种进栈出栈)。
**在实际项目开发的过程中我们是要尽量避免递归!因为项目代码参数、调用关系都比较复杂,不容易控制递归深度,甚至会栈溢出。**
## 周四
在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中我们使用空节点作为标记,给出了统一的前中后序迭代法。
此时又多了一种前中后序的迭代写法,那么有同学问了:前中后序迭代法是不是一定要统一来写,这样才算是规范。
其实没必要,还是自己感觉哪一种更好记就用哪种。
但是**一定要掌握前中后序一种迭代的写法,并不因为某种场景的题目一定要用迭代,而是现场面试的时候,面试官看你顺畅的写出了递归,一般会进一步考察能不能写出相应的迭代。**
## 周五
在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。
看完这篇文章去leetcode上怒刷五题文章中 编号107题目的样例图放错了原谅我匆忙之间总是手抖但不影响大家理解。
只有同学发现leetcode上“515. 在每个树行中找最大值”,也是层序遍历的应用,依然可以分分钟解决,所以就是一鼓作气解决六道了,哈哈。
**层序遍历遍历相对容易一些,只要掌握基本写法(也就是框架模板),剩下的就是在二叉树每一行遍历的时候做做逻辑修改。**
## 周六
在[二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获!
**文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。**
如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
```
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
invertTree(root->left); // 左
swap(root->left, root->right); // 中
invertTree(root->left); // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了
return root;
}
};
```
代码虽然可以,但这毕竟不是真正的递归中序遍历了。
但使用迭代方式统一写法的中序是可以的。
代码如下:
```
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
if (node->right) st.push(node->right); // 右
st.push(node); // 中
st.push(NULL);
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
swap(node->left, node->right); // 节点处理逻辑
}
}
return root;
}
};
```
为什么这个中序就是可以的呢,因为这是用栈来遍历,而不是靠指针来遍历,避免了递归法中翻转了两次的情况,大家可以画图理解一下,这里有点意思的。
## 总结
**本周我们都是讲解了二叉树,从理论基础到遍历方式,从递归到迭代,从深度遍历到广度遍历,最后再用了一个翻转二叉树的题目把我们之前讲过的遍历方式都串了起来。**

View File

@ -190,7 +190,7 @@ for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。
dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正左和正上两个方向),那么先遍历物品,再遍历背包的过程如图所示:
dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正方向),那么先遍历物品,再遍历背包的过程如图所示:
![动态规划-背包问题5](https://img-blog.csdnimg.cn/202101101032124.png)

View File

@ -80,7 +80,7 @@ dp[j]表示容量为j的背包所背的物品价值可以最大为dp[j]
看一下递归公式dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
dp数组在推导的时候一定是取价值最大的数如果题目给的价值都是正整数那么非0下标都初始化为0就可以了如果题目给的价值有负数那么非0下标就要初始化为负无穷
dp数组在推导的时候一定是取价值最大的数如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。
**这样才能让dp数组在递归公式的过程中取的最大的价值而不是被初始值覆盖了**