一个二分查找算法和贪心算法结合的场景
之所以写这个,是因为我前两周在参加 LeetCode
周赛的时候,碰到了一个这样题,题目链接如下:
1648. 销售价值减少的颜色球
1648. 销售价值减少的颜色球
你有一些球的库存
inventory
,里面包含着不同颜色的球。一个顾客想要 任意颜色 总数为orders
的球。
这位顾客有一种特殊的方式衡量球的价值:每个球的价值是目前剩下的 同色球 的数目。比方说还剩下
6
个黄球,那么顾客买第一个黄球的时候该黄球的价值为6
。这笔交易以后,只剩下5
个黄球了,所以下一个黄球的价值为5
(也就是球的价值随着顾客购买同色球是递减的)。
给你整数数组
inventory
,其中inventory[i]
表示第i
种颜色球一开始的数目。同时给你整数orders
,表示顾客总共想买的球数目。你可以按照 任意顺序 卖球。
请你返回卖了
orders
个球以后 最大 总价值之和。由于答案可能会很大,请你返回答案对10 ** 9 + 7
取余数 的结果。
示例 1:
输入:inventory = [2,5], orders = 4
输出:14
解释:卖 1 个第一种颜色的球(价值为 2 ),卖 3 个第二种颜色的球(价值为 5 + 4 + 3)。
最大总和为 2 + 5 + 4 + 3 = 14 。
提示
1 <= inventory.length <= 10 ** 5
1 <= inventory[i] <= 10 ** 9
1 <= orders <= min(sum(inventory[i]), 10 ** 9)
分析
刚开始我完全没有意识到有二分查找的思想,就是想着用一个优先队列,然后每次取出一个元素,加上当前值,把当前值减一,然后再把当前值放入优先队列。
没过几分钟,程序就写完了,但是呢提交后显示运行超时了,我就想着去优化程序。于是我又读了下题,看看是不是我漏了啥重要条件,结果读了几遍发现这道题就是贪心的思想啊,不可能错的呀,但是结果就是超时了。。。
于是周赛结束后我特意去查了下大神写的代码,真的是让我惊呆了,是贪心的思想没错,但是时二分和贪心进行结合。
这个题想法很简单,设置一个 sum
变量,sum
每次加上数组中的最大值,然后将当前值减去 1
,直到此过程重复 orders
步骤。
然后为啥用优先队列会超时呢?其实优先队列的底层就是堆,每次取出元素,加入元素都需要对堆进行调整,调整的时间复杂度是 O(logn)
,其中 n
是优先队列的长度,然后需要进行 orders
操作,所以总的时间复杂度就是 O(Nlogn)
, 其中 N
是 orders
, n
是数组长度。
而这道题的话,orders
会取到 10 ** 9
,所以自然而然就会超时了。
那么应该如何解决呢?
解决思路
既然单独使用优先队列解决不了问题,那我们就换个思路进行思考。因为每次都要取数组中的最大值,然后减去 1
, 所以最后呢数组中的元素肯定是小于等于某一个阈值的,这个我想你肯定是能够理解的。
那这个阈值能不能求出来呢?如果能求出来的话,那问题是不是就容易解决了呢?你想啊,如果现在我们已经求出了这个阈值,那么是不是就知道了数组中的每个元素被减了多少次,进而累加求和不就得到结果了嘛。
好,现在问题已经变成了如何求解阈值了,这个如何求解呢?我们假定阈值为 threshold
,那么它满足啥条件呢?
∑ a i > t h r e s h o l d a i − t h r e s h o l d < = orders < ∑ a i > t h r e s h o l d a i - threshold + 1 \sum_{a_{i}>t h r e s h o l d} a_{i}-t h r e s h o l d<=\text { orders }<\sum_{a_{i}>t h r e s h o l d} a_{i} \text { - threshold }+1 ai>threshold∑a