LeetCode---贪心算法
贪心算法
参考公众号:代码随想录。
什么是贪心
贪心的本质是选择每个阶段的局部最优,从而达到全局最优。
例如,有堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?指定每次拿最大的,最终结果就是拿走最大数额的钱。每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
再例如:如果有一堆盒子,有一个背包体积为n,如何把背包尽可能装满,如果还每次选最大的盒子,就不行了,这时候就需要动态规划。
什么时候用贪心
贪心算法并没有固定的套路,所以难点就是如何通过局部最优,推出整体最优。
严重是否可以用贪心:最好的策略就是举反例,如果想不到反例,那么就试试贪心吧。
贪心的一般解题步骤
贪心算法一般分为四步:
- 将问题分解为若干个子问题;
- 找出适合的贪心策略;
- 求解每个子问题的最优解;
- 将局部最优解堆叠成全局最优解。
LeetCode
455. 分发饼干
思路
大尺寸的饼干既可以满足胃口小的孩子也可以满足胃口大的孩子,那么就应该优先满足胃口大的。
这个的局部最优就是将大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的孩子。
可以尝试使用贪心策略,先将饼干数组和孩子数组排序。
然后从后向前遍历孩子数组,大饼干优先满足胃口大的,并统计满足的孩子数量。
解法1
# 时间复杂度:主要是排序的时间复杂度O(mlogm+nlogn)
# 空间复杂度:O(logm+logn),其中 m 和 n 分别是数组 g 和 s 的长度。空间复杂度主要是排序的额外空间开销。
def findContentChildren(g, s):
# 1. 先对g和s升序排序
g.sort()
s.sort()
# 2. 因为孩子胃口和饼干都已经排过序,所以从小到大选择每个孩子能满足每个孩子胃口的最小饼干(贪心算法)
i = 0
j = 0
while i < len(g) and j < len(s):
if g[i] <= s[j]:
i += 1
j += 1
else:
j += 1
return i
if __name__ == '__main__':
g = [1, 2, 3]
s = [1, 1]
print(findContentChildren(g, s))
376. 摆动序列
思路
- 假如有2个数,计算出其差值,假设为正数;
- 假入有3个数,计算nums[2]-nums[1]差值,是否为负数,这样就可以达到局部最优;
- 如果不满足则计算下一组数字,忽略当前数字。
例如: nums= [10, 8, 6, 8], 第一组8-10=-2,第二组6-8=-2,第三组8-6=2,则存在[10,6,8]的子序列满足正负交替。注意:第一组数字[10,8]和[10,6]都可以满足,但是如果选[10,8]第3位再选6或者8就不满足条件了。
解
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
length = len(nums)
if length <= 1:
return length
# 初始化
max_length = 1
pre_diff = 0
# result = [nums[0]]
for i in range(1, len(nums)):
diff = nums[i] - nums[i-1]
# 如果当前节点和前一个节点差值与前一组数分别为“峰”或“谷”,则满足要求
if (diff > 0 and pre_diff <= 0) or (diff < 0 and pre_diff >= 0):
max_length += 1
pre_diff = diff
# result.append(nums[i])
return max_length
53. 最大子序和
思路
如果 [-2,1] 在一起,计算起点的时候,是从1开始计算,因为负数只会拉低总和,这就是贪心的地方!
局部最优:当前“连续和”为负数的时候立刻放弃,从下个元素重新计算“连续和”,因为负数加上下个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”,局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。
从代码⻆度上来讲:遍历nums,从头开始用temp_sum累积,如果temp_sum 加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积temp_sum 了,因为已经变为负数的temp_sum,只会拖累总和。
解
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
"""
1. result 记录当前遍历过的序列的最大和
2. temp_sum 在result的基础上继续遍历,如果遇到temp_sum>result就更新result
:param nums:
:return:
"""
result = float("-inf")
temp_sum = 0
for i in range(len(nums)):
# 计算临时和
temp_sum += nums[i]
# temp_sum 大于 result时更新result,相当于不断确定最大序终止位置
if temp_sum > result:
result = temp_sum
# temp_sum 小于0时重置为0,相当于重置最大子序起始位置,因为负数只会让和更小,所以从下一个数重新开始计算
if temp_sum < 0:
temp_sum = 0
return result
122. 买卖股票的最佳时机 II
解法1: 贪心
def maxProfit(prices):
"""
1. profit记录利润
2. start购买的价格,end记录售出的价格
3. 遍历 prices,当nums[i]小于nums[i-1]时,售出股票,并重置start和end为nums[i]
4. 挡nums[i]>=nums[i-1]时,说明还在上涨,则end=nums[i]
:param prices:
:return:
"""
profit = 0
start = prices[0]
end = prices[0]
for i in range(1, len(prices)):
if prices[i] < prices[i-1]:
profit += end - start
start = prices[i]
end = prices[i]
# 遍历到最后一个元素时,要结算收益
if i == len(prices) - 1 and (end - start) > 0:
profit += end - start
return profit
更优思路:
def max_profit(prices: List[int]) -> int:
result = 0
i = 0
while i < len(prices)-1:
# 1. 如果后一天和前一天是上升趋势,那么就取其差值为利润,相当于今天买入,明天卖出,
# 这样一直累加则最终结果就是最大利润,例如: [1,3,5], (3-1) + (5-3)=4 ;
# 2. 如果遇到下降趋势则不做任何操作,相当于不买入也不卖出
if prices[i + 1] > prices[i]:
result += prices[i + 1] - prices[i]
i += 1
return result