前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >二分查找算法(下):通过 LeetCode 周赛学习二分查找算法

二分查找算法(下):通过 LeetCode 周赛学习二分查找算法

作者头像
与你一起学算法
发布2021-03-23 15:03:51
4000
发布2021-03-23 15:03:51
举报

一个二分查找算法和贪心算法结合的场景

之所以写这个,是因为我前两周在参加 LeetCode 周赛的时候,碰到了一个这样题,点击「阅读原文」可以直达题目链接,题目具体如下:

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), 其中 Nordersn 是数组长度。

而这道题的话,orders 会取到 10 ** 9,所以自然而然就会超时了。

那么应该如何解决呢?

解决思路

既然单独使用优先队列解决不了问题,那我们就换个思路进行思考。因为每次都要取数组中的最大值,然后减去 1, 所以最后呢数组中的元素肯定是小于等于某一个阈值的,这个我想你肯定是能够理解的。

那这个阈值能不能求出来呢?如果能求出来的话,那问题是不是就容易解决了呢?你想啊,如果现在我们已经求出了这个阈值,那么是不是就知道了数组中的每个元素被减了多少次,进而累加求和不就得到结果了嘛。

好,现在问题已经变成了如何求解阈值了,这个如何求解呢?我们假定阈值为 threshold,那么它满足啥条件呢?

怎么理解呢?

就是说当前值是 threshold时,

就代表了所有的操作的次数,它应该是小于等于 orders的,为啥呢?拿例 1 举例,数组为 [2, 5], orders4,相当于要进行四次操作。

  1. 51,变为 4
  2. 41,变为 3
  3. 31,变为 2
  4. 剩下两个 2, 21,变为 1

此时所有数组中的元素都小于等于 2,所以这个例子中的 threshold就是 2。但是

有可能出现小于 orders 的情况,比如文中的这个例子,这时候又需要当前数组中的部分元素再减去 1,但是又不能所有元素都减去 1,如果那样的话, threshold 就会改变了。

因为这个函数是一个单调递减的函数,所以存在唯一的 threshold,满足上述式子。所以问题就转化为了在 010 ** 9 之间查找最小的 threshold,使得

看到了吗?这个问题就转化为了上篇文章中我们提到的二分算法的变体问题,没理解的话,你品,你再品。

然后

就表示了数组中元素还需要再减 1 的次数。

这个问题到这里就解决了,接下来看看代码吧,这里面还有很多骚操作,保证出乎你的意外。

代码实现

class Solution:
    def maxProfit(self, inventory: List[int], orders: int) -> int:
        max_num = 10 ** 9 + 7
        res = 0
        low, high = 0, 10 ** 9
        threshold = None
        while low <= high:
            mid = low + ((high - low) >> 1)
            temp = 0
            for i in range(len(inventory)):
                if inventory[i] >= mid:
                    temp += (inventory[i] - mid)
            if temp <= orders:
                threshold = mid
                high = mid - 1
            else:
                low = mid + 1
        temp = 0
        for i in range(len(inventory)):
            if inventory[i] > threshold:
                temp += (inventory[i] - threshold)
        temp = orders - temp
        for i in range(len(inventory)):
            if inventory[i] >= threshold:
                if temp > 0:
                    # 等差数列求和公式
                    res += ((inventory[i] + threshold) * (inventory[i] - (threshold) + 1) // 2)
                    temp -= 1
                else:
                    # 等差数列求和公式
                    res += ((inventory[i] + threshold + 1) * (inventory[i] - (threshold + 1) + 1) // 2)
        return res % max_num

这个是我今天下午刚写的,前一部分是二分查找的实现,后面是累加求和的过程。

不知道最后一个 for 循环你看懂了没?在计算 res的过程中,运用到了等差数列的求和公式,我当时在看别人代码的时候是一脸懵逼的,当时想着计算的话不是应该有循环的吗?为啥没有循环呢?没有循环怎么计算从 a[i]threshold 呢?突然间恍然大悟,不得不佩服大神。

总结

现在回过头来看,这道题的思想已经很正常了,但是当我在参加 LeetCode 周赛的时候,当时心态都被它给搞崩了。。。

你看提到二分查找算法的话,我想每个人都知道,提及贪心算法,每个人也都有话可说,但是二者结合起来,就让很多人摸不着头脑了。

当然,这并不是说我们之前学的算法知识没有用,而是我们缺乏一种融会贯通的思维,在学习的过程中,要学会举一反三。

这道题带给我的不仅仅是知识点的融会贯通,更让我惊讶的是数学知识的使用,没有刻意的地方,一切是那么的自然。

我们每个人学数学的话也都学了好多年,但是更多的是用来考试,真正在编程过程的使用时是很少的。

虽然那次周赛我只做了一道题,但是感觉收获是很大的,开阔了眼界,拓展了思维。

如果你对编程也感兴趣的话,欢迎联系我,我们共同交流进步。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 与你一起学算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个二分查找算法和贪心算法结合的场景
    • 1648. 销售价值减少的颜色球
    • 分析
    • 解决思路
    • 代码实现
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档