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 从最小/最大开始贪心】。

相关推荐
Purple Coder2 小时前
STM32基础(1)
职场和发展
散峰而望2 小时前
【算法练习】算法练习精选:陶陶摘苹果(基础+升级)、Music Notes、字串变换,你能AC几道?
数据结构·c++·算法·leetcode·贪心算法·github·动态规划
暗夜猎手-大魔王2 小时前
转载--Hermes Agent 04 | Agent 主循环:一次对话背后发生了什么
人工智能·python·算法
手写码匠3 小时前
华为云Flexus+DeepSeek征文|基于华为云Flexus X实例 + Dify + DeepSeek 构建企业级智能知识库问答系统实战
人工智能·深度学习·算法·aigc
吴可可1233 小时前
Win7上开发CAD2004自定义实体全解析
c++·算法
YXXY3133 小时前
二叉树中的深搜算法介绍
算法
zz34572981133 小时前
C语言中字符串常量存储位置
c语言·开发语言·算法·青少年编程
noipp3 小时前
推荐题目:洛谷 P16510 [GKS 2015 #C] gRanks
java·c语言·开发语言·c++·python·算法
菜菜的顾清寒3 小时前
力扣HOT100(50)动态规划-零钱兑换
算法·leetcode·动态规划