今天继续Codility的Lessons部分的第14个主题——Binary search algorithm
Codility - Lessons - Binary search algorithm
还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~
PDF Reading Material
-
前言: 介绍了binary search是一种非常有效的,可以将线性(n)复杂度降低到log(n)复杂度的算法
-
14.1: 举了个游戏的例子说明binary search的思想,一共16个数,电商只会根据你每次给的数字,告诉你最终正确的数字是大于还是小于该数字,因此最优的策略是在开始就先给中间的数字,这样可以排除掉一半的错误答案。
-
14.2: 通过一个在31个顺序排列的数中,找到某个数的位置的例子,完成了binary search的样例代码的编写工作,并说明了时间复杂度为什么是O(logN)
-
14.3: 讲了binary search思想运用在找最优解问题上的方法,即只要可以事先圈定一个最优解的范围,就可以利用binary search的思想开始搜索,直到找到最优解
-
14.4: 举了一个板子填洞的样例题,屋顶存在着长度不定的很多洞,需要返回的是可以满足用k个长度为n的板子能实现将所有洞进行填充的,最小的(也是对本题最优的)板长度n。
我们可以直观推理出,假设最优解是n,则任何大于n长度的板子,也一定也是能实现洞填充的,所以我们可以对板子的最优(小)长度,采用binary search思想进行搜索。而对每一种长度n,判断是否能够满足填满洞口的时间复杂度是n,因此,最终可以用O(NlogN)的复杂度实现本题的解答。
Tasks
MinMaxDivision: Divide array A into K blocks and minimize the largest sum of any block.
题目概述:本题的输入是两个正整数K和M,以及一个Array A,K代表要将A分成的K个Blocks,M代表Array中的最大元素也不会大于M,而Array A本身由N个正整数组成。需要的输出,是在将Array A中的N个正整数分成K个blocks后,所有blocks的sum值中最大的一个。
解法概述:这里要承认,拿到这道题之后想了很久我自己也没有想到好的解法,一开始思路局限在如何在输入A上进行binary search可以实现目的,发现根本没有思路,后来还是参考了网上才想明白。binary search的思想并非直接应用在Array A的K Blocks的划分上,而是用于在所有可能的结果区间里,搜索最小的满足条件的解上。
因为这个需要返回的minimum large sum一定是介于A.max()~A.sum()之间,所以我们可以将这两个值设定为初始的begin和end,然后从mid值开始判断是否满足可以将A中的N个interger分成K个sum值小于等于mid的blocks,如果可以则继续向下搜索,反之向上搜索,直到最后找到最小的、可以满足“将A中的N个interger分成K个sum值小于等于mid的blocks”的mid值,这就是需要返回的答案。
def check(K, A, mid):
blocks = 1
cur_sum = 0
for a in A:
if cur_sum+a>mid:
blocks+=1
cur_sum=a
if blocks>K:
return False
else:
cur_sum+=a
return True
def solution(K, M, A):
begin = max(A)
end = sum(A)
result = end
while begin<=end:
mid = (begin+end)//2
if check(K, A, mid):
result = mid
end = mid - 1
else:
begin = mid + 1
return result
NailingPlanks: Count the minimum number of nails that allow a series of planks to be nailed.
题目描述:题目的输入是三个Array A、B、C:
- Array A、B都包含N个正整数,两Array同index处的a,b,代表了一个木板的起始和终止index,因此一共就代表了n个木板的起尾。
- Array C一共有M个正整数,代表的是可以顺序砸入钉子的M个位置(这M个位置不一定是递增的)
最终需要返回的,是能够实现让所有木板上都有至少一个钉子时,最少的顺序砸入钉子数。
解法描述:这里有一点需要特别强调的,就是题干中关于钉子的砸入,一定是按照Array C中原始index顺序砸入的。也就是并不是可以按照需要从Array C中只选取合适的,这个限制让题目的计算复杂度下降。
本题我们依然将binary_search思想,直接用在确定最终需要返回的result(钉子数)上,直接从mid=(M+1)//2开始判断前mid个钉子是否能确保让每块木板上都有一个钉子,然后再不断确定下一search区间,直到确定最终的答案。
而判断是否每块木板上都有一个钉子,就是solution的check部分,我们是模拟了一个长度为2M的Array(nail_cnt),然后将C中钉子的index顺序标注进这个Array,最后再顺序判断每块plank是否都有钉子。为了快速判断每块Plank是否有钉子,则是利用之前章节提到过的presum思想,直接通过presum后的Array中是否有nal_cnt[b]>nal_cnt[a-1]来确定。整体Solution函数如下:
def check(A, B, C, M, mid):
# 模拟一个长度为2M的Array,并标注进去所有钉子的位置
nail_cnt = [0] * (2 * M) # A、B、C中最大可能值为2M
for c in C[:mid]:
nail_cnt[c-1] += 1
# presum
for i in range(1, 2*M):
nail_cnt[i] += nail_cnt[i-1]
# 在最前边多放一个0,方便后续通过nail_cnt[b] == nail_cnt[a-1]判断每块plank是否有钉子
nail_cnt = [0] + nail_cnt
# check (a,b) in zip(A,B)
for a, b in zip(A,B):
if nail_cnt[b] == nail_cnt[a-1]:
return False
return True
def solution(A, B, C):
M = len(C)
begin = 1
end = M
result = -1
while begin<=end:
mid = (begin+end)//2
# print(begin, end, mid)
if check(A, B, C, M, mid):
result = mid
end = mid-1
else:
begin = mid+1
return result