LeetCode 2144. 打折购买糖果的最小开销【贪心】

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

一家商店正在打折销售糖果。每购买 两个 糖果,商店会 免费 送一个糖果。

免费送的糖果唯一的限制是:它的价格需要小于等于购买的两个糖果价格的 较小值

  • 比方说,总共有 4 个糖果,价格分别为 1234 ,一位顾客买了价格为 23 的糖果,那么他可以免费获得价格为 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 <= 100
  • 1 <= 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 从最小/最大开始贪心】。

相关推荐
复杂网络4 小时前
论最小 Agent 计算机的形态
算法
kisshyshy20 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2124 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack204 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法