LeetCode 3296. 使山区高度为零的最少秒数

LeetCode 3296. 使山区高度为零的最少秒数

题目描述

给定一个整数 mountainHeight 表示山的高度,以及一个数组 workerTimes 表示每个工人降低单位高度所需的基础时间。工人降低高度的规则如下:

  • 工人降低第 1 个单位高度需要 workerTime 秒;
  • 降低第 2 个单位需要 2 * workerTime 秒;
  • 降低第 3 个单位需要 3 * workerTime 秒;
  • ......
  • 降低第 k 个单位需要 k * workerTime 秒。

因此,一个工人降低 k 个单位高度所需的总时间为:

time=workerTime×(1+2+⋯+k)=workerTime×k(k+1)2. \text{time} = \text{workerTime} \times (1 + 2 + \cdots + k) = \text{workerTime} \times \frac{k(k+1)}{2}. time=workerTime×(1+2+⋯+k)=workerTime×2k(k+1).

所有工人可以同时工作 ,目标是使所有工人降低的总高度至少mountainHeight。求所需的最少秒数。

思路分析

这是一个典型的二分答案 问题。我们直接二分最终的时间 T,判断在 T 秒内,所有工人最多能降低的总高度是否 ≥ mountainHeight。如果可行,则尝试更小的时间;否则需要增大时间。

如何判断给定时间 T 内工人能降低的高度?

对于某个工人,其基础时间为 t,我们需要找到最大的整数 k,使得该工人在 T 秒内能完成 k 个单位的工作,即:
t⋅k(k+1)2≤T. t \cdot \frac{k(k+1)}{2} \le T. t⋅2k(k+1)≤T.

变形得:
k(k+1)2≤Tt. \frac{k(k+1)}{2} \le \frac{T}{t}. 2k(k+1)≤tT.

work = T / t(注意这里除法是整数除法,但推导公式时用实数),则问题转化为求最大的 k 满足:
k(k+1)≤2⋅work. k(k+1) \le 2 \cdot work. k(k+1)≤2⋅work.

即:
k2+k−2⋅work≤0. k^2 + k - 2 \cdot work \le 0. k2+k−2⋅work≤0.

这是一个一元二次不等式,其正根为:
k=−1+1+8⋅work2. k = \frac{-1 + \sqrt{1 + 8 \cdot work}}{2}. k=2−1+1+8⋅work .

由于 k 必须是整数,我们取该值的整数部分(向下取整)。注意,由于浮点数计算可能存在精度误差,我们可以在开方后加上一个很小的 eps(如 1e-7)再取整,确保不会因为微小的舍入导致结果偏小。

得到每个工人的 k 后,累加所有工人的 k 值,即为总降低高度 cnt

二分上下界的确定

  • 下界 :至少需要 1 秒,取 l = 1
  • 上界 :考虑最坏情况------只用最慢的工人(即 maxWorkerTimes)独自完成全部 mountainHeight 个单位所需的时间。该工人降低全部高度所需时间为:
    maxWorkerTimes×mountainHeight×(mountainHeight+1)2. \text{maxWorkerTimes} \times \frac{\text{mountainHeight} \times (\text{mountainHeight} + 1)}{2}. maxWorkerTimes×2mountainHeight×(mountainHeight+1).
    这个值一定大于或等于实际最优解,因此可以作为安全的上界。

二分过程

在区间 [l, r] 上进行二分查找,每次取中点 mid,计算总降低高度 cnt

  • cnt >= mountainHeight,说明 mid 可行,记录答案并尝试更小的时间(r = mid - 1);
  • 否则,需要增大时间(l = mid + 1)。

最终 ans 即为最小秒数。

代码实现(C++)

cpp 复制代码
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

