今天继续Codility的Lessons部分的第16个主题——Greedy algorithms
Codility - Lessons - Greedy algorithms
还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~
PDF Reading Material
- 前言: 介绍了贪婪算法的整体思路,即基于目前现状找到一个局部最优的解法,和可以找到全局最优解法的动规/穷举法的区别,在于贪婪算法找到的局部最优的解法可能并非全局最优解。但是另一方面,在针对某类问题的情况下,用贪婪算法的计算复杂度,是要低于动态规划和穷举法的。
- 16.1-The Coin Changing Problem: 举了一个换硬币的例子,来说明贪婪算法并无法对所有场景给出最优解:题目是给出能凑出需要的总金额的最少硬币数的方案,贪婪算法的思路是在候选硬币list中,按照每次取最大可行硬币的原则,最终凑出需要的金额。
- 在需要的总金额是6,候选硬币是【1,2,5】三种的情况下,贪婪算法能够给出【1,5】的最优解
- 但在需要的总金额是6,候选硬币是【1,3,5】三种的情况下,贪婪算法给出的是【1,1,4】的次优解,而非【3,3】的全局最优解。但是贪婪算法的计算复杂度可以实现更高的O(n)。
- 16.2-Proving Correctness: 想要证明贪婪算法得到的解就是最优解,本质其实就是证明目前处理的问题中,每一步基于贪婪算法算出来的最优解,一定也是符合最终的全局最优解的。
- 16.3-Exercise: 举了一个安排皮划艇手们来坐皮划艇的例子,要安排一共n个皮划艇手,坐到尽可能少的皮划艇中,每个皮划艇最多坐两个人,最大承重为k。例题中一共给了两种贪婪算法的解题思路,但是最关键的还是证明贪婪算法对于本道题得到的解就是全局最优解上。
- 这里可以直接看下PDF的原文,大概思路就是反证,也就是假设最优解并不是按我们贪婪算法的思路(迭代判断最重和最轻的皮划艇手是否可以坐在一艘船里,可以则直接安排,不可以则安排最重的皮划艇手单独坐一艘船),也可以根据大小关系转换成和我们贪婪算法的思路一致(假如最优解里最终皮划艇手h,并不是和最轻皮划艇手l坐一船,而是和另外的皮划艇手x,则根据l<x,我们一定可以交互最轻皮划艇手l和x),因此证明,贪婪算法的每一步思路并不会造成偏离最优解。
Tasks
MaxNonoverlappingSegments: Find a maximal set of non-overlapping segments.
问题概述:本题的输入是两个等长的Array A和B,两数组中每对a和b,代表了一根绳子的首末位置,并且b是单调递增的,不会存在下根绳子的末尾节点<当前绳子的末尾节点。需要返回的result,是输入代表的绳子序列中,能够实现所有绳子都不重合(任意两根绳子没有重叠点)的最大绳子数。
解法概述:这里题干有一点很关键,就是板子的end序列是单增的,则我们使用贪婪算法对该题得到的解是全局最优解。我们贪婪算法的思路是,从第一根绳子开始就算result=1,然后根据第一根绳子的end节点,向后循环判断每根后续绳子是否有【begin>第一根绳子end节点】,如果有则对result+1,并记录这跟新计入绳子的end,继续向后循坏进行【begin>最后一根计数绳子end节点】的判断。
要证明这样贪婪搜索出来的解就是全局最优解,我们还是用反证的思路:假如存在全局最优解,且第一根绳子并不是Array A、B中的第一根,则因为Array A、B中的第一根绳子的end节点,一定小于等于全局最优解中第一根绳子的end节点,因此可以进行替换。同理该反正思路,也可以放在全局最优解的后续每根绳子上,因此可以证明我们贪婪算法思路下得到的result,一定就是全局最优解。
def solution(A, B):
# Implement your solution here
pre_b = -1
result = 0
for a,b in zip(A, B):
if a>pre_b:
result+=1
pre_b = b
return result
TieRopes: Tie adjacent ropes to achieve the maximum number of ropes of length >= K.
问题概述:本题输入是正整数K,和一个代表了N个不同长度绳子的Array A。我们可以按照Array A中绳子的顺序,选择对连续的任意多个绳子进行打结操作,打结后的两根绳子,就变成了一根长度等于原始两根绳子长度相加的新绳子。而K的含义,则是代表了我们的目标,我们需要通过自定义的打结方式,实现能够得到最多的长度>=K的绳子数。
解法概述:贪婪算法的思路,是从原始绳子Array中的第一根就开始算作要满足>=K的第一根绳子的组成部分,并在向后循环原始绳子数组的过程中,按照只要当前绳子还不满足>=K,就直接与下一根绳子进行打结操作,进行长度判断。难点还是在于如何证明这种贪婪算法的思路下,得到的result一定就是全局最优的解。
反证法思考的话,假如存在比这样的贪婪算法得到的结果更好(result更大)的最优解,最优解下,原始绳子序列中第一根绳子(称为a1),并不在第一根符合>=K条件的处理后绳子中,而是第k根绳子。则我们可以将原始序列中<k的绳子都和最优解的绳子绑在一起,这样并不会导致最优解变化,因此第一根绳子一定可以算在最优解里了。同样的思路可以继续对最优解里第2~最后一根绳子适用,证明贪婪算法得到的最优解,一定就是全局最优解。
def solution(K, A):
result = 0
curr_len = 0
for a in A:
if curr_len+a>=K:
result += 1
curr_len = 0
else:
curr_len += a
return result