【C/C++】2335. 装满杯子需要的最短总时长

205 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情


题目链接:2335. 装满杯子需要的最短总时长

题目描述

现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。

给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]amount[1]amount[2] 分别表示需要装满冷水、温水和热水的杯子数量。返回装满所有杯子所需的 最少 秒数。

提示:

  • amount.length == 3
  • 0 <= amount[i] <= 100

示例 1:

输入:amount = [1,4,2]
输出:4
解释:下面给出一种方案:
第 1 秒:装满一杯冷水和一杯温水。
第 2 秒:装满一杯温水和一杯热水。
第 3 秒:装满一杯温水和一杯热水。
第 4 秒:装满一杯温水。
可以证明最少需要 4 秒才能装满所有杯子。

示例 2:

输入:amount = [5,4,4]
输出:7
解释:下面给出一种方案:
第 1 秒:装满一杯冷水和一杯热水。
第 2 秒:装满一杯冷水和一杯温水。
第 3 秒:装满一杯冷水和一杯温水。
第 4 秒:装满一杯温水和一杯热水。
第 5 秒:装满一杯冷水和一杯热水。
第 6 秒:装满一杯冷水和一杯温水。
第 7 秒:装满一杯热水。

示例 3:

输入: amount = [5,0,0]
输出: 5
解释: 每秒装满一杯冷水。

整理题意

题目规定现在有一台饮水机,每秒钟可以选择装满一杯任意类型的水或者装满两杯任意两种不同类型的水,现在给定三种类型的水各需要多少杯,问装满所有杯子所需的 最少 秒数。

三种水需要的数量以数组 amount 形式给出,其中 amount[0]amount[1]amount[2] 分别表示需要装满冷水、温水和热水的杯子数量。

解题思路分析

因为题目要求最少时间,所以每秒钟尽可能的都要选择装满两种不同类型的水,来使得使用的时间最少。但是考虑到如果仅剩一种类型的水,那么剩下的每秒钟就只能装一杯一种类型的水,所以为了避免这种情况发生,我们需要尽可能的先装种类需求数量大的水,那么可以 贪心 的每次选取需求量最大的两种类型的水进行装水。

那么很容易想到使用 优先队列 来完成这个模拟过程。

优化

需求数量很大时,模拟所需要的时间也非常大,我们可以进一步考虑,因为核心思想为:尽可能每次消耗最大的两种类型的水。那么我们先将三种类型所需的杯数进行排序,这里假设三种类型所需的杯数进行排序后为:x < y < z,那么此时可以进行分类讨论:

  • x + y <= z 时,说明较小的两种加起来都无法消耗完最大的一种,此时所需的秒数为 z
  • x + y > z 时,此时考虑剩余部分 t = (x + y) - z,我们首先将 xy 进行内部消耗,这里是为了尽可能的使得 x + y = z,但是由于 t 可能是奇数,这会导致最后 x + y - 1 = z,所以这里的 t 又分为两种情况,为偶数的时候和为奇数的时候:
    • t 为偶数的时候,xy 可以先内部消耗至 x + y = z,此时所需的装水时间为:t / 2 + z
    • t 为奇数的时候,xy 可以先内部消耗至 x + y - 1 = z,此时所需的装水时间为:t / 2 + z + 1

这里因为 x + y > z 所以不存在 x 消耗完后 y 仍然大于 z 的情况,所以必定可以内部消耗至 x + y = zx + y - 1 = z 的情况。

具体实现

优先队列

  1. 将三种类型水的数量分别放入大顶堆的优先队列;
  2. 每次取出优先队列中前两种类型的水进行消耗,如果队列中仅剩余一种类型的水,那么此时仅需将答案加上剩余类型水的数量即可。

贪心 + 分类讨论

  1. 对三种类型的水数量进行排序;
  2. 分类讨论两种情况下所需的装水时间。

复杂度分析

  • 时间复杂度:O(1)O(1),采用 贪心 + 分类讨论 的方法时间复杂度为 O(1)O(1)
  • 空间复杂度:O(1)O(1),采用 贪心 + 分类讨论 的方法仅需常数空间。

代码实现

class Solution {
public:
    int fillCups(vector<int>& amount) {
        // 排序后分类讨论
        sort(amount.begin(), amount.end());
        // 较小的两种加起来都无法消耗完最大的一种
        if(amount[0] + amount[1] <= amount[2]) return amount[2];
        // 较小的两种加起来大于最大的一种
        // 考虑超出部分 t = (amount[0] + amount[1]) - amount[2];
        // 不存在最小的消耗完了 任然大于最后一种
        int t = (amount[0] + amount[1]) - amount[2];
        if(t & 1) return t / 2 + amount[2] + 1;
        else return t / 2 + amount[2];
    }
};

总结

  • 由于该题数据范围较小,采用 优先队列暴力模拟贪心+分类讨论 两种方法在时间复杂度上没有明显的差异,但是当数据范围增加时,两种方法的时间复杂度和空间复杂度将拉开差异。
  • 测试结果:

6112.png

结束语

生活中,我们每个人都会有烦恼和忧愁,都曾经历挫折与不顺。阳光的人并不是万事如意,而是他们明白,击败苦难的不会是沮丧与抱怨,而是乐观与坚持。顺境也好,逆境也罢,保持一份积极向上的心态,才能走得更加稳健。新的一天,加油!