class Solution {
private:
    static constexpr double eps = 1e-7;  // 用于防止浮点误差

public:
    long long minNumberOfSeconds(int mountainHeight, vector<int>& workerTimes) {
        // 找到最慢工人的时间,用于计算二分上界
        int maxWorkerTimes = *max_element(workerTimes.begin(), workerTimes.end());
        
        // 二分上下界
        long long l = 1;
        long long r = static_cast<long long>(maxWorkerTimes) * mountainHeight * (mountainHeight + 1) / 2;
        long long ans = 0;

        while (l <= r) {
            long long mid = l + (r - l) / 2;  // 避免溢出
            long long totalHeight = 0;        // 所有工人在 mid 秒内能降低的总高度

            for (int t : workerTimes) {
                // work = mid / t 表示该工人能完成的"工作量"(即 k(k+1)/2 的最大值)
                long long work = mid / t;
                
                // 解二次方程求最大 k: k = floor( (-1 + sqrt(1 + 8*work)) / 2 )
                // 加 eps 避免浮点误差导致取整偏小
                long long k = static_cast<long long>((-1.0 + sqrt(1 + 8.0 * work)) / 2 + eps);
                totalHeight += k;
                
                // 如果已经超过目标高度,可以提前结束循环(优化)
                if (totalHeight >= mountainHeight) break;
            }

            if (totalHeight >= mountainHeight) {
                ans = mid;
                r = mid - 1;   // 尝试更小的时间
            } else {
                l = mid + 1;   // 时间不够,增大
            }
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度 :二分查找次数为 O(log⁡(上界))O(\log(\text{上界}))O(log(上界)),每次检查需要遍历所有工人(设工人数为 nnn),因此总时间复杂度为 O(nlog⁡(maxWorkerTimes⋅mountainHeight2))O(n \log(\text{maxWorkerTimes} \cdot \text{mountainHeight}^2))O(nlog(maxWorkerTimes⋅mountainHeight2))。在题目给定范围内可以接受。
  • 空间复杂度 :仅使用常数个变量,O(1)O(1)O(1)。

注意事项

  1. 整数溢出 :计算上界时,maxWorkerTimesmountainHeight 都可能较大,乘积可能超出 32 位整数范围,因此使用 long long 存储。
  2. 浮点精度 :公式 k=−1+1+8⋅work2k = \frac{-1 + \sqrt{1 + 8 \cdot \text{work}}}{2}k=2−1+1+8⋅work 涉及开方,由于浮点数运算可能产生微小误差,直接取整可能导致 k 比实际值小 1。加上一个很小的 eps(如 1e-7)可以修正这种误差,保证正确性。
  3. 提前退出 :在遍历工人时,如果 totalHeight 已经达到或超过 mountainHeight,可以提前结束循环,节省时间。
  4. 二分写法 :使用 l <= r 的闭区间写法,注意更新边界时 mid 已经检查过,因此 l = mid + 1r = mid - 1

总结

本题的关键在于将问题转化为二分答案,并利用二次方程求根快速计算每个工人在给定时间内的最大工作量。这种方法避免了直接模拟的复杂性,同时利用了数学推导,使算法高效且易于实现。

理解该题后,可以进一步思考如果工人降低单位时间不是等差数列而是其他规律(如指数增长),应如何调整判定函数。二分答案的思路依然通用,只需改变内部的计算公式即可。

相关推荐
会员源码网2 小时前
抽象数据类型(ADT):理论与实践的桥梁
算法
像污秽一样2 小时前
算法设计与分析-习题4.5
数据结构·算法·排序算法·剪枝
样例过了就是过了2 小时前
LeetCode热题100 全排列
数据结构·c++·算法·leetcode·dfs
2401_898075122 小时前
分布式系统监控工具
开发语言·c++·算法
程序员夏末2 小时前
【LeetCode | 第六篇】算法笔记
笔记·算法·leetcode
OKkankan3 小时前
撕 STL 系列:封装红黑树实现 mymap 和 myset
java·c++·算法
xh didida3 小时前
数据结构--实现链式结构二叉树
c语言·数据结构·算法
ab1515173 小时前
3.15二刷基础90、105、106、110
数据结构·c++·算法
C蔡博士3 小时前
最近点对问题(Closest Pair of Points)
java·python·算法