搞定大厂算法面试之 leetcode 精讲 4. 贪心
搞定大厂算法面试之 leetcode 精讲 4.贪心
视频教程(高效学习):点击学习
目录:
什么是贪心算法
贪心法,又称贪心算法,贪婪算法,在对问题求解时,总是做出在当前看来最好的选择,期望通过每个阶段的局部最优选择达到全局最优,但结果不一定最优
适用场景:简单的说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,就能用贪心算法的到最后的最优解,这种子问题最优解称为最优子结构
贪心算法与动态规划的不同点在于它对每个子问题的解决方案都做出当前的最优选择,不能回退,而动态规划会保留之前的运算结果,并根据之前的结果进行选择,有回退的功能,贪心是动态规划的理想化的情况。
122. 买卖股票的最佳时机 II(medium)
方法 1.动态规划
思路:根据题意只能持有一只股票,不限制交易次数,我们可以用动态规划来做,首先定义状态,题中有两个状态,一个是天数,一个是是否持有股票,所以我们定义
dp[i][0]
表示第 i 天交易完后手里没有股票的最大利润,dp[i][1]
表示第 i 天交易完后手里持有一支股票的最大利润,接下来就是定义状态转移方程:假如当前的状态是
dp[i][0]
,表示手中没股票,则可由前一天的两种情况转移过来,第一种是dp[i-1][0]
,表示前一天手里没股票,而且今天没做任何操作。第二种是dp[i-1][1]
,表示前一天持有股票,但是今天卖了,所以收益是dp[i-1][1]+prices[i]
,我们需要求出这两种情况下的最大值就是最大利润,状态转移方程就是:dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
假如当前的状态是
dp[i][1]
,表示手中有股票,则可由前一天的两种情况转移过来,第一种是dp[i−1][1]
,表示前一天手中有股票,即是今天没做任何操作。第二种是dp[i−1][0]
,表示前一天没有股票,但是今天买进了,所以收益是dp[i-1][1]-prices[i]
,我们需要求出这两种情况下的最大值就是最大利润,状态转移方程就是:dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
由上面的状态转移方程我们知道,当前天的最大收益,只与前一天的状态相关,所以我们可以不用定义二维数组来存放状态,只需要将
dp[i - 1][0]
,dp[i - 1][1]
存放在变量中。复杂度分析:时间复杂度:
O(n)
,n 是数组长度,每天有持有股票或者没持有两种状态,一共 2n 的状态转移次数,时间复杂度就是O(2n)
,时间复杂度和常系数无关,所以时间复杂度就是O(n)
。空间复杂度O(n)
,因为要开辟 n 的空间存放状态,虽然是二维数组,但是第二维是常数。如果进行了状态压缩,空间复杂度可以优化到 O(1)
js:
Java:
方法 2.贪心
思路:因为不限制交易次数,只要今天价格比昨天高,就交易,利润为正累加,最后的和就是最大的利润,注意第一天是没有利润的,这道题之所以可以用贪心是因为局部最优:收集每天的正利润,可以推导出,全局最优:求得最大利润。
复杂度分析:时间复杂度
O(n)
,n 是数组的长度。空间复杂度是O(1)
js:
Java:
455. 分发饼干 (easy)
思路:大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。排序两个数组,从右到左遍历,用大饼干首先满足胃口大的小孩
复杂度:时间复杂度
O(mlogm + nlogn)
。空间复杂度O(logm + logn)
js:
java:
435. 无重叠区间 (medium)
方法 1.动态规划
思路:
dp[i]
表示前 i 个区间中最大不重合区间的个数,首先将区间数组按左边界排序,找出 intervals 中最多有多少个不重复的区间,动态规划方程dp[i] = Math.max(dp[i], dp[j] + 1)
。intervals 的长度减去最多的不重复的区间 就是最少删除区间的个数复杂度:时间复杂度
O(n^2)
,两层嵌套循环 leetcode 执行超时 复杂度过高。空间复杂度O(n)
,dp 数组的空间
js:
java:
方法 2.贪心
思路:intervals 按右边界排序,然后从左往右遍历,右边界结束的越早,留给后面的区间的空间就越大,不重合的区间个数就越多,intervals 的长度减去最多的不重复的区间 就是最少删除区间的个数
复杂度:时间复杂度
O(nlogn)
,数组排序O(nlogn)
,循环一次数组O(n)
。空间复杂度O(logn)
,排序需要的栈空间
js:
java:
能不能用贪心算法需要满足贪心选择性,贪心算法正确的的证明可以用反证法
以这一题为例:
我们的思路是保留最多的不重合的区间,所以按照区间结尾排序,区间结尾结束的越早且和前一个区间不重叠的,就加入最多不重复的区间中,我们称为算法 a,假如算法 a 中的某一个步骤是选择区间
[a, b]
,我们称为区间 A。假设这个选择不正确,也就是说算法 a 得到的不是最优解。
我们假设存在另一个算法 c 能得到最优解,算法 c 中的一个步骤是选择区间
[c, d]
,我们称为区间 C,使得它是最优解中的一个区间,其中d>b
,因为算法 a 选择的是结尾最先结束且不重合的区间,如果算法 a 不正确,又因为区间数组中的区间是固定的,则其他算法 c 肯定存在d>b
的情况。我们用区间 A 替换区间 C 完全不影响算法 c 的结果,因为
b<d
,所以不影响区间 C 后面区间的结果。所以我们选择了区间 A 也构成了一个最优解。而我们假设的是选择区间 A 不是最优解,所以和之前的假设矛盾,所以算法 a 是正确的贪心算法
55. 跳跃游戏 (medium)
方法 1.动态规划
思路:
dp[i]
表示能否到达位置 i,对每个位置 i 判断能否通过前面的位置跳跃过来,当前位置 j 能达到,并且当前位置 j 加上能到达的位置如果超过了 i,那dp[i]
更新为 ture,便是 i 位置也可以到达。复杂度:时间复杂度
O(n^2)
,空间复杂度O(n)
js:
java:
方法 2.贪心
思路:不用考虑每一步跳跃到那个位置,而是尽可能的跳跃到最远的位置,看最多能覆盖的位置,不断更新能覆盖的距离。
复杂度:时间复杂度
O(n)
,遍历一边。空间复杂度O(1)
js:
java:
881. 救生艇 (medium)
思路:题意是一条船只能坐 2 人,要求尽可能的用少的船装下这些人。所以可以用贪心策略。让更多的人组成 2 人组,而且这些 2 人组的两人重量加起来不超过船的载重。所以可以先排序 people,用双指针从两边向中间遍历,让重的人和轻的人组成 2 人组,如果当前最重的人和最轻的人的重量和超过了载重,那只能让重的人先乘一条船。
复杂度:时间复杂度
O(nlogn)
,排序的复杂度。空间复杂度O(logn)
,排序的栈空间
js:
java:
452. 用最少数量的箭引爆气球 (medium)
思路:区间按照结尾从小到大排序,循环数组,如果后面一个区间的开始大于前一个区间的结尾 就需要新增一支箭。
复杂度:时间复杂度
O(nlogn)
,排序的复杂度O(nlogn)
,循环数组的复杂度O(n)
。空间复杂度O(logn)
,排序栈空间
js:
java:
134. 加油站(medium)
思路:首先判断总油量是否小于总油耗,如果是则肯定不能走一圈。如果否,那肯定能跑一圈。接下来就是循环数组,从第一个站开始,计算每一站剩余的油量,如果油量为负了,就以这个站为起点从新计算。如果到达某一个点为负,说明起点到这个点中间的所有站点都不能到达该点。
复杂度:时间复杂度
O(n)
,空间复杂度O(1)
js:
java:
621. 任务调度器 (medium)
思路:先排个数最多的任务 A,在 A 的冷却时间内插入其他任务,先计算前 n-1 行 n 的间隔的时间大小,再计算和最大次数相同的字母个数,然后累加进 ret。最后在 tasks 的长度和 ret 中取较大的一个
复杂度:时间复杂度
O(n)
,空间复杂度O(1)
js:
java:
860. 柠檬水找零 (easy)
思路:优先找面额大的
复杂度:时间复杂度
O(n)
,空间复杂度O(1)
js:
java:
版权声明: 本文为 InfoQ 作者【全栈潇晨】的原创文章。
原文链接:【http://xie.infoq.cn/article/ac822f2d7d8cbf610f62adaaa】。文章转载请联系作者。
评论