前言
最近贪心算法苦恼很久,这个可恶的局部最优解,真是伤透我心啊。所以我决定好好的全面了解他,对自己在贪心这块不会再出现错误,没错,我就是这么贪🤩🤩。
贪心算法是什么
贪心算法(又称贪婪算法)是指:在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。(摘自百度百科)
划重点,不从整体最优上加以考虑,而是局部最优解。那么带来什么问题了呢?局部最优解和整体最优解的区别我们用两个小例子来说明一下:
第一个demo:
这有面值分别为1,5和11单位的硬币,希望找回总额为15单位的硬币,同时拿到的硬币数量最少。在贪心算法眼里:先拿一张最大的11,因为剩余的单位不足以支撑5单位,便需要再找4个1单位面值的硬币,共找回5个硬币。
但最优的解答应是3个5单位面值的硬币。
第二个demo:
咱们以leedcode的礼物的最大值为例:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
[
[1,3,1],
[1,5,1],
[4,2,1]
]
当时我第一眼便用了贪心,因为简单啊,一下子就想到了,下面我来说说我当时的思路:每次找到最大的那一条边,走到最后不就是最大了么,然后我欻欻地写了个递归,从第一个数到下一个数,哪个大到达哪个位置,但是我意识到一个问题,这样是到不了最后位置的;就像下图
那如果我给他设定最后位置在右下角,只能向下、右呢?
那这就不是最大值了,最大值肯定是有15的,所以这题只能用动态规划做。
借用一下K神的图
每个节点只与其上方、左方有关,从第一个行开始向右到底,算出一行最大值。接下来每一行看其左边和上边哪一个与自己相加为最大值,第2行第1列便是其上面1加上本身值,得到2,;接下来到第2列,左边2,4与自己相加得到最大值的是4…由此求得结果。
因此,我们可以用一句话来概括贪心算法:一个非常精明但是格局没有打开的算法。
贪心算法的应用
所以说贪心算法只有在某些特定的场景下才能使用。贪心的基本思路:从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到算法中的某一步不能再继续前进时,算法停止。
直接上题
738. 单调递增的数字
当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈单调递增 。
简单理解一下,把n拆分成字符
1、前一个字符要<=当前字符
2、同时要小于n。
很明显的贪心问题,用98为例,
n=98
输出=89
n = 1234321
输出 = 1233999
局部最优:由此可以看出不满足1条件的地方str[i-1]-=1,同时str[i]=9
即需要两个位置变换,前一个数不满足1条件便持续-1,后面的数在尽可能大的情况下要变换为9;只要找到不满足1的,就要替换为9。
1234321中4>3,然后4->3;3->9;123399
。
全局最优:得到小于等于N的最大单调递增的整数
class Solution:
def monotoneIncreasingDigits(self, n: int) -> int:
l = list(str(n))
#n=['1', '2', '3', '4', '3', '2', '1']
for i in range(len(l)-1,0,-1):
if int(l[i]) < int(l[i-1]):
l[i-1] = str(int(l[i-1]) - 1)
l[i:] = '9' * (len(l) - i)
return int("".join(l))
再来一个剑指offer中的题:
剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入: 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
这个题需要不断分解,
切分方案 | 乘积 | 结论 |
---|---|---|
2=1+1 | 1×1=1 | 2 不切分 |
3=1+2 | 1×2=2 | 3 不切分 |
4=2+2=1+3 | 2×2=4>1×3=3 | 4 和 2 等价,且左边更优(特例) |
5=2+3=1+4 | 2 × 3 = 6 > 1×2×2=4 | 5 应切分为 2+3更大 |
6=3+3=2+2+2 | 3×3=9>2×2×2=8 | 6 应切分为 3+3 ,进而推出 3 比 2 更优 |
7=3+2+2 | 3×2×2=12 | 可以转换为1-6的方案 |
8=2+2+2+2=3+3+2= | 16<18 | 可以转换为1-6的方案 |
… |
得出一个结论:大部分情况下3越多越好(疯狂贪3),需要推导公式看这
class Solution:
def cuttingRope(self, n: int) -> int:
if n <= 3: return n - 1
m, z = n // 3, n % 3
if z == 0: return int(math.pow(3, m))
if z == 1: return int(math.pow(3, m - 1) * 4)
return int(math.pow(3, m) * 2)
总结
贪心算法只有在特定情况下才可以使用,总结一下贪心的核心思想:当面临处境的时候只考虑当前情况下最优解,然后从局部去解决全局问题(前提是全局问题可以由局部问题解决)
先分析->判断是否可用贪心->while or 规律完成答题