This commit is contained in:
youngyangyang04
2020-12-22 15:42:02 +08:00
parent 1b906da06e
commit f881e3ec27
9 changed files with 245 additions and 56 deletions

View File

@ -59,11 +59,11 @@
* 求职
* [程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)
* [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw)
* [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)
* [北京有这些互联网公司,你都知道么?]()
* [北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/BKrjK4myNB-FYbMqW9f3yw)
* [上海有这些互联网公司,你都知道么?]()
* [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ)
* [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)
* [广州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Ir_hQP0clbnvHrWzDL-qXg)
* [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ)
* 算法性能分析
@ -217,6 +217,7 @@
* [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)
* [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g)
* [贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)
* [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)
* 动态规划

View File

@ -130,8 +130,6 @@ public:
所以使用vector动态数组来insert是费时的插入再拷贝的话单纯一个插入的操作就是O(n^2)了甚至可能拷贝好几次就不止O(n^2)了。
对于本题不用vector的话如果有多少人就申请多大的固定数组来模拟插入操作也可以这样就不会有扩充数组的情况但是就要真的手动模拟插入的操作了比较麻烦。
改成链表之后C++代码如下:
```C++

View File

@ -44,6 +44,11 @@
* 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
* 背包中每一个元素一定是不可重复放入。
**按照这四部在分析一波**
* 确定dp数组以及下标的含义
* 确定递推公式
* dp数组如何初始化
* 确定遍历顺序
定义里数组为dp[]dp[i] 表示 背包中放入体积为i的商品最大价值为dp[i]。

View File

@ -1,22 +1,70 @@
<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>
> 代码很简单,思路很高端
# 435. 无重叠区间
题目链接https://leetcode-cn.com/problems/non-overlapping-intervals/
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
## 思路
这道题目如果真的去模拟去重复区间的行为,是非常麻烦的,还要有删除区间。
**相信很多同学看到这道题目都冥冥之中感觉要排序,但是究竟是按照右边界排序,还是按照左边界排序呢?**
按照右边界排序,从左向右遍历,右边界越小越好,因为右边界越小,留给下一个区间的空间就越大,所以可以从左向右遍历,优先选右边界小的。
这其实是一个难点!
按照边界排序,那么就是从右向左遍历,左边界数值越大越好(越靠右),这样就给前一个区间的空间就越大,所以可以从右向左遍历
按照边界排序,就要从左向右遍历,因为右边界越小越好,只要右边界越小,留给下一个区间的空间就越大,所以从左向右遍历,优先选右边界小的
如果按照左边界排序,还从左向右遍历的话,要处理各个区间右边界的各种情况,就比较复杂了,这其实也就不是贪心了
按照左边界排序,就要从右向左遍历,因为左边界数值越大越好(越靠右),这样就给前一个区间的空间就越大,所以可以从右向左遍历
如果按照左边界排序,还从左向右遍历的话,要处理各个区间右边界的各种情况,就很复杂了。
一些同学做这道题目可能真的去模拟去重复区间的行为,这是比较麻烦的,还要去删除区间。
题目只是要求移除区间的个数,没有必要去真实的模拟删除区间!
**我来按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了**
此时问题就是要求非交叉区间的最大个数。
右边界排序之后,局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间大一些,从而尽量避免交叉。全局最优:选取最多的非交叉区间。
局部最优推出全局最优,试试贪心!
这里记录非交叉区间的个数还是有技巧的,如图:
<img src='../pics/435.无重叠区间.png' width=600> </img></div>
![435.无重叠区间](https://img-blog.csdnimg.cn/20201221201553618.png)
区间123456都按照右边界排好序。
@ -26,7 +74,7 @@
区间4结束之后在找到区间6所以一共记录非交叉区间的个数是三个。
总共区间个数为6减去非交叉区间的个数33。移除区间的最小数量就是3。
总共区间个数为6减去非交叉区间的个数3。移除区间的最小数量就是3。
C++代码如下:
@ -41,7 +89,7 @@ public:
if (intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int count = 1; // 记录非交叉区间的个数
int end = intervals[0][1];
int end = intervals[0][1]; // 记录区间分割点
for (int i = 1; i < intervals.size(); i++) {
if (end <= intervals[i][0]) {
end = intervals[i][1];
@ -52,6 +100,33 @@ public:
}
};
```
* 时间复杂度O(nlogn) ,有一个快排
* 空间复杂度O(1)
大家此时会发现如此复杂的一个问题,代码实现却这么简单!
# 总结
本题我认为难度级别可以算是hard级别的
总结如下难点:
* 难点一:一看题就有感觉需要排序,但究竟怎么排序,按左边界排还是右边界排。
* 难点二:排完序之后如何遍历,如果没有分析好遍历顺序,那么排序就没有意义了。
* 难点三:直接求重复的区间是复杂的,转而求最大非重复区间个数。
* 难点四:求最大非重复区间个数时,需要一个分割点来做标记。
**这四个难点都不好想,但任何一个没想到位,这道题就解不了**
一些录友可能看网上的题解代码很简单,照葫芦画瓢稀里糊涂的就过了,但是其题解可能并没有把问题难点讲清楚,然后自己再没有钻研的话,那么一道贪心经典区间问题就这么浪费掉了。
贪心就是这样,代码有时候很简单(不是指代码短,而是逻辑简单),但想法是真的难!
这和动态规划还不一样,动规的代码有个递推公式,可能就看不懂了,而贪心往往是直白的代码,但想法读不懂,哈哈。
**所以我把本题的难点也一一列出,帮大家不仅代码看的懂,想法也理解的透彻!**
循序渐进学算法,认准「代码随想录」就够了,值得介绍给身边的朋友同学们!
> 我是[程序员Carl](https://github.com/youngyangyang04),组队刷题可以找我,本文[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/20200815195519696.png),期待你的关注!

View File

@ -1,7 +1,44 @@
// 如何转化为01背包问题
# 思路
尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,**这样就化解成01背包问题了**。
只不过物品的重量为store[i]物品的价值也为store[i]。
对应着01背包里的物品重量weight[i]和 物品价值value[i]。
接下来进行动规四步曲:
* 确定dp数组以及下标的含义
我习惯直接使用一维dp数组如果习惯使用二维dp数组的同学可以看这篇
dp[j]表示容量这里说容量更形象其实就是重量为j的背包最多可以背dp[j]这么重的石头。
* 确定递推公式
dp[j]有两个来源方向一个是dp[j]自己一个是dp[j - stones[i]]。
那么dp[j]就是取最大的:**dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);**
一些同学可能看到这dp[j - stones[i]] + stones[i]中 又有- stones[i] 又有+stones[i],看着有点晕乎。
还是要牢记dp[j]的含义要知道dp[j - stones[i]]为 容量为j - stones[i]的背包最大所背重量。
* dp数组如何初始化
既然 dp[j]中的j表示容量那么最大容量重量是多少呢就是所有石头的重量和。
因为提示中给出1 <= stones.length <= 301 <= stones[i] <= 1000所以最大重量就是30 * 1000
如何初始化呢只要因为重量都不会是负数所以dp[j]都初始化为0就可以了这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。
* 确定遍历顺序
for循环遍历石头的数量嵌套一个for循环遍历背包容量且因为是01背包每一个物品只使用一次所以遍历背包容量的时候要倒序。
具体原因我在
尽量让石头分成,重量相同的两堆,这样就化解成 01背包问题了。
```
class Solution {
@ -12,13 +49,14 @@ public:
for (int i = 0; i < stones.size(); i++) sum += stones[i];
int target = sum / 2;
for (int i = 0; i < stones.size(); i++) {
for (int j = target; j >= 0; j--) {
if (j - stones[i] >= 0) {
dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
}
for (int j = target; j >= stones[i]; j--) {
dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - dp[target] - dp[target];
}
};
```
* 时间复杂度O(m * n) , m是石头总重量n为石头块数
* 空间复杂度O(m)

