1648 销售价值减少的颜色球(python3 二分查找)

1. 问题描述:

你有一些球的库存 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 。

示例 2:

输入:inventory = [3,5], orders = 6
输出:19
解释:卖 2 个第一种颜色的球(价值为 3 + 2),卖 4 个第二种颜色的球(价值为 5 + 4 + 3 + 2)。
最大总和为 3 + 2 + 5 + 4 + 3 + 2 = 19 。

示例 3:

输入:inventory = [2,8,4,10,6], orders = 20
输出:110

示例 4:

输入:inventory = [1000000000], orders = 1000000000
输出:21
解释:卖1000000000 次第一种颜色的球,总价值为 500000000500000000 。 500000000500000000 对 10 ^ 9 + 7 取余为 21 。

提示:
1 <= inventory.length <= 10 ^ 5
1 <= inventory[i] <= 10 ^ 9
1 <= orders <= min(sum(inventory[i]), 10 ^ 9)

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sell-diminishing-valued-colored-balls

2. 思路分析:

① 对于这种一看没有思路的题目,我们可以尝试想一下对于一般的题目有哪些解决办法,比如递归、动态规划、模拟、哈希表这些,分析题目可以发现这些思路都不适合解决这道问题,而且考虑到数据规模是比较大的(数据规模为10 ^ 5),所以我们尝试一下二分查找的思路解决,分析题目可以知道二分查找的思路是可行的(后面分析可以知道当前的问题是单调的),二分查找需要确定的参数与如下几个:

(1) 二分查找中间范围的含义

(2) 二分查找的左右边界

可以知道(1)是最重要的,是题目的核心,对于一般的二分查找的题目来说二分查找的中间范围mid对应的数组表示的往往是题目的答案,我们可以根据这个mid值来确定下一次查找的左右边界的范围,这样就可以一步步逼近最终的答案,但是对于这道题目来说我们不能通过查找中间范围mid来确定最终的答案,因为我们根本不知道答案(最大的价值)在哪个范围的,由题目可以知道题目只存在着几个参数,二分查找查找的往往是题目中的这些参数,并且通过题目中参数的限定条件来逼近最终的答案,从题目可以知道参数有:球的价值、个数,所以可以往这两个参数去想,当我们二分查找的中间范围mid的含义表示的是球最后卖出去的价值,可以发现通过球最后卖出去的价值mid确定是否可以卖出orders个球,进而求解出最大价值,所以我们需要先通过二分查找来找出球最终卖出去的价值,然后再计算总的价值,这里比一般的二分查找多了一步计算最终答案的过程,综上通过二分查找最后卖出的球的价值的解决方法是可行的。

② 由①可以知道二分查找的mid代表的含义是球最后卖出去的价值,分析题目可以知道二分查找的左边界为数组元素的最小值1,右边界为数组元素的最大值10 ^ 9,这样我们可以使用一个方法来检测最终球卖出去的价值为mid的时候时可以卖出大于等于orders的数目,怎么样进行检测呢?我们可以通过遍历inventory数组,当数组元素inventory[i] 大于等于mid的时候说明是可以当前的inventory[i] 是可以卖出价值为mid的,这样我们就可以累加数组元素与mid之间的差值来求解出当前能够卖出去的球的数目,最后返回累加的能够卖出去的数目是否大于orders来决定下一次查找的范围,当发现价值为mid的时候可以卖出去的球的数目大于orders的数目,那么左边的范围就需要增大,否则右边的范围就需要减小,这样才能使获取的价值尽可能大,而且二分求解出最后卖出去球的价值m对应的球的数目肯定是大于等于orders的。

③ 由②知道二分求解出最后的价值m对应的球的数目肯定是大于等于orders,所以我们需要在循环出求解出数组元素inventory[i]大于m的数目计算这一部分的价值,并且orders应该减去对应的数目,当循环结束之后再处理数组元素等于m的情况,为什么不在循环中处理数组元素 inventory[i] == m的情况呢?因为有可能计算数组元素 inventory[i] == m的时候最终累加的数目有可能是大于orders的,比如这样的数据:inventory = [2,5],orders = 4,二分查找的中间范围为m = 2,假如我们在循环中计算5 + 4 + 3 + 2 + 2,那么最终卖出去的球的数目为5不符合题目的要求,所以我们需要在for循环结束之后再处理mid == inventory[i]的情况。

3. 代码如下:

from typing import List


class Solution:
    # check检查最后卖出去的球的价值为mid的球的数目是否大于等于orders
    def check(self, inventory: List[int], orders: int, mid: int):
        count = 0
        for x in inventory:
            if x >= mid: count += (x - mid + 1)
            if count >= orders: return True
        return False

    def maxProfit(self, inventory: List[int], orders: int) -> int:
        l, r, mod = 1, 10 ** 9 + 7, 10 ** 9 + 7
        mid = (l + r) // 2
        m = -1
        while l <= r:
            mid = (l + r) // 2
            if self.check(inventory, orders, mid):
                m = mid
                l = mid + 1
            else:
                r = mid - 1
        res = 0
        for x in inventory:
            if x> m:
                res += (m + x) * (x- m + 1) // 2
                orders -= (x- m + 1)
            res %= mod
        res = (res + orders * m) % mod
        return res

第二种写法(主要是在最后求解答案的时候):

from typing import List

class Solution:
    ## check检查当前二分查找的最后卖出的球的价值mid对应的球的数目是否大于等于orders    
    def check(self, inventory: List[int], orders: int, mid: int):
        count = 0
        for x in inventory:
            if x >= mid:
                count += x - mid + 1
            if count >= orders: return True
        return False
    
    def maxProfit(self, inventory: List[int], orders: int) -> int:
        l, r = 0, 10 ** 9
        m = -1
        while l <= r:
            mid = l + r >> 1
            if self.check(inventory, orders, mid):
                m = mid
                l = mid + 1
            else:
                r = mid - 1
        # 计算最后inventory[i]大于m的价值
        res = 0
        mod = 10 ** 9 + 7
        for x in inventory:
            if x > m:
               # 计算所有卖出去的球价值大于m的价值
               res += (x + m + 1) * (x - m) // 2 % mod
               orders -= x - m
        res = (res + m * orders) % mod
        return res

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值