Medium
题目描述
你有一些不同颜色的球,有一个客户想要订购任意颜色的球。
客户对彩色球的估值很奇怪。每个彩色球的价值等于你目前库存中该颜色球的数量。例如,如果你拥有 6 个黄球,客户会为第一个黄球支付 6。交易后,只剩下 5 个黄球,所以下一个黄球的价值为 5(即球的价值随着你向客户出售更多球而递减)。
给你一个整数数组 inventory,其中 inventory[i] 表示第 i 种颜色的球的初始数量。还给你一个整数 orders,表示客户想要的球的总数。你可以按任何顺序出售球。
返回销售 orders 个彩球后能获得的最大总价值。由于答案可能很大,请返回对 10^9 + 7 取模的结果。
示例 1:
输入:inventory = [2,5], orders = 4
输出:14
解释:第1种颜色卖1次(2),第2种颜色卖3次(5 + 4 + 3)。
最大总价值是 2 + 5 + 4 + 3 = 14。
示例 2:
输入:inventory = [3,5], orders = 6
输出:19
解释:第1种颜色卖2次(3 + 2),第2种颜色卖4次(5 + 4 + 3 + 2)。
最大总价值是 3 + 2 + 5 + 4 + 3 + 2 = 19。
约束条件:
1 <= inventory.length <= 10^51 <= inventory[i] <= 10^91 <= orders <= min(sum(inventory[i]), 10^9)
解题思路
这道题的核心思想是贪心策略:每次都卖出价值最高的球来获得最大收益。
解题思路分析:
贪心策略:由于我们想要最大化总价值,应该优先卖出当前价值最高的球。每卖出一个球,该颜色球的价值就会减1。
排序 + 模拟:可以对库存进行排序,然后模拟卖球的过程。但这种方法在数据量大时会超时。
二分查找优化(推荐):关键观察是,必定存在一个阈值 k,使得所有价值大于 k 的球都会被完全卖出,价值等于 k 的球可能部分卖出。我们可以用二分查找找到这个 k 值。
数学计算:对于每个颜色,如果要从价值 high 卖到价值 low(包含),总价值为等差数列求和:
(high + low) * count / 2。
算法流程:
- 先对库存排序
- 二分查找阈值 k
- 计算所有价值 > k 的球的贡献(完全卖出)
- 计算价值 = k 的球的贡献(部分卖出)
这种方法时间复杂度为 O(n log max(inventory)),空间复杂度为 O(1)。
代码实现
class Solution {
public:
int maxProfit(vector<int>& inventory, int orders) {
sort(inventory.begin(), inventory.end(), greater<int>());
long long left = 0, right = *max_element(inventory.begin(), inventory.end());
long long k = 0;
while (left <= right) {
long long mid = left + (right - left) / 2;
long long count = 0;
for (int inv : inventory) {
if (inv > mid) {
count += inv - mid;
}
}
if (count <= orders) {
k = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
long long result = 0;
long long usedOrders = 0;
const int MOD = 1e9 + 7;
for (int inv : inventory) {
if (inv > k) {
long long count = inv - k;
usedOrders += count;
result = (result + ((long long)inv + k + 1) * count / 2) % MOD;
}
}
long long remaining = orders - usedOrders;
result = (result + remaining * k) % MOD;
return (int)result;
}
};
class Solution:
def maxProfit(self, inventory: List[int], orders: int) -> int:
inventory.sort(reverse=True)
left, right = 0, max(inventory)
k = 0
while left <= right:
mid = (left + right) // 2
count = sum(max(0, inv - mid) for inv in inventory)
if count <= orders:
k = mid
right = mid - 1
else:
left = mid + 1
result = 0
used_orders = 0
MOD = 10**9 + 7
for inv in inventory:
if inv > k:
count = inv - k
used_orders += count
result = (result + (inv + k + 1) * count // 2) % MOD
remaining = orders - used_orders
result = (result + remaining * k) % MOD
return result
public class Solution {
public int MaxProfit(int[] inventory, int orders) {
Array.Sort(inventory, (a, b) => b.CompareTo(a));
long left = 0, right = inventory.Max();
long k = 0;
while (left <= right) {
long mid = left + (right - left) / 2;
long count = 0;
foreach (int inv in inventory) {
if (inv > mid) {
count += inv - mid;
}
}
if (count <= orders) {
k = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
long result = 0;
long usedOrders = 0;
const int MOD = 1000000007;
foreach (int inv in inventory) {
if (inv > k) {
long count = inv - k;
usedOrders += count;
result = (result + (inv + k + 1) * count / 2) % MOD;
}
}
long remaining = orders - usedOrders;
result = (result + remaining * k) % MOD;
return (int)result;
}
}
/**
* @param {number[]} inventory
* @param {number} orders
* @return {number}
*/
var maxProfit = function(inventory, orders) {
inventory.sort((a, b) => b - a);
let left = 0, right = Math.max(...inventory);
let k = 0;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
let count = 0;
for (let inv of inventory) {
if (inv > mid) {
count += inv - mid;
}
}
if (count <= orders) {
k = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
let result = 0;
let usedOrders = 0;
const MOD = 1e9 + 7;
for (let inv of inventory) {
if (inv > k) {
let count = inv - k;
usedOrders += count;
result = (result + Math.floor((inv + k + 1) * count / 2)) % MOD;
}
}
let remaining = orders - usedOrders;
result = (result + remaining * k) % MOD;
return result;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n log max(inventory)),其中 n 是颜色数量。二分查找需要 O(log max(inventory)) 次,每次需要 O(n) 时间计算 |
| 空间复杂度 | O(1),只使用常数额外空间(不考虑排序的空间) |