View File

@ -1,62 +1,88 @@
# 什么是动态规划
动态规划英文Dynamic Programming简称DP如果某一问题有很多重叠子问题使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,**这一点就区分于贪心**
所以动态规划中每一个状态一定是由上一个状态推导出来的,**这一点就区分于贪心**,贪心是局部直接选最优的,
在[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中我举了一个背包问题的例子。
例如有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i]得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。
动态规划中dp[j]是又dp[j-weight[i]]推导出来的。
但如果是贪心呢dp[j]每次选一个最大的或者最小的就完事了。
所以贪心解决不了动态规划的问题,这也是最大的区别。
很多讲解动态规划的文章都会讲最优子结构啊和重叠子问题啊这些,这些东西都是教科书的上定义,晦涩难懂而且不实用。
大家只要知道,动规是由前一个状态推导出来的,而贪心是局部直接算最优的,就够用了。
对于上述提到的背包问题,后序会详细讲解。
# 动态规划的解题步骤
题目的时候很多同学会陷入一个误区就是以为把状态转移公式背下来照葫芦画瓢改改就开始写代码甚至把题目AC之后都不太清楚dp[i]表示的是什么。
这就是一种朦胧的状态,然后就把题给过了,遇到稍稍难一点的,可能直接就不会了,然后看题解,然后继续照葫芦画瓢陷入这种恶性循环中。
关于状态转移公式
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下四步曲,这四步都搞清楚了,才能说把动态规划真的掌握了!
**对于动态规划问题,我将拆解为如下四步曲,这四步都搞清楚了,才能说把动态规划真的掌握了!**
* 确定dp数组以及下标的含义
* dp数组如何初始化
* 确定递推公式
* dp数组如何初始化
* 确定遍历顺序
一些同学可能想为什么要先确定递推公式,然后在考虑初始化呢?
因为一些情况是递推公式决定了dp数组要输入初始化
后面的讲解中我都是围绕着这四个点来经行讲解。
可能刷过动态规划题目的同学可能都知道递推公式的重要性,感觉确定了递推公式这道题目就解出来了。
其实 确定递推公式 仅仅是解题里的一步而且, dp数组的初始化 以及确定遍历顺序,都非常重要
其实 确定递推公式 仅仅是解题里的一步而且,dp数组以及下标的含义 dp数组的初始化 以及确定遍历顺序,都非常重要
**很多同学搞不清楚dp数组应该如何初始化或者遍历的顺序以至于记下来公式但写的程序怎么改都通过不了**
后序的讲解的大家就会发现其重要性
# 动态规划如何debug
很多同学甚至知道递推公式但搞不清楚dp数组应该如何初始化或者正确的遍历顺序以至于记下来公式但写的程序怎么改都通过不了。
# 动态规划应该如何debug
平时我自己写的时候也经常出问题,**找问题的最好方式就是把dp数组打印出来看看究竟是不是按照自己思路推导的**
一些同学对于dp的学习是上来就是俯视类型的总是想一下子全盘接纳一鼓作气写出代码如果代码能通过万事大吉通过不了的话就凭感觉改一改。
# 背包三讲
这是一个很不好的习惯!
背包九讲其实看起来还是有点费劲的,而且都是伪代码理解起来吃力
<img src='../pics/416.分割等和子集1.png' width=600> </img></div>
**做动规的题目写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍心中有数确定最后推出的是想要的结果**
然后在写代码如果代码没通过就打印dp数组看看是不是和自己推导的哪里不一样。
如果和自己模拟推导的一样,那么就是自己的递归公式有问题。
如果和自己模拟推导的不一样,那么就是代码实现细节有问题。
**这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了**
我来举一个例子:一些同学可能代码通过不了,都会把代码抛到出来问:我这里代码都已经和题解一模一样的,为什么通过不了呢?
发出这样的问题之前,其实可以自己先思考这三个问题:
* 这道题目我推导状态转移公式了么?
* 我打印dp数组的日志了么
* 打印出来了dp数组和我想的一样么
**如果这灵魂三问自己都做到了,基本上这道题目也就解决了**或者更清晰的知道自己究竟是哪一点不明白是状态转移不明白还是实现代码不知道该怎么写还是不理解遍历dp数组的顺序。
然后在问问题,目的性就很强了,回答问题的同学也可以快速知道提问者的疑惑了。
# 动态规划可以解决哪一类问题
# 完全背包
有N 种物品和一个容量为V 的背包每种物品都有无限件可用。放入第i种 物品的耗费的空间是Ci 得到的价值是Wi 。求解:将哪些物品装入背包,可使 这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
这个问题非常类似于01背包问题所不同的是每种物品有无限件
首先想想为什么01背包中要按照v递减的次序来 循环。让v递减是为了保证第i次循环中的状态F [i, v]是由状态F [i 1, v Ci]递 推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入 第i件物品”这件策略时依据的是一个绝无已经选入第i件物品的子结果F [i 1, v Ci]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加 选一件第i种物品”这种策略时却正需要一个可能已选入第i种物品的子结 果F [i, v Ci]所以就可以并且必须采用v递增的顺序循环。这就是这个简单的
程序为何成立的道理。
值得一提的是上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。(可能说的就是组合或者排列了)
# 多重背包
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用每件耗费 的空间是Ci 价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略 微一改即可。
# 总结
后台回复:背包九讲 就可以获得pdf

View File

@ -1,6 +1,6 @@
很多刚刚关注「代码随想录」的录友,表示想从头开始打卡学习。
**打卡方式**就是在文章留言区记录第n天打卡+自己的总结。很多录友都正在从头开始打卡,看看留言就知道了,你并不孤独,哈哈。
**打卡方式**就是在文章留言区记录第n天打卡或者第n次打卡+自己的总结。很多录友都正在从头开始打卡,看看留言就知道了,你并不孤独,哈哈。
**以下是我整理的文章列表每个系列都排好了顺序文章顺序即刷题顺序这是全网最详细的刷题顺序了所以录友们挨个看就OK**
@ -17,11 +17,13 @@
* 求职
* [程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)
* [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw)
* [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Yzrkim-5bY0Df66Ao-hoqA)
* [北京有这些互联网公司,你都知道么?]()
* [互联网大厂技术面试流程和注意事项](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw)
* [北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/BKrjK4myNB-FYbMqW9f3yw)
* [上海有这些互联网公司,你都知道么?]()
* [成都有这些互联网公司,你都知道么?]()
* [深圳有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)
* [广州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Ir_hQP0clbnvHrWzDL-qXg)
* [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ)
* 算法性能分析
* [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw)
@ -167,6 +169,14 @@
* [贪心算法K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)
* [本周小结!(贪心算法系列二)](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)
* [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)
* [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g)
* [贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)
* [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)
(持续更新.....

View File

@ -129,3 +129,6 @@ void test_1_wei_bag_problem() {
}
```
# 总结
我个人倾向于使用一维dp数组的写法比较直观简洁所以在后面背包问题的讲解中我都直接使用一维dp数组来进行推导。

View File

@ -0,0 +1,33 @@
# 背包问题
背包九讲其实看起来还是有点费劲的,而且都是伪代码理解起来吃力
# 完全背包
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]得到的价值是value[i] 。**每件物品都有无限个(也就是可以放入背包多次)**,求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是每种物品有无限件。
**终点讲解两个for顺序的问题完全背包就可以换顺序了因为不需要从后向前遍历了**
程序为何成立的道理。
值得一提的是上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。(可能说的就是组合或者排列了)
# 多重背包
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用每件耗费 的空间是Ci 价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略 微一改即可。
# 总结
<img src='../pics/416.分割等和子集1.png' width=600> </img></div>
# 总结
后台回复:背包九讲 就可以获得pdf