1、算法描述:
贪心算法是指在对问题求解时,总是做出在当前看来最好的选择,不从总体最优上加以考虑,只是局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法可以认为是动态规划算法的一个特例,如果满足每做出一个局部最优解,得到的最终结果都是全局最优,那么就可以使用贪心算法对动态规划进行优化,降低时间复杂度。
例如:有100张人民币,拿出10张,是拿出的总额最大。每次拿出剩余人民币中的最大值(局部最优),最后得到的一定是总额最大的情况(全局最优)。
但是并不是所有的问题都满足这个条件,所以不是所有的问题都可以用贪心算法解决。
2、例题分析:
①两地调度:https://leetcode-cn.com/problems/two-city-scheduling/
公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0],飞往 B 市的费用为 costs[i][1]。返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。
例如:
输入:[[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 A 市,费用为 10。
第二个人去 A 市,费用为 30。
第三个人去 B 市,费用为 50。
第四个人去 B 市,费用为 20。
最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。
分析:
举个例子:
a这个人去A:100,去B:110,然后b这个人去A:10,去B:50。
由此可知a去A比B便宜10,而b去A比B便宜40。所以选择便宜的多的,即a去B,b去A。
所以将去两地的费用差作为贪心因子。
如果按照A-B的差从小到大排序,则对于前N个人,去A比去B便宜的多,所以去A地;同理,后N个人去B地。
代码如下:
class Solution:
def twoCitySchedCost(self, costs: List[List[int]]) -> int:
# 若去A地能节省更多的费用,则去A,否则去B
# 比如:a这个人去A:100,去B:110.然后b这个人去A:10,去B:50。
# 可知a去A比B便宜10.而b去A比B便宜40。所以选择便宜的多的,即a去B,b去A。
# 所以根据差值从小到大排序
# 前一半人去A地,后一半人去B地
costs.sort(key = lambda x : x[0] - x[1])
res = 0
n = len(costs) // 2
for i in range(n):
# 前一半去A的费用加上后一半去B的费用
res += costs[i][0] + costs[i + n][1]
return res
②无重叠区间:https://leetcode-cn.com/problems/non-overlapping-intervals/
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
例如:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
分析:
作为区间调度问题,此题就是求最多有几个不重叠的区间。
如果贪心因子选择区间的开始节点,那么会遇到有的区间开始的早,结束得很晚,就会错过中间的一些小区间,此时得到的结果就不是最优解。
如果选择区间结束节点作为贪心因子,那么在它结束节点之后的区间是最大的,可选择的区间个数也是最多的,所以最后可以得到最多的不重叠区间。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
# 贪心算法
if len(intervals)==0:
return 0
# 按照区间右侧升序排序
# 如果后一个区间的开始大于等于当前区间的结束,就不重叠
intervals = sorted(intervals,key = lambda x:x[1])
res = 1 # 不重叠区间个数
end = intervals[0][1] # 第一个区间的结束节点
# 遍历每个区间
for x in intervals:
start = x[0]
if end <= start:
res += 1
end = x[1]
return len(intervals)-res