LeetCode 2594. Minimum Time to Repair Cars【数组,二分】1915

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

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

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

给你一个整数数组 ranks ,表示一些机械工的 能力值ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n^2 分钟内修好 n 辆车。

同时给你一个整数 cars ,表示总共需要修理的汽车数目。

请你返回修理所有汽车 最少 需要多少时间。

注意: 所有机械工可以同时修理汽车。

示例 1:

js 复制代码
输入:ranks = [4,2,3,1], cars = 10
输出:16
解释:
- 第一位机械工修 2 辆车,需要 4 * 2 * 2 = 16 分钟。
- 第二位机械工修 2 辆车,需要 2 * 2 * 2 = 8 分钟。
- 第三位机械工修 2 辆车,需要 3 * 2 * 2 = 12 分钟。
- 第四位机械工修 4 辆车,需要 1 * 4 * 4 = 16 分钟。
16 分钟是修理完所有车需要的最少时间。

示例 2:

js 复制代码
输入:ranks = [5,1,8], cars = 6
输出:16
解释:
- 第一位机械工修 1 辆车,需要 5 * 1 * 1 = 5 分钟。
- 第二位机械工修 4 辆车,需要 1 * 4 * 4 = 16 分钟。
- 第三位机械工修 1 辆车,需要 8 * 1 * 1 = 8 分钟。
16 分钟时修理完所有车需要的最少时间。

提示:

  • 1 <= ranks.length <= 10^5
  • 1 <= ranks[i] <= 100
  • 1 <= cars <= 10^6

解法 二分+优化

注意,能力值越低,修车越快。(按照英文描述, r a n k s i ranks_i ranksi 应该翻译成「排名」,排名越靠前,修车越快)。

如果已知修车的时间 t t t,我们可以算出每个人在 t t t 分钟内能修好多少辆车。例如一个能力值 r = 3 r=3 r=3 的人可以在 t = 16 t=16 t=16 分钟内修好 2 2 2 辆车,但无法修好 3 3 3 辆车。

根据题意,需要满足
r n 2 ≤ t rn^2 \le t rn2≤t解得
n ≤ t r n\le \sqrt {\dfrac{t}{r}} n≤rt

所以能力值为 r r r 的机械工最多可以修
⌊ t r ⌋ \left\lfloor\sqrt {\dfrac{t}{r}}\right\rfloor ⌊rt ⌋

​辆车。

累加每个人在 t t t 分钟内能修的车的个数,如果
∑ i = 0 i = n − 1 ⌊ t ranks [ i ] ⌋ ≥ cars \sum_{i=0}^{i=n-1} \left\lfloor\sqrt {\dfrac{t} {\textit{ranks}[i]} }\right\rfloor \ge \textit{cars} i=0∑i=n−1⌊ranks[i]t ⌋≥cars

则说明可以在 t t t 分钟修完所有车。

上式表明,t t t 越大,能修的车越多,越能满足要求。有了这样的单调性,我们就可以二分答案了。

二分上界为 min ⁡ ( ranks ) ⋅ cars 2 \min(\textit{ranks}) \cdot \textit{cars}^2 min(ranks)⋅cars2 ,即让能力值最低(修车最快)的人修好所有车所需要的时间

问:开方直接取整的做法是否会有精度误差?

答:代码中对整数开方,只要整数转浮点没有丢失精度(在 2 53 − 1 2^{53}-1 253−1 内),开方出来的整数部分就是正确的。具体可以参考 IEEE 754。

cpp 复制代码
class Solution {
public:
    long long repairCars(vector<int> &ranks, int cars) {
        int min_r = *min_element(ranks.begin(), ranks.end());
        long long left = 0, right = 1LL * min_r * cars * cars;
        while (left + 1 < right) { // 开区间
            long long mid = (left + right) / 2, s = 0;
            for (int r : ranks)
                s += sqrt(mid / r);
            (s >= cars ? right : left) = mid;
        }
        return right;
    }
};

复杂度分析:

  • 时间复杂度: O ( n log ⁡ ( m c 2 ) ) \mathcal{O}(n\log(mc^2)) O(nlog(mc2)) ,其中 n n n 为 ranks \textit{ranks} ranks 的长度, m = min ⁡ ( ranks ) m=\min(\textit{ranks}) m=min(ranks) , c = cars c=\textit{cars} c=cars 。
  • 空间复杂度: O ( 1 ) \mathcal{O}(1) O(1) 。仅用到若干额外变量。

解法2 优化

能力值相同的人,在 t t t 分钟内修好的车的个数是一样的。

根据数据范围, ranks \textit{ranks} ranks 中至多有 100100100 个不同的数字,我们可以统计 r a n k s ranks ranks 中每个数字的出现次数,这样每次二分中至多循环 100 100 100 次。

此外,如果循环中发现 s ≥ cars s\ge \textit{cars} s≥cars ,可以提前退出循环

cpp 复制代码
class Solution {
public:
    long long repairCars(vector<int> &ranks, int cars) {
        int min_r = ranks[0], cnt[101]{}; // 数组比哈希表更快
        for (int r : ranks) {
            min_r = min(min_r, r);
            cnt[r]++;
        }
        long long left = 0, right = 1LL * min_r * cars * cars;
        while (left + 1 < right) {
            long long mid = (left + right) / 2, s = 0;
            for (int r = min_r; r <= 100 && s < cars; r++) // 至多循环 100 次
                s += (long long) sqrt(mid / r) * cnt[r];
            (s >= cars ? right : left) = mid;
        }
        return right;
    }
};

复杂度分析:

  • 时间复杂度: O ( n + M log ⁡ ( m c 2 ) ) \mathcal{O}(n + M\log(mc^2)) O(n+Mlog(mc2)) ,其中 n n n 为 ranks \textit{ranks} ranks 的长度 M = max ⁡ ( ranks ) M=\max(\textit{ranks}) M=max(ranks) , m = min ⁡ ( ranks ) m=\min(\textit{ranks}) m=min(ranks) , c = c a r s c=cars c=cars 。
  • 空间复杂度: O ( M ) \mathcal{O}(M) O(M) 。
相关推荐
The_Ticker2 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
Lenyiin35 分钟前
02.06、回文链表
数据结构·leetcode·链表
爪哇学长38 分钟前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan42 分钟前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
烦躁的大鼻嘎1 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝1 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
祁思妙想2 小时前
10.《滑动窗口篇》---②长度最小的子数组(中等)
leetcode·哈希算法
用户37791362947552 小时前
【循环神经网络】只会Python,也能让AI写出周杰伦风格的歌词
人工智能·算法
福大大架构师每日一题2 小时前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言