2021-06-09

求解最优化的问题常常会有一系列的步骤,而每个步骤往往会面临着选择。

而贪心算法就是这样的算法,它在每一步都做出最优解,也就是说,它总是做出局部最优解,寄希望于通过局部最优解来获得全局最优解。

我们首先举个栗子:调度竞争资源来完成任务,比如我们要利用有限的资源(如一个阶梯教室)来实现任务(举办活动),每个活动的时间不一样,分别有开始时间和就结束时间,拿如何得到最优解呢?

我们看到虽然用动态规划来求解活动选择问题,但是并不需要这么做,我们可以反复选择最早结束的活动,同时保留与此兼容的活动,重复这一过程,直到不再有剩余活动。我们选择了活动结束的最早时间,所以活动结束时间必然是单调递增的,所以我们只需要按照结束时间的单调递增顺序处理所有活动,直到活动结束,每个活动只考察一次。

定理:考虑任何非空子集问题S,a是S中最早结束的活动,所以a必然在S中某个最大兼容活动子集中。
贪心算法往往是这种自顶向下的设计,先做出一个选择,然后再求解下一个问题,而不是自底向上解出许多子问题,然后再做出选择。

在做贪心选择时,我们直接做出当前问题中看起来最优的解,而不是考虑到子问题的解,这也是贪心算法和动态规划的不同之处,在动态规划中,我们往往每一个步骤都求做一个选择,这个选择往往依赖于子问题的解。而在贪心算法中,我们总是做出当时看来最佳的选择,然后再求解剩下唯一的子问题。贪心算法做出选择时可能会依赖于之前的选择或者子问题的解,但是绝对不依赖于将来的选择或者子问题的解。就是我们前面所说的,一个动态规划问题是自底向上的,而一个贪心算法问题是自顶向下的。

下面是几道leetcode题,解法思路都是贪心算法,我用的是python:

1.分配饼干

Leetcode : 455. Assign Cookies (Easy)

Input: [1,2], [1,2,3]

Output: 2

题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。

思路:因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。
假设在某次选择中,贪心策略选择给第 i 个孩子分配第 m 个饼干,并且第 i 个孩子满足度最小,第 m 个饼干为可以满足第 i 个孩子的最小饼干,利用贪心策略最终可以满足 k 个孩子。假设最优策略在这次选择中给 i 个孩子分配第 n 个饼干,并且这个饼干大于第 m 个饼干。我们发现使用第 m 个饼干去替代第 n 个饼干完全不影响后续的结果,因此不存在比贪心策略更优的策略,即贪心策略就是最优策略。

class Solution:
   def findContentChildren(self, g, s):
       """
       :type g: List[int]
       :type s: List[int]
       :rtype: int
       """
       g.sort()
       s.sort()
       i=j=res=0
       while i<len(g) and j<len(s):
           if g[i]<=s[j]:
               res += 1
               i += 1
           j += 1
       return res


2.投飞镖刺破气球

Leetcode : 452. Minimum Number of Arrows to Burst Balloons (Medium)

Input:

[[10,16], [2,8], [1,6], [7,12]]

Output:

2

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

思路:从左往右投飞镖,并且在每次投飞镖时满足以下条件:
1.左边已经没有气球了;
2.本次投飞镖能够刺破最多的气球。

class Solution:
   def findMinArrowShots(self, points):
       """
       :type points: List[List[int]]
       :rtype: int
       """
 
       points.sort(key = lambda x:(x[0],x[1]))
       n = len(points)
       if n == 0:return 0
       S = points[0][0]
       E = points[0][1]
       res = 1
       for i in range(1,n):
           if E>points[i][1]:
               E = points[i][1]
           elif S<points[i][0]:
               S = points[i][0]
           if S>E:
               res += 1
               S = points[i][0]
               E = points[i][1]
       return res

3.股票的最大收益

Leetcode : 122. Best Time to Buy and Sell Stock II (Easy)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

input: [7,1,5,3,6,4]

output: 7

思路: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。

随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加加到收益中,从而在局部最优的情况下也保证全局最优。

class Solution:
   def maxProfit(self, prices):
       """
       :type prices: List[int]
       :rtype: int
       """
       profit = 0
       for i in range(len(prices)-1):
           if prices[i+1]>prices[i]:
               profit += (prices[i+1]-prices[i])
       return profit

4.种植花朵

Leetcode : 605. Can Place Flowers (Easy)

Input: flowerbed = [1,0,0,0,1], n = 1

Output: True

假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。

示例 1:

input: flowerbed = [1,0,0,0,1], n = 1
output: True

思路:自己想吧,不难的

class Solution:
   def canPlaceFlowers(self, flowerbed, n):
       """
       :type flowerbed: List[int]
       :type n: int
       :rtype: bool
       """
       flowerbed.append(0)
       flowerbed.insert(0,0)
       num = 0
       res = 0
       for i in range(len(flowerbed)):
           if flowerbed[i] == 0:
               num += 1
           else :
               num = 0
           if num == 3:
               res += 1
               num = 1
       return res>=n
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值