【C/C++】2234. 花园的最大总美丽值

508 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情


题目链接:2234. 花园的最大总美丽值

题目描述

Alice 是 n 个花园的园丁,她想通过种花,最大化她所有花园的总美丽值。

给你一个下标从 0 开始大小为 n 的整数数组 flowers ,其中 flowers[i] 是第 i 个花园里已经种的花的数目。已经种了的花 不能 移走。同时给你 newFlowers ,表示 Alice 额外可以种花的 最大数目 。同时给你的还有整数 target ,full 和 partial 。

如果一个花园有 至少 target 朵花,那么这个花园称为 完善的 ,花园的 总美丽值 为以下分数之

  • 完善 花园数目乘以 full.
  • 剩余 不完善 花园里,花的 最少数目 乘以 partial 。如果没有不完善花园,那么这一部分的值为 0 。 请你返回 Alice 种最多 newFlowers 朵花以后,能得到的 最大 总美丽值。

提示:

  • 1flowers.length1051 \leqslant flowers.length \leqslant 10^5
  • 1flowers[i],target1051 \leqslant flowers[i], target \leqslant 10^5
  • 1newFlowers10101 \leqslant newFlowers \leqslant 10^{10}
  • 1full,partial1051 \leqslant full, partial \leqslant 10^5

示例 1:

输入:flowers = [1,3,1,1], newFlowers = 7, target = 6, full = 12, partial = 1
输出:14
解释:Alice 可以按以下方案种花
- 在第 0 个花园种 2 朵花
- 在第 1 个花园种 3 朵花
- 在第 2 个花园种 1 朵花
- 在第 3 个花园种 1 朵花
花园里花的数目为 [3,6,2,2] 。总共种了 2 + 3 + 1 + 1 = 7 朵花。
只有 1 个花园是完善的。
不完善花园里花的最少数目是 2 。
所以总美丽值为 1 * 12 + 2 * 1 = 12 + 2 = 14 。
没有其他方案可以让花园总美丽值超过 14 。

示例 2:

输入:flowers = [2,4,5,3], newFlowers = 10, target = 5, full = 2, partial = 6
输出:30
解释:Alice 可以按以下方案种花
- 在第 0 个花园种 3 朵花
- 在第 1 个花园种 0 朵花
- 在第 2 个花园种 0 朵花
- 在第 3 个花园种 2 朵花
花园里花的数目为 [5,4,5,5] 。总共种了 3 + 0 + 0 + 2 = 5 朵花。
有 3 个花园是完善的。
不完善花园里花的最少数目为 4 。
所以总美丽值为 3 * 2 + 4 * 6 = 6 + 24 = 30 。
没有其他方案可以让花园总美丽值超过 30 。
注意,Alice可以让所有花园都变成完善的,但这样她的总美丽值反而更小。

题意整理

题目给了一个整数数组 flowersflowers[i] 表示第 i 个花园中花朵的数量,同时给了可以额外种植的花朵数量 newFlowers ,另外还有一些计算花园总美丽值的参数 target ,full 和 partial :

  • target :表示 完善 花园和 不完善 花园的分界线,花朵数大于等于 target 的花园为完善花园。
  • full 和 partial :将完善花园个数乘以 full,不完善花园中最少花朵数乘以 partial,两者相加后得到花园的 总美丽值 。 题目要求我们利用可操作的花朵数量 newFlowers 来使得花园总美丽值最大,问最大值为多少。

解题思路分析

首先观察题目数据范围,基本都在 10510^5 级别,newFlowers 达到了 101010^{10} ,需要注意 int 溢出问题。

由于 partial 的值可能很大,当我们提升不完善花园的花朵数下限时可能带来的收益(总美丽值)更高,所以我们不能贪心的去尽可能完善花园。

为了把每种可能的情况都考虑到,通常需要暴力 枚举 某些参数来保证每种情况都不重不漏。

这里考虑 枚举 完善花园的个数,从而得到不完善花园的个数,在枚举完善花园个数时可以贪心的从花朵数较多的开始枚举,也就是按照花朵数从大到小枚举,这样可以使得在完善的花园数相同时,剩余的可操作花朵数量最多,换句话说也就是用最少的花朵使得完善的花园数最多,这样剩下的可操作花朵数量也就更多。

  • 首先对初始的花园花朵数量按照从大到小排序。
  • 枚举完善花园个数,同时计算完善这些花园后剩余的可操作花朵数量。
  • 计算剩余可操作的花朵数量可以给其余不完善的花园花朵数量下限提升到多少。
  • 最后计算当前枚举下的总美丽值,记录最大总美丽值即可。

具体实现

花园最大美丽值.jpg

  1. 将整数数组 flowers 按照从大到小排序;
  2. 枚举i 个花园为完善花园,计算完善前 i 个花园所需的花朵数量,这里可以使用前缀和的思想进行优化。更具体的,提前处理每个花园的最大花朵数量为 target ,那么可以直接用 target * i - pre 求得完善前 i 个花园所需的花朵数量;
  3. 计算完善前 i 个花园后所剩的可操作花朵数量:newFlowers - (target * i - pre)
  4. 求不完善花园所能提升的花朵数量下限时,可以使用二分或者双指针两种方法,由于二分需要使用两次(二分查找不完善花园的下限花朵数,以及二分查找能够到达该下限花朵数的花园数量)时间复杂度为 O(nlognlogn)O(n * \log n * \log n),而 双指针 只需要 O(n)O(n),因为双指针只增不减,所以时间复杂度为 O(n)O(n)
  5. 双指针操作为:根据指针 i(完善前 i 个花园)求得指针 j(能够将花朵数小于等于花园 j 的花园填充至和第 j 个花园一样多),需要注意的是这里剩余的花朵数不能够将所有花园的花朵数提升至和第 j - 1 个花园一样多。
  6. 根据图我们可以看到,此时我们能够填满至第 j 个花园,但不能够填满至第 j - 1 个花园。再求得填充至第 j 个花园后剩余的花朵数还可以为这 n - j 个花园填充多少:用剩余花朵数除以花园个数即可。因此我们还可以给每个花园添加 restnj\lfloor \dfrac{\textit{rest}}{n-j} \rfloor 朵花,rest 表示填充至第 j 个花园后剩余的花朵数,且向下取整。
  7. 此时得到了完善 i 个花园的情况下所能够提升不完善花园的最大下限,维护答案最大值即可:ans = max(ans, full * i + partial * min(target - 1, flowers[j] + rest / (n - j)));

需要注意的是不完善花园的最大下限不能超过 target - 1,否则将变成完善花园。

复杂度分析:

  • 时间复杂度:O(nlogn)O(n \log n),排序需要 O(nlogn)O(n \log n),枚举完善花园以及双指针求得答案需要 O(n)O(n ),所以总的时间复杂度为 O(nlogn)O(n \log n)
  • 空间复杂度:O(logn)O(\log n),即为排序需要的栈空间。

代码实现

由于双指针解法优于二分,此处省略二分解法。

枚举 + 双指针

class Solution {
public:
    long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {
        //对flowers降序排序
        sort(flowers.begin(), flowers.end(), greater<int>());
        //处理大于target花园
        int n = flowers.size();
        for(int i = 0; i < n; i++) flowers[i] = min(flowers[i], target);
        long long ans = 0;
        //判断是否存可以全部完善,记录这种情况下的答案
        long long sum = 0;
        for(int i = 0; i < n; i++) sum += flowers[i];
        if((long long)target * n - sum <= newFlowers) ans = (long long)full * n;
        //顺便可以利用sum记录后缀和,不断减去前面的flowers[i]即可
        long long pre = 0;  //pre 记录前缀和
        int j = 0;  //j指针寻找临界点
        //枚举i
        for(int i = 0; i < n; i++){
            if(i != 0) pre += flowers[i - 1];
            if(flowers[i] == target) continue;
            //rest为填满前 i 个花园后剩余的花朵数量
            long long rest = newFlowers - ((long long)target * i - pre);
            //无法填满前i个花园
            if(rest < 0) break;
            //利用j找到填满j后面花园的临界点,注意保证j在i(包括i)之后
            while(j < i || (j < n && (long long)flowers[j] * (n - j) - sum > rest)){
                //不断更新后缀和
                sum -= flowers[j++];
            }
            //减去填满后缀所需得花朵数
            rest -= (long long)flowers[j] * (n - j) - sum;
            //更新答案
            ans = max(ans, (long long)full * i + (long long)partial * min((long long)target - 1, (long long)flowers[j] + (long long)rest / (n - j)));
        }
        return ans;
    }
};

总结

该题难点在于考虑 排序后枚举完善花园个数 ,核心思想为 枚举 ,因为需要考虑到每种情况不重不漏,使用枚举不失为一种策略。

其次在处理不完善花园的花朵数量下限时,二分和双指针都可以通过,但双指针的做法更优,这里对比二分和双指针两种做法的效率: 微信截图_20220524140139.png 微信截图_20220524140152.png 很明显双指针在执行用时上明显优于二分,内存消耗的优化是利用一个变量优化了前缀和数组。

结束语

任何人的成功都无法一蹴而就,每一个阶段的抵达,都离不开一步一个脚印的积累。只要不急不躁,耐心努力,保持对新事物的好奇,就是行进在成为更好自己的路上。慢慢来,别着急,生活终将为你备好所有的答案。