本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
一家商店正在打折销售糖果。每购买 两个 糖果,商店会 免费 送一个糖果。
免费送的糖果唯一的限制是:它的价格需要小于等于购买的两个糖果价格的 较小值 。
- 比方说,总共有
4个糖果,价格分别为1,2,3和4,一位顾客买了价格为2和3的糖果,那么他可以免费获得价格为1的糖果,但不能获得价格为4的糖果。
给你一个下标从 0 开始的整数数组 cost ,其中 cost[i] 表示第 i 个糖果的价格,请你返回获得 所有 糖果的 最小 总开销。
示例 1:
java
输入:cost = [1,2,3]
输出:5
解释:我们购买价格为 2 和 3 的糖果,然后免费获得价格为 1 的糖果。
总开销为 2 + 3 = 5 。这是开销最小的 唯一 方案。
注意,我们不能购买价格为 1 和 3 的糖果,并免费获得价格为 2 的糖果。
这是因为免费糖果的价格必须小于等于购买的 2 个糖果价格的较小值。
示例 2:
java
输入:cost = [6,5,7,9,2,2]
输出:23
解释:最小总开销购买糖果方案为:
- 购买价格为 9 和 7 的糖果
- 免费获得价格为 6 的糖果
- 购买价格为 5 和 2 的糖果
- 免费获得价格为 2 的最后一个糖果
因此,最小总开销为 9 + 7 + 5 + 2 = 23 。
示例 3:
java
输入:cost = [5,5]
输出:10
解释:由于只有 2 个糖果,我们需要将它们都购买,而且没有免费糖果。
所以总最小开销为 5 + 5 = 10 。
提示:
1 <= cost.length <= 1001 <= cost[i] <= 100
方法 排序,贪心
为什么要从大到小贪心?如何证明贪心是对的?
核心思路:计算免费糖果的价格和的上界,然后找到一个可以达到该上界的购买方案,从而说明上界是可以取到的。
总开销 = 所有糖果的价格和 - 免费糖果的价格和。由于所有糖果的价格和是个定值,所以最小化总开销,等价于最大化免费糖果的价格和。
设 a a a 是 c o s t cost cost 从大到小排序后的数组,下标从 1 1 1 开始。
- 设第一大免费糖果的价格为 f 1 f_1 f1 ,那么 f 1 f_1 f1 最大是多少?在免费获取 f 1 f_1 f1 之前,必须购买两个价格都大于等于 f 1 f_1 f1 的糖果,所以 f 1 f_1 f1 至多是第三大的糖果价格,即 f 1 ≤ a 3 f_1 \le a_3 f1≤a3 。
- 设第二大免费糖果的价格为 f 2 f_2 f2 ,那么 f 2 f_2 f2 最大是多少?在免费获取 f 2 f_2 f2 之前,必须购买四个价格都大于等于 f 2 f_2 f2 的糖果,以及免费获得一个价格大于等于 f 2 f_2 f2 的糖果,所以 f 2 f_2 f2 至多是第六大的糖果价格,即 f 2 ≤ a 6 f_2 \le a_6 f2≤a6 。
依次类推,我们有 f i ≤ a 3 i f_i \le a_{3i} fi≤a3i 。所以 最大免费糖果的价格和 ≤ a 3 + a 6 + ⋯ + a 3 k ( 3 k ≤ n ) 最大免费糖果的价格和 \le a_3 + a_6 + \dots + a_{3k}\quad (3k \le n) 最大免费糖果的价格和≤a3+a6+⋯+a3k(3k≤n) 右边这个上界是可以取到的:按照 c o s t cost cost 从大到小,买两个送一个,买两个送一个......直到获得所有糖果。
java
class Solution {
public int minimumCost(int[] cost) {
Arrays.sort(cost);
int ans = 0;
// 从大到小,买两个送一个
for (int i = cost.length - 1; i >= 0; i -= 3) {
ans += cost[i];
if (i > 0) {
ans += cost[i - 1];
}
}
return ans;
}
}
cpp
class Solution {
public:
int minimumCost(vector<int>& cost) {
ranges::sort(cost);
int ans = 0;
// 从大到小,买两个送一个
for (int i = cost.size() - 1; i >= 0; i -= 3) {
ans += cost[i];
if (i > 0) {
ans += cost[i - 1];
}
}
return ans;
}
};
python
class Solution:
def minimumCost(self, cost: list[int]) -> int:
cost.sort(reverse=True)
return sum(cost) - sum(cost[2::3]) # 买两个送一个
# 写法2
class Solution:
def minimumCost(self, cost: list[int]) -> int:
cost.sort()
ans = 0
# 从大到小,买两个送一个
for i in range(len(cost) - 1, -1, -3):
ans += cost[i]
if i > 0:
ans += cost[i - 1]
return ans
rust
impl Solution {
pub fn minimum_cost(mut cost: Vec<i32>) -> i32 {
cost.sort_unstable();
// 从大到小,买两个送一个
let mut ans = 0;
let mut i = cost.len() as i32 - 1;
while i >= 0 {
ans += cost[i as usize];
if i > 0 {
ans += cost[i as usize - 1];
}
i -= 3;
}
ans
}
}
go
func minimumCost(cost []int) (ans int) {
slices.Sort(cost)
// 从大到小,买两个送一个
for i := len(cost) - 1; i >= 0; i -= 3 {
ans += cost[i]
if i > 0 {
ans += cost[i-1]
}
}
return
}
js
var minimumCost = function(cost) {
cost.sort((a, b) => a - b);
let ans = 0;
// 从大到小,买两个送一个
for (let i = cost.length - 1; i >= 0; i -= 3) {
ans += cost[i];
if (i > 0) {
ans += cost[i - 1];
}
}
return ans;
};
c
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
int minimumCost(int* cost, int costSize) {
qsort(cost, costSize, sizeof(int), cmp);
int ans = 0;
// 从大到小,买两个送一个
for (int i = costSize - 1; i >= 0; i -= 3) {
ans += cost[i];
if (i > 0) {
ans += cost[i - 1];
}
}
return ans;
}
复杂度分析:
- 时间复杂度: O ( n log n ) O(n\log n) O(nlogn) ,其中 n n n 是 c o s t cost cost 的长度。瓶颈在排序上。
- 空间复杂度: O ( 1 ) O(1) O(1) 。忽略排序的栈开销。
专题训练
贪心题单的【1.1 从最小/最大开始贪心】。