Hard
题目描述
给你一个下标从 0 开始的 m x n 整数矩阵 values,表示 m 个不同商店里 m x n 个不同物品的价值。每个商店有 n 个物品,第 i 个商店的第 j 个物品的价值为 values[i][j]。此外,第 i 个商店的物品已经按照价值非递增的顺序排好序了,也就是说 values[i][j] >= values[i][j + 1] 对于所有 0 <= j < n - 1 都成立。
每一天,你可以在一个商店里购买一件物品。具体来说,在第 d 天,你可以:
- 选择任意一个商店
i - 购买数组中最右边的物品
j,开销为values[i][j] * d。换句话说,选择该商店中下标最大的物品j,该物品没有被购买过,以values[i][j] * d的价格购买
注意,所有物品都视为不同的物品。如果你已经从商店 1 购买了物品 0,你还可以在别的商店购买其他的物品 0。
返回购买所有 m x n 个物品需要的 最大开销。
示例 1:
输入:values = [[8,5,2],[6,4,1],[9,7,3]]
输出:285
解释:第一天,从商店 1 购买物品 2,价格为 values[1][2] * 1 = 1
第二天,从商店 0 购买物品 2,价格为 values[0][2] * 2 = 4
第三天,从商店 2 购买物品 2,价格为 values[2][2] * 3 = 9
第四天,从商店 1 购买物品 1,价格为 values[1][1] * 4 = 16
第五天,从商店 0 购买物品 1,价格为 values[0][1] * 5 = 25
第六天,从商店 1 购买物品 0,价格为 values[1][0] * 6 = 36
第七天,从商店 2 购买物品 1,价格为 values[2][1] * 7 = 49
第八天,从商店 0 购买物品 0,价格为 values[0][0] * 8 = 64
第九天,从商店 2 购买物品 0,价格为 values[2][0] * 9 = 81
因此,总开销为 285
示例 2:
输入:values = [[10,8,6,4,2],[9,7,5,3,2]]
输出:386
提示:
1 <= m == values.length <= 101 <= n == values[i].length <= 10^41 <= values[i][j] <= 10^6values[i]已经按非递增顺序排序
解题思路
解题思路
这道题的核心是要最大化总开销,而每天的开销为物品价值乘以天数。由于天数是递增的,我们需要合理安排购买顺序。
关键观察:
- 每天购买的开销 = 物品价值 × 当前天数
- 天数越大,同样价值的物品开销越高
- 要最大化总开销,应该让价值大的物品在后面的天数购买
贪心策略: 每天都选择当前所有商店中最右边物品值最小的那个物品购买。这样可以确保价值大的物品留到后面天数更大的时候购买,从而最大化总开销。
算法步骤:
- 使用最小堆维护每个商店当前最右边物品的值和对应的商店索引
- 每天从堆中取出最小值对应的物品购买
- 购买后将该商店的指针左移,如果还有物品则重新入堆
- 重复直到所有物品都被购买
推荐解法: 使用优先队列(最小堆)的贪心算法,时间复杂度最优且实现简洁。
另一种解法是将所有物品排序后按价值从小到大购买,但需要额外的空间存储所有物品。
代码实现
class Solution {
public:
long long maxSpending(vector<vector<int>>& values) {
int m = values.size(), n = values[0].size();
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
vector<int> pos(m, n - 1);
for (int i = 0; i < m; i++) {
pq.push({values[i][pos[i]], i});
}
long long result = 0;
int day = 1;
while (!pq.empty()) {
auto [val, shop] = pq.top();
pq.pop();
result += (long long)val * day;
day++;
pos[shop]--;
if (pos[shop] >= 0) {
pq.push({values[shop][pos[shop]], shop});
}
}
return result;
}
};
class Solution:
def maxSpending(self, values: List[List[int]]) -> int:
import heapq
m, n = len(values), len(values[0])
heap = []
pos = [n - 1] * m
for i in range(m):
heapq.heappush(heap, (values[i][pos[i]], i))
result = 0
day = 1
while heap:
val, shop = heapq.heappop(heap)
result += val * day
day += 1
pos[shop] -= 1
if pos[shop] >= 0:
heapq.heappush(heap, (values[shop][pos[shop]], shop))
return result
public class Solution {
public long MaxSpending(int[][] values) {
int m = values.Length, n = values[0].Length;
var pq = new PriorityQueue<(int val, int shop), int>();
int[] pos = new int[m];
for (int i = 0; i < m; i++) {
pos[i] = n - 1;
pq.Enqueue((values[i][pos[i]], i), values[i][pos[i]]);
}
long result = 0;
int day = 1;
while (pq.Count > 0) {
var (val, shop) = pq.Dequeue();
result += (long)val * day;
day++;
pos[shop]--;
if (pos[shop] >= 0) {
pq.Enqueue((values[shop][pos[shop]], shop), values[shop][pos[shop]]);
}
}
return result;
}
}
var maxSpending = function(values) {
const m = values.length, n = values[0].length;
const heap = new MinPriorityQueue({priority: x => x[0]});
const pos = new Array(m).fill(n - 1);
for (let i = 0; i < m; i++) {
heap.enqueue([values[i][pos[i]], i]);
}
let result = 0;
let day = 1;
while (!heap.isEmpty()) {
const [val, shop] = heap.dequeue().element;
result += val * day;
day++;
pos[shop]--;
if (pos[shop] >= 0) {
heap.enqueue([values[shop][pos[shop]], shop]);
}
}
return result;
};
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 优先队列贪心 | O(mn log m) | O(m) |
| 排序后贪心 | O(mn log(mn)) | O(mn) |
说明:
- 时间复杂度:每个物品都要进出堆一次,每次操作的复杂度为 O(log m)
- 空间复杂度:堆中最多存储 m 个元素,pos 数组需要 O(m) 空间