This commit is contained in:
youngyangyang04
2020-12-17 15:42:21 +08:00
parent f2c905d3b1
commit ac17ab9f16
14 changed files with 449 additions and 53 deletions

View File

@ -213,6 +213,7 @@
* [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ)
* [贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)
* [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)
* [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)
* 动态规划
@ -375,6 +376,7 @@
|[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |二叉搜索树 |简单|**递归** **迭代**|
|[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**|
|[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**|
|[0714.买卖股票的最佳时机含手续费](https://github.com/youngyangyang04/leetcode/blob/master/problems/0714.买卖股票的最佳时机含手续费.md) |贪心 动态规划 |中等|**贪心** **动态规划** 和122.买卖股票的最佳时机II类似贪心的思路很巧妙|
|[0763.划分字母区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0763.划分字母区间.md) |贪心 |中等|**双指针/贪心** 体现贪心尽可能多的思想|
|[0738.单调递增的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/0738.单调递增的数字.md) |贪心算法 |中等|**贪心算法** 思路不错,贪心好题|
|[0739.每日温度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0739.每日温度.md) |栈 |中等|**单调栈** 适合单调栈入门|
@ -388,7 +390,7 @@
|[0973.最接近原点的K个点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0973.最接近原点的K个点.md) |优先级队列 |中等|**优先级队列**|
|[0977.有序数组的平方](https://github.com/youngyangyang04/leetcode/blob/master/problems/0977.有序数组的平方.md) |数组 |中等|**双指针** 还是比较巧妙的|
|[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**|
|[1005.K次取反后最大化的数组和](https://github.com/youngyangyang04/leetcode/blob/master/problems/1005.K次取反后最大化的数组和.md) |贪心/排序 |**贪心算法** 贪心基础题目|
|[1005.K次取反后最大化的数组和](https://github.com/youngyangyang04/leetcode/blob/master/problems/1005.K次取反后最大化的数组和.md) |贪心/排序 |简单|**贪心算法** 贪心基础题目|
|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**|
|[1049.最后一块石头的重量II](https://github.com/youngyangyang04/leetcode/blob/master/problems/1049.最后一块石头的重量II.md) |动态规划 |中等|**01背包**|
|[1207.独一无二的出现次数](https://github.com/youngyangyang04/leetcode/blob/master/problems/1207.独一无二的出现次数.md) |哈希表 |简单|**哈希** 两层哈希|

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -1,13 +1,21 @@
> 笔者在BAT从事技术研发多年利用工作之余重刷leetcode更多原创文章请关注公众号「代码随想录」。
# 题目地址
https://leetcode-cn.com/problems/remove-element/
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201215214102642.png" width=400 >
</p>
<p align="center">
<a href="https://github.com/youngyangyang04/leetcode-master"><img src="https://img.shields.io/badge/Github-leetcode--master-lightgrey" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201115103410182.png"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://www.zhihu.com/people/sun-xiu-yang-64"><img src="https://img.shields.io/badge/知乎-代码随想录-blue" alt=""></a>
<a href="https://www.toutiao.com/c/user/60356270818/#mid=1633692776932365"><img src="https://img.shields.io/badge/头条-代码随想录-red" alt=""></a>
</p>
> 移除元素想要高效的话,不是很简单!
# 编号27. 移除元素
题目地址https://leetcode-cn.com/problems/remove-element/
给你一个数组 nums 和一个值 val你需要 原地 移除所有数值等于 val 的元素并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并**原地**修改输入数组。
@ -112,7 +120,8 @@ public:
};
```
拓展: 也可以一个指向前面一个指向后面遇到需要删除的就交换最后返回指针的位置加1只不过这么做更改了数组元素的位置了不算是移除元素。
> 更多算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。
**循序渐进学算法认准「代码随想录」Carl手把手带你过关斩将**
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201216002707465.jpg" width=200 >
</p>

View File

@ -44,7 +44,7 @@
这道题目,要在数组中插入目标值,无非是这四种情况。
<img src='../pics/35_搜索插入位置3.png' width=600> </img></div>
![35_搜索插入位置3](https://img-blog.csdnimg.cn/20201216232148471.png)
* 目标值在数组所有元素之前
* 目标值等于数组中某一个元素
@ -85,13 +85,13 @@ public:
效率如下:
<img src='../pics/35_搜索插入位置.png' width=600> </img></div>
![35_搜索插入位置](https://img-blog.csdnimg.cn/20201216232127268.png)
## 二分法
既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
<img src='../pics/35_搜索插入位置4.png' width=600> </img></div>
![35_搜索插入位置4](https://img-blog.csdnimg.cn/202012162326354.png)
大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。
@ -101,7 +101,7 @@ public:
大体讲解一下二分法的思路这里来举一个例子例如在这个数组中使用二分法寻找元素为5的位置并返回其下标。
<img src='../pics/35_搜索插入位置5.png' width=600> </img></div>
![35_搜索插入位置5](https://img-blog.csdnimg.cn/20201216232659199.png)
二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。
@ -151,7 +151,7 @@ public:
时间复杂度O(1)
效率如下:
<img src='../pics/35_搜索插入位置2.png' width=600> </img></div>
![35_搜索插入位置2](https://img-blog.csdnimg.cn/2020121623272877.png)
## 二分法第二种写法

View File

@ -1,12 +1,22 @@
> 笔者在BAT从事技术研发多年利用工作之余重刷leetcode更多原创文章请关注公众号「代码随想录」。
# 题目地址
https://leetcode-cn.com/problems/spiral-matrix-ii/
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201215214102642.png" width=400 >
</p>
<p align="center">
<a href="https://github.com/youngyangyang04/leetcode-master"><img src="https://img.shields.io/badge/Github-leetcode--master-lightgrey" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201115103410182.png"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://www.zhihu.com/people/sun-xiu-yang-64"><img src="https://img.shields.io/badge/知乎-代码随想录-blue" alt=""></a>
<a href="https://www.toutiao.com/c/user/60356270818/#mid=1633692776932365"><img src="https://img.shields.io/badge/头条-代码随想录-red" alt=""></a>
</p>
> 一进循环深似海从此offer是路人
# 题目59.螺旋矩阵II
题目地址https://leetcode-cn.com/problems/spiral-matrix-ii/
给定一个正整数 n生成一个包含 1 到 n2 所有元素且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
@ -48,7 +58,7 @@ https://leetcode-cn.com/problems/spiral-matrix-ii/
那么我按照左闭右开的原则,来画一圈,大家看一下:
<img src='../pics/螺旋矩阵.png' width=600> </img></div>
![螺旋矩阵](https://img-blog.csdnimg.cn/2020121623550681.png)
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
@ -111,5 +121,9 @@ public:
}
};
```
> 更过算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。
**循序渐进学算法认准「代码随想录」Carl手把手带你过关斩将**
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201216002707465.jpg" width=200 >
</p>

View File

@ -1,7 +1,7 @@
## 题目地址
https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
> 给出两个序列
> 给出两个序列 可以加unorder_map优化一下
看完本文,可以一起解决如下两道题目

View File

@ -1,10 +1,21 @@
## 题目地址
https://leetcode-cn.com/problems/minimum-size-subarray-sum/
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201215214102642.png" width=400 >
</p>
<p align="center">
<a href="https://github.com/youngyangyang04/leetcode-master"><img src="https://img.shields.io/badge/Github-leetcode--master-lightgrey" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201115103410182.png"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://www.zhihu.com/people/sun-xiu-yang-64"><img src="https://img.shields.io/badge/知乎-代码随想录-blue" alt=""></a>
<a href="https://www.toutiao.com/c/user/60356270818/#mid=1633692776932365"><img src="https://img.shields.io/badge/头条-代码随想录-red" alt=""></a>
</p>
> 滑动窗口拯救了你
# 题目209.长度最小的子数组
题目链接: https://leetcode-cn.com/problems/minimum-size-subarray-sum/
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
@ -107,5 +118,9 @@ public:
时间复杂度O(n)
空间复杂度O(1)
> 更多算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。
**循序渐进学算法认准「代码随想录」Carl手把手带你过关斩将**
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201216002707465.jpg" width=200 >
</p>

View File

@ -1,49 +1,147 @@
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201215214102642.png" width=400 >
</p>
<p align="center">
<a href="https://github.com/youngyangyang04/leetcode-master"><img src="https://img.shields.io/badge/Github-leetcode--master-lightgrey" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201115103410182.png"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
<a href="https://www.zhihu.com/people/sun-xiu-yang-64"><img src="https://img.shields.io/badge/知乎-代码随想录-blue" alt=""></a>
<a href="https://www.toutiao.com/c/user/60356270818/#mid=1633692776932365"><img src="https://img.shields.io/badge/头条-代码随想录-red" alt=""></a>
</p>
## 思路
> 就不能好好站个队
# 406.根据身高重建队列
题目链接https://leetcode-cn.com/problems/queue-reconstruction-by-height/
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性queue[0] 是排在队列前面的人)。
示例 1
输入people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
示例 2
输入people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
提示:
* 1 <= people.length <= 2000
* 0 <= hi <= 10^6
* 0 <= ki < people.length
题目数据确保队列可以被重建
# 思路
本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后在按照另一个维度重新排列
其实如果大家认真做了[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)就会发现和此题有点点的像
[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次遇到两个维度权衡的时候一定要先确定一个维度再确定另一个维度
**如果两个维度一起考虑一定会顾此失彼**
相信大家困惑的点是先确k还是先确定h呢也就是 究竟先按h排序呢还先按照k排序呢
对于本题相信大家困惑的点是先确k还是先确定h呢也就是究竟先按h排序呢还先按照k排序呢
如果按照k来从小到大排序排完之后会发现k的排列并不符合条件身高也不符合条件两个维度哪一个都没确定下来
那么按照身高h来排序呢身高一定是从大到小排身高相同k小的站前面让高个子在前面
那么按照身高h来排序呢身高一定是从大到小排身高相同的话则k小的站前面让高个子在前面
**此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!**
此时只需要按照k为下重新插入队列就可以了呢,为什么呢?
那么只需要按照k为下重新插入队列就可以了为什么呢
以图中{5,2} 为例
<img src='../pics/406.根据身高重建队列.png' width=600> </img></div>
![406.根据身高重建队列](https://img-blog.csdnimg.cn/20201216201851982.png)
**都说用贪心算法,是贪心究竟贪在哪里呢?**
贪心在优先按身高高的people的k来插入后序插入节点也不会影响前面已经插入的节点最终按照k的规则完成了队列
按照身高排序之后优先按身高高的people的k来插入后序插入节点也不会影响前面已经插入的节点最终按照k的规则完成了队列
局部最优优先按照身高搞的people的k来插入。插入操作过后的people满足队列属性
所以在按照身高从大到小排序后
全局最优:最后都做完插入操作,整个队列满足题目队列属性
**局部最优优先按身高高的people的k来插入。插入操作过后的people满足队列属性**
局部最优可推出全局最优。
**全局最优:最后都做完插入操作,整个队列满足题目队列属性**
整个插入过程如下:
局部最优可推出全局最优找不出反例那就试试贪心
一些同学可能也会疑惑你怎么知道局部最优就可以推出全局最优呢 有数学证明么
在贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)我已经讲过了这个问题了
刷题或者面试的时候手动模拟一下感觉可以局部最优推出整体最优而且想不到反例那么就试一试贪心至于严格的数学证明就不在讨论范围内了
如果没有读过[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)的同学建议读一下相信对贪心就有初步的了解了
回归本题整个插入过程如下
排序完的people
[[7,0], [7,1], [6,1], [5,0], [5,2][4,4]]
排序完:
[[7,0], [7,1], [6,1], [5,0], [5,2][4,4]]
插入的过程
插入[7,0][[7,0]]
插入[7,1][[7,0],[7,1]]
插入[6,1][[7,0],[6,1],[7,1]]
插入[5,0][[5,0],[7,0],[6,1],[7,1]]
插入[5,2][[5,0],[7,0],[5,2],[6,1],[7,1]]
插入[4,4][[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
插入[7,0][[7,0]]
插入[7,1][[7,0],[7,1]]
插入[6,1][[7,0],[6,1],[7,1]]
插入[5,0][[5,0],[7,0],[6,1],[7,1]]
插入[5,2][[5,0],[7,0],[5,2],[6,1],[7,1]]
插入[4,4][[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
此时就按照题目的要求完成了重新排列
C++代码如下
```C++
// 版本一
class Solution {
public:
static bool cmp(const vector<int> a, const vector<int> b) {
if (a[0] == b[0]) return a[1] < b[1];
return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort (people.begin(), people.end(), cmp);
vector<vector<int>> que;
for (int i = 0; i < people.size(); i++) {
int position = people[i][1];
que.insert(que.begin() + position, people[i]);
}
return que;
}
};
```
* 时间复杂度O(nlogn + n^3)
* 空间复杂度O(n)
大家会发现这个n^3 是怎么来的?
其实数组的插入操作复杂度是O(n^2)寻找插入元素位置O(1)插入元素O(n^2),因为插入元素后面的元素要整体向后移。
如果对数组的增删时间复杂度不清楚的话,可以做做这道题目[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)数组中插入元素和删除元素都是O(n^2)的复杂度。
我们就是要模拟一个插入队列的行为,所以不应该使用数组,而是要使用链表!
链表的插入操作复杂度是O(n)寻找插入元素位置O(n)插入元素O(1)。
可以看出使用链表的插入效率要比普通数组高出一个数量级!
改成链表之后C++代码如下:
```C++
// 版本二
class Solution {
public:
// 身高从大到小排身高相同k小的站前面
@ -53,16 +151,51 @@ public:
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort (people.begin(), people.end(), cmp);
list<vector<int>> que; // 使用list底层是链表实现插入效率比vector高的多
list<vector<int>> que; // list底层是链表实现插入效率比vector高的多
for (int i = 0; i < people.size(); i++) {
int position = people[i][1]; // 插入到下标为position的位置
std::list<vector<int>>::iterator it = que.begin();
while (position--) {
it++;
while (position--) { // 寻找在插入位置
it++;
}
que.insert(it, people[i]);
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());
}
};
```
* 时间复杂度O(nlogn + n^2)
* 空间复杂度O(n)
大家可以把两个版本的代码提交一下试试就可以发现其差别了
# 总结
关于出现两个维度一起考虑的情况我们已经做过两道题目了另一道就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)
**其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**
这道题目可以说比[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少其贪心的策略也是比较巧妙
最后我给出了两个版本的代码可以明显看是使用C++中的list底层链表实现比vector数组效率高得多
**对使用某一种语言容器的使用,特性的选择都会不同程度上影响效率**
所以很多人都说写算法题用什么语言都可以主要体现在算法思维上其实我是同意的但也不同意
对于看别人题解的同学题解用什么语言其实影响不大只要题解把所使用语言特性优化的点讲出来大家都可以看懂并使用自己语言的时候注意一下
对于写题解的同学刷题用什么语言影响就非常大如果自己语言没有学好而强调算法和编程语言没关系其实是会误伤别人的
**这也是我为什么统一使用C++写题解的原因**其实用其他语言javapythonphpgo啥的我也能写我的Github上也有用这些语言写的小项目但写题解的话我就不能保证把语言特性这块讲清楚所以我始终坚持使用最熟悉的C++写题解
**而且我在写题解的时候涉及语言特性,一般都会后面加上括号说明一下。没办法,认真负责就是我,哈哈**
就酱,「代码随想录一直都是干货满满值得介绍给身边的朋友同学们
**循序渐进学算法认准「代码随想录」Carl手把手带你过关斩将**
<p align='center'>
<img src="https://img-blog.csdnimg.cn/20201216002707465.jpg" width=200 >
</p>

View File

@ -0,0 +1,102 @@
# 思路
本题相对于[贪心算法122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg),多添加了一个条件就是手续费。
## 贪心算法
在[贪心算法122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)中使用贪心策略不用关系具体什么时候买卖,只要收集每天的正利润,最后稳稳的就是最大利润了。
而本题有了手续费,就要关系什么时候买卖了,因为只计算所获得利润,可能不足以手续费。
如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。
此时无非就是要找到两个点,买入日期,和卖出日期。
* 买入日期:其实很好想,遇到更低点就记录一下。
* 卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+费用),就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天。
所以我们在做收获利润操作的时候其实有两种情况:
* 情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。
* 情况二:收获利润的这一天是收获利润区间里的最后一天(相当于真正的卖出了),后面要重新记录最小价格了。
贪心算法C++代码如下:
```C++
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int result = 0;
int minPrice = prices[0]; // 记录最低价格
for (int i = 1; i < prices.size(); i++) {
// 买入
if (prices[i] < minPrice) minPrice = prices[i]; // 情况二
// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
if (prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee;
minPrice = prices[i] - fee; // 情况一,这一步很关键
}
}
return result;
}
};
```
从代码中可以看出对情况一的操作,因为如果还在收获利润的区间里,表示并不是真正的卖出,而计算利润每次都要减去手续费,
**所以要让minPrice = prices[i] - fee;,这样在明天收获利润的时候,才不会多减一次手续费!**
理解这里很关键,其实也是核心所在,很多题解关于这块都没有说清楚。
## 动态规划
我在「代码随想录」公众号里正在讲解贪心算法将在下一个系列详细讲解动态规划所以本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。
相对于[贪心算法122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)的动态规划解法中,只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。
C++代码如下:
```C++
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
// dp[i][1]第i天持有的最多现金
// dp[i][0]第i天持有股票所剩的最多现金
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] -= prices[0]; // 持股票
for (int i = 1; i < n; i++) {
// 第i天持股票所剩最多现金 = max(第i-1天持股票所剩现金, 第i-1天持现金-买第i天的股票)
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
// 第i天持有最多现金 = max(第i-1天持有的最多现金第i-1天持有股票所剩的最多现金+第i天卖出股票-手续费)
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return max(dp[n - 1][0], dp[n - 1][1]);
}
};
```
当然可以对空间经行优化,因为当前状态只是依赖前一个状态。
C++ 代码如下:
```C++
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int holdStock = (-1) * prices[0]; // 持股票
int saleStock = 0; // 卖出股票
for (int i = 1; i < n; i++) {
int previousHoldStock = holdStock;
holdStock = max(holdStock, saleStock - prices[i]);
saleStock = max(saleStock, previousHoldStock + prices[i] - fee);
}
return saleStock;
}
};
```
细心的同学可能发现在计算saleStock的时候 使用的已经是最新的holdStock了理论上应该使用上一个状态的holdStock即i-1时候的holdstock但是

View File

@ -1,21 +1,78 @@
> 如果对贪心算法理论基础还不了解的话,可以看看这篇:[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) ,相信看完之后对贪心就有基本的了解了。
这道题目我们需要维护三种金额的数量510和20。
> 给我来一杯柠檬水
# 860.柠檬水找零
题目链接https://leetcode-cn.com/problems/lemonade-change/
在柠檬水摊上每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零返回 true 否则返回 false 
示例 1
输入:[5,5,5,10,20]
输出true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
示例 2
输入:[5,5,10]
输出true
示例 3
输入:[10,10]
输出false
示例 4
输入:[5,5,10,10,20]
输出false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。
提示:
* 0 <= bills.length <= 10000
* bills[i] 不是 5 就是 10 或是 20 
# 思路
这是前几天的leetcode每日一题感觉不错给大家讲一下。
这道题目刚一看,可能会有点懵,这要怎么找零才能保证完整全部账单的找零呢?
**但仔细一琢磨就会发现,可供我们做判断的空间非常少!**
只需要维护三种金额的数量510和20。
有如下三种情况:
* 情况一账单是5直接收下。
* 情况二账单是10消耗一个5增加一个10
* 情况三账单是20优先消耗一个10和一个5如果不够消耗三个5
* 情况三账单是20优先消耗一个10和一个5如果不够消耗三个5
这道题大家可能感觉纯模拟就可以了,其实这里有贪心,就在情况三
此时大家就发现 情况一,情况二,都是固定策略,都不用我们来做分析了,而唯一不确定的其实在情况三。
而情况三逻辑也不复杂甚至感觉纯模拟就可以了,其实情况三这里是有贪心的。
账单是20的情况为什么要优先消耗一个10和一个5呢
**因为美元10只能给账单20找零而美元5可以给账单10和账单20找零美元5更万能**
局部最优遇到账单20优先消耗美元10完成本次找零。全局最优完成全部账单的找零。
所以局部最优遇到账单20优先消耗美元10完成本次找零。全局最优完成全部账单的找零。
局部最优可以推出全局最优,并找不出反例,那么就试试贪心
局部最优可以推出全局最优,并找不出反例,那么就试试贪心算法!
C++代码如下:
@ -49,8 +106,19 @@ public:
return true;
}
};
```
# 总结
咋眼一看好像很复杂,分析清楚之后,会发现逻辑其实非常固定。
这道题目可以告诉大家,遇到感觉没有思路的题目,可以静下心来把能遇到的情况分析一下,只要分析到具体情况了,一下子就豁然开朗了。
如果一直陷入想从整体上寻找找零方案,就会把自己陷进去,各种情况一交叉,只会越想越复杂了。
就酱,如果感觉「代码随想录」干货满满,就帮忙宣传一波吧,感激不尽!
> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20201124161234338.png),关注后就会发现和「代码随想录」相见恨晚!**
**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**

View File

@ -1,4 +1,57 @@
# 1518.换酒问题
小区便利店正在促销,用 numExchange 个空酒瓶可以兑换一瓶新酒。你购入了 numBottles 瓶酒。
如果喝掉了酒瓶中的酒,那么酒瓶就会变成空的。
请你计算 最多 能喝到多少瓶酒。
![1518.换酒问题](https://img-blog.csdnimg.cn/20201215173958151.png)
输入numBottles = 9, numExchange = 3
输出13
解释:你可以用 3 个空酒瓶兑换 1 瓶酒。
所以最多能喝到 9 + 3 + 1 = 13 瓶酒。
![1518.换酒问题](https://img-blog.csdnimg.cn/20201215174130529.png)
输入numBottles = 15, numExchange = 4
输出19
解释:你可以用 4 个空酒瓶兑换 1 瓶酒。
所以最多能喝到 15 + 3 + 1 = 19 瓶酒。
示例 3
输入numBottles = 5, numExchange = 5
输出6
示例 4
输入numBottles = 2, numExchange = 3
输出2
提示:
* 1 <= numBottles <= 100
* 2 <= numExchange <= 100
# 思路
这道题目其实是很简单的了,简单到大家都不以为这是贪心算法,哈哈
来分析一下:
局部最优:每次换酒用尽可能多的酒瓶。全局最优:喝到最多的酒。
局部最优可以推出全局最优,那么这就是贪心!
本题其实
每次环境
其实思路是简单的,但本题在
```
// 这道题还是有陷阱啊15 4 这个例子答案应该是19 而不是18
class Solution {