题目描述

✅ 1648. 销售价值减少的颜色球

image-20260929090819616

image-20260929090819767

image-20260929090819858

题意分析

某种颜色还剩多少球,下一个球就能卖多少钱,卖出后该颜色库存减一。需要恰好卖出 orders 个球,使总收入最大,最后对 $10^9+7$ 取模。订单数可能很大,不能逐个球模拟销售。

解法:贪心 + 二分查找

核心思路

[!blue]
库存为 value 的一种颜色,会依次产生 value, value-1, ..., 1 的销售价格。最大收入就是从所有颜色的价格序列中选择最高的 orders 项。这样的选择也符合实际销售顺序:选到某个低价前,它同色的所有更高价格都会先被选中。

用价格水平线 level 分成两部分:所有高于它的价格完整卖出,再用价格恰好等于它的球补足订单。给定水平线 $x$,完整高价段的数量为 $sold(x)=\sum_i\max(0,inventory[i]-x)$。水平线越高,这个数量越少,因此可以二分最小的满足 sold(x) <= orders 的 $x$。

搜索范围是 $0$ 到最大库存。若高价段数量超过订单,说明水平线太低,令左边界越过中点;否则保留中点并收缩右边界。得到的 level 保证完整高价段不会超卖。

当 level > 0 时,由最小性可知 sold(level) <= orders < sold(level-1)。两次计数之差,正好是能再以 level 卖出一个球的颜色数量,所以足够填满剩余订单。若 level = 0,题目又保证订单不超过总库存,只能是全部库存恰好卖完,剩余订单为零。

库存大于 level 的颜色贡献价格段 level+1 到 value,用等差公式 (level+1+value) * (value-level) / 2 一次求和。最后加上 remaining * level。乘法先用 $64$ 位整数完成,再做整数除法与收益取模;销量始终保留真实数值,不参与取模。

解题步骤

  1. 取得最大库存,二分最低可行水平线。
  2. 每轮累加高于中点的销量,超过订单即可停止计数。
  3. 按等差公式结算所有完整高价段。
  4. 剩余订单乘以水平线价格,加入收益后取模。

代码实现

class Solution {
    private static final long MOD = 1_000_000_007L;

    public int maxProfit(int[] inventory, int orders) {
        int maxInventory = 0;

        for (int value : inventory) {
            maxInventory = Math.max(maxInventory, value);
        }

        int left = 0;
        int right = maxInventory;

        while (left < right) {
            int mid = left + (right - left) / 2;
            long sold = 0;

            for (int value : inventory) {
                if (value > mid) {
                    sold += value - mid;

                    if (sold > orders) {
                        break;
                    }
                }
            }

            // 寻找完整高价段销量不超过订单的最低水平线。
            if (sold <= orders) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }

        int level = left;
        long sold = 0;
        long profit = 0;

        for (int value : inventory) {
            if (value > level) {
                long count = value - level;
                // 结算水平线之上的完整等差价格段,先用宽类型乘除。
                long segment = ((long) level + 1 + value) * count / 2;

                profit = (profit + segment) % MOD;
                sold += count;
            }
        }

        // 未覆盖的订单统一按水平线价格补齐。
        long remaining = orders - sold;

        profit = (profit + remaining * level) % MOD;

        return (int) profit;
    }
}
func maxProfit(inventory []int, orders int) int {
    const mod int64 = 1_000_000_007

    maxInventory := 0
    for _, value := range inventory {
        if value > maxInventory {
            maxInventory = value
        }
    }

    left, right := 0, maxInventory
    for left < right {
        mid := left + (right-left)/2
        var sold int64
        for _, value := range inventory {
            if value > mid {
                sold += int64(value - mid)
                if sold > int64(orders) {
                    break
                }
            }
        }
        // 寻找完整高价段销量不超过订单的最低水平线。
        if sold <= int64(orders) {
            right = mid
        } else {
            left = mid + 1
        }
    }

    level := left
    var sold, profit int64
    for _, value := range inventory {
        if value > level {
            count := int64(value - level)
            // 结算水平线之上的完整等差价格段,先用宽类型乘除。
            segment := (int64(level) + 1 + int64(value)) * count / 2
            profit = (profit + segment) % mod
            sold += count
        }
    }
    // 未覆盖的订单统一按水平线价格补齐。
    remaining := int64(orders) - sold
    profit = (profit + remaining*int64(level)) % mod
    return int(profit)
}

复杂度分析

  • 时间复杂度:$O(n\log(V+1))$,V 为最大库存,每轮判定扫描全部颜色。
  • 空间复杂度:$O(1)$,只维护计数和收益。

关键点总结

[!green]

  • 高于水平线的全部出售,等于水平线的按需出售。
  • 整段等差求和替代逐球模拟。
  • 乘积先使用 64 位完成整数除法,再参与取模累加。
  • 二分中的销量计数只用于判断,可提前结束;最终结算重新统计完整高价段。

易错点总结

[!yellow]

  • 销量太多时继续降低水平线:会卖出更多,应提高下界。
  • 等差段从 level 开始计算:把待补的边界价格重复计入完整段。
  • 对订单数量取模:破坏真实销售数量。
  • 直接先对等差乘积取模再除二:普通整数除法不再等价。

相似题目

题目 难度 关联与区别
378. 有序矩阵中第 K 小的元素 中等 每种颜色的收益形成一条有序递减序列,本题取全局前若干项并求和,可按收益层批量计数而不逐球模拟。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2023/64943900
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!