[leetcode题解] 第1648题Sell Diminishing Valued Colored Balls

本篇博客详细分析了LeetCode第1648题——如何高效地卖出减价彩色球,提出两种解法。第一种解法利用贪心策略和最大堆,但效率较低;第二种解法通过寻找最大值和次最大值,结合二分查找优化,时间复杂度为O(n log M),其中n为球的种类数,M为最大价值。通过示例展示了算法的运行过程。
摘要由CSDN通过智能技术生成

https://leetcode-cn.com/problems/sell-diminishing-valued-colored-balls/

分析

很容易找到这道题的贪心策略:每次卖出价值最大(数目最多)的那种球。
我们可以使用最大堆来辅助获取最大的inventory[i],把它加到ans后再把inventory[i] - 1放回堆中,同时orders--。这种思路很简单,但效率太低了,时间复杂度为 O ( o r d e r s ⋅ l o g n ) O(orders\cdot logn) O(orderslogn) n n n为球的种类。

解法一

最直观的加速方法是什么?我们可以获取到最大值inventory[i]和次最大值inventory[j],然后将第i种球卖出inventory[i] - inventory[j]次并计算这个过程的利润。接下来会有两个最大值inventory[i] == inventory[j],需要再寻找次最大值inventory[k],然后将第i和第j种球都卖出inventory[i] - inventory[k]次,并计算利润。重复这个过程直到orders == 0,就找到了答案。
我们可以先对inventory进行排序来帮助我们找到每一次计算的最大值和次最大值。
以示例3为例展示该算法的过程:

阶段一:[10, 8, 6, 4, 2] orders = 20
\qquad \quad 最大值10,次最大值8,有1个最大值。因此1种球卖2次,利润为1 * (10 + 9) = 19。
阶段二:[8, 8, 6, 4, 2] orders = 18
\qquad \quad 最大值8,次最大值6,有2个最大值。因此2种球分别卖2次,利润为2 * (8 + 7) = 30。
阶段三:[6, 6, 6, 4, 2] orders = 14
\qquad \quad 最大值6,次最大值4,有3个最大值。因此3种球分别卖2次,利润为3 * (6 + 5) = 33。
阶段四:[4, 4, 4, 4, 2] orders = 8
\qquad \quad 最大值4, 次最大值2,有4个最大值。因此4种球分别卖2次,利润为4 * (4 + 3) = 28。
总利润为110。

orders小于当前可以卖的总次数时,需要计算数目最多的那几种球分别可以卖多少次,剩余的orders用来给这其中的某几种球再卖一次。

代码:
class Solution {
public:
    int mod = 1e9 + 7;
    
    int maxProfit(vector<int>& inventory, int orders) {
        sort(inventory.rbegin(), inventory.rend());
        long ans = 0;
        int j = 0;
        while (orders > 0) {
            // [0, j) 范围内的颜色的球数 == MAX
            while (j < inventory.size() && inventory[j] >= inventory[0]) j++;
            int next = 0;
            // 可从MAX一直卖到next + 1
            if (j < inventory.size()) next = inventory[j];
            long bucks = j, delta = inventory[0] - next;
            long rem = bucks * delta; // 可卖的次数
            if (rem > orders) {
                int delta = orders / bucks; // 每种球可卖的次数
                long a1 = inventory[0], an = a1 - delta + 1;
                ans += ((a1 + an) * delta / 2) * bucks;
                ans += (inventory[0] - delta) * (orders % bucks);
            }
            else {
                long a1 = inventory[0], an = next + 1;
                ans += ((a1 + an) * delta / 2) * bucks;
                inventory[0] = next;
            }
            orders -= rem;
            ans %= mod;
        }
        return ans;
    }
};

解法二

解法一的核心思路在于对数目最多的那几种球进行售卖,直到它们的数目等于次最多。通过这种方法,我们其实能够推导出最终卖完后的inventory与初始的inventory之间的关系:假设最终最大的inventory值为T,那么有:
o r d e r s ≥ ∑ i n v e n t o r y [ i ] > T i n v e n t o r y [ i ] − T orders \geq \sum_{inventory[i] > T}inventory[i] - T ordersinventory[i]>Tinventory[i]T
即我们要把数目多于T的球一直卖出到数目为T。为什么取不等号?这对应于解法一所提及的orders小于当前可以卖的总次数的情况。因此,如果卖出到Torders > 0,那么可以将数目为Torders种球再卖出一次,其数目最终为T - 1
显然,如果能找到这个T,那么计算销售过程的总利润也就很简单了。
采用二分法来寻找满足上述不等式的最小的T,如果我们把满足不等式看成一个条件的话,不难理解,这就是一个用二分法寻找左边界的问题。

代码

代码中用了一点小trick,为了防止求和溢出int范围,采用相减的方式来判断是否满足不等式。

class Solution {
public:
    int mod = 1e9 + 7;
    
    int maxProfit(vector<int>& inventory, int orders) {
        long left = 0, right = *max_element(inventory.begin(), inventory.end());
        // 二分查找寻找左边界
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (valid(inventory, mid, orders)) right = mid - 1;
            else left = mid + 1;
        }
        long ans = 0;
        for (int& num : inventory) {
            if (num > left) {
                long a1 = num, an = left + 1, n = num - left;
                ans += ((a1 + an) * n / 2) % mod;
                orders -= n;
            }
        }
        if (orders > 0) ans += left * orders;
        ans %= mod;
        return ans;
    }

    bool valid(vector<int>& inventory, int k, int orders) {
        for (int& num : inventory) {
            if (num > k) orders -= (num - k);
            if (orders < 0) return false;
        }
        return orders >= 0;
    }
};

算法时间复杂度 O ( n ⋅ l o g M ) O(n\cdot logM) O(nlogM) M M Minventory的最大值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值