Day03-二分系列之-二分答案
给大家推荐一下咱们的 陪伴打卡小屋 知识星球啦,详细介绍 =>笔试刷题陪伴小屋-打卡赢价值丰厚奖励 <=
⏰小屋将在每日上午发放打卡题目,包括:
- 一道该算法的模版题 (主要以力扣,牛客,acwing等其他OJ网站的题目作为模版)
- 一道该算法的应用题(主要以往期互联网大厂 笔试真题 的形式出现,评测在咱们的 笔试突围OJ)
小屋day03
咱们的二分系列用的都是 day02 的二分模版~
二分模版介绍:二分模版
有小伙伴可能会问了,既然系统有自带的二分库,为什么我们还要自己手写二分呢?清隆认为主要有以下两点:
- 在二分的其他应用中,手写二分更利于设置 check 条件和实现具体逻辑
- 加深对二分的理解和提高代码熟练度
今天给大家带来二分答案相关的题
前言
什么是二分答案?
说直白点其实就是先 猜 一个答案,然后通过check函数来检查这个答案 是否合法,继而跟新二分的左右端点。
能够二分答案的题需要题目满足具有一定的性质,如单调性、二段性等等。
🎀 模版题
2187. 完成旅途的最少时间
题目描述
给你一个数组 time
,其中 time[i]
表示第 i
辆公交车完成 一趟 旅途 所需要花费的时间。
每辆公交车可以 连续 完成多趟旅途,也就是说,一辆公交车当前旅途完成后,可以 立马开始 下一趟旅途。每辆公交车 独立 运行,也就是说可以同时有多辆公交车在运行且互不影响。
给你一个整数 totalTrips
,表示所有公交车 总共 需要完成的旅途数目。请你返回完成 至少 totalTrips
趟旅途需要花费的 最少 时间。
示例 1:
输入:time = [1,2,3], totalTrips = 5
输出:3
解释:
- 时刻 t = 1 ,每辆公交车完成的旅途数分别为 [1,0,0] 。已完成的总旅途数为 1 + 0 + 0 = 1 。
- 时刻 t = 2 ,每辆公交车完成的旅途数分别为 [2,1,0] 。已完成的总旅途数为 2 + 1 + 0 = 3 。
- 时刻 t = 3 ,每辆公交车完成的旅途数分别为 [3,1,1] 。已完成的总旅途数为 3 + 1 + 1 = 5 。
所以总共完成至少 5 趟旅途的最少时间为 3 。
示例 2:
输入:time = [2], totalTrips = 1
输出:2
解释:
只有一辆公交车,它将在时刻 t = 2 完成第一趟旅途。
所以完成 1 趟旅途的最少时间为 2 。
解题思路
时间越多,可以完成的旅途也就越多,有 单调性,可以二分答案。
在二分之前需要设置区间左右端。
-
左端点:答案的最小可能值
-
右端点:答案的最大可能值
-
当然实际运用中,左端点可以设置的更小点也没事,右端点同理
现在我们尝试来进行 猜 答案,假设当前答案为 X:
那么可以完成的旅途数量为: ∑ i = 0 n − 1 ⌊ x t i m e [ i ] ⌋ \sum_{i=0}^{n - 1} \lfloor \frac{x}{time[i]} \rfloor ∑i=0n−1⌊time[i]x⌋
如果比 totalTrips
大了,说明 X 还能继续缩小,那么跟新(左移)右端点,否则跟新(右移)左端点。
此时我们发现在 check 函数判断之后 需要跟新的是右端点,所以使用 第一个二分模版
时间复杂度:O(nlogU),其中 n 为time 的长度,U 为 二分设置的区间长度
参考代码
-
Python
class Solution:def minimumTime(self, time: List[int], totalTrips: int) -> int:l, r = 0, int(1e18) + 10 # 设置左右端点def check(x: int) -> bool:sum = 0for t in time:sum += x // tif sum >= totalTrips:return Truereturn Falsewhile l < r:mid = (l + r) // 2if check(mid):r = midelse:l = mid + 1return l
-
Java
import java.util.List;class Solution {public long minimumTime(int[] time, int totalTrips) {long l = 0, r = (long)1e18 + 10; // 设置左右端点while (l < r) {long mid = l + (r - l) / 2;if (check(time, mid, totalTrips)) {r = mid;} else {l = mid + 1;}}return l;}private boolean check(int[] time, long x, int totalTrips) {long sum = 0;for (int t : time) {sum += x / t;if (sum >= totalTrips) {return true;}}return false;} }
-
Cpp
class Solution { public:long long minimumTime(vector<int>& time, int totalTrips) {long long l = 0, r = 1e18 + 10; // 设置左右端点auto check = [&](long long x) -> bool{long long sum = 0;for(int t : time){sum += x / t;if(sum >= totalTrips) return true;}return false;};while(l < r){long long mid = l + r >> 1; if(check(mid)) r = mid;elsel = mid + 1;}return l;} };
🍰 笔试真题
- 该题来自今年 华为春招 的笔试题,出现在笔试第一题。
K小姐的购物系统调度
评测链接🔗
问题描述
K小姐负责维护一个购物系统,该系统面临着来自多个客户端的请求。为了应对系统的性能瓶颈,需要实现一个降级策略,以防止系统超负荷。
系统需要确定一个请求调用量的最大阈值 v a l u e value value。如果所有客户端的请求总量未超过系统的最大承载量 c n t cnt cnt,则所有请求均可正常处理,此时返回 − 1 -1 −1。否则,超过 v a l u e value value 的客户端调用量需要被限制在 v a l u e value value,而未超过 v a l u e value value 的客户端可以正常请求。要求计算可以接受的最大 v a l u e value value,以便尽可能地满足更多的请求。
输入格式
第一行包含 n n n 个空格分隔的正整数 R 1 , R 2 , . . . , R n R_1, R_2, ..., R_n R1,R2,...,Rn,表示每个客户端在一定时间内发送的交易请求数量。
第二行包含一个正整数 c n t cnt cnt,表示购物系统的最大调用量。
输出格式
输出一个整数,表示系统能够接受的最大请求调用量的阈值 v a l u e value value。
样例输入
1 4 2 5 5 1 6
13
样例输出
2
解释说明
若将 v a l u e value value 设置成 6 6 6, 1 + 4 + 2 + 5 + 5 + 1 + 6 > 13 1+4+2+5+5+1+6>13 1+4+2+5+5+1+6>13 不符合。
将 v a l u e value value 设置为 2 2 2 , 1 + 2 + 2 + 2 + 2 + 1 + 2 = 12 < 13 1+2+2+2+2+1+2=12<13 1+2+2+2+2+1+2=12<13 符合。
可以证明 v a l u e value value 最大为 2 2 2。
评测数据与规模
- 0 < n ≤ 1 0 5 0 < n \le 10^5 0<n≤105
- 0 ≤ R i ≤ 1 0 5 0 \le R_i \le 10^5 0≤Ri≤105
- 0 ≤ c n t ≤ 1 0 9 0 \le cnt \le 10^9 0≤cnt≤109
题解
我们先来判断 题目答案是否有 单调性
- 当阈值 v a l u e value value 变小时,请求量会被限制会变多,总的请求量就会减小
- 当阈值 v a l u e value value 变大时,请求量会被限制会变少,总的请求量就会变大.
所以 v a l u e value value 最大,答案最大,因此答案具有 单调性
因此我们可以尝试 猜 一个答案 x x x
- 设置 x x x 的左右边界, 左边界可以设置为 0 0 0 ,右边界最大为 m a x ( R i ) max(R_i) max(Ri),其中 0 < i < n 0 < i < n 0<i<n。
- 每次通过 x x x ,对当前的请求量求和,并和 c n t cnt cnt 做比较。
- 判断当前 x x x 是否满足条件,如果满足,则尝试猜更大的 x x x,即跟新(右移)左端点
- 因为是右移左端点,所以这里采用二分的 第二个模版
时间复杂度: O ( n log ( n ) ) O(n\log(n)) O(nlog(n))
AC代码
- Java
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);List<Integer> list = new ArrayList<>();// 读取所有输入的整数while (sc.hasNextInt()) {int x = sc.nextInt();list.add(x);}int[] nums = new int[list.size()];// 将List转为数组for (int i = 0; i < list.size(); i++) {nums[i] = list.get(i);}int left = 0;int maxv = Arrays.stream(nums).max().getAsInt(); // 找到数组中的最大值int right = maxv;// 二分查找最大值while (left < right) {int mid = (left + right + 1) / 2;if (check(nums, mid)) {left = mid;} else {right = mid - 1;}}// 输出结果if (right == maxv) {System.out.println(-1);} else {System.out.println(left);}}// 检查是否满足条件private static boolean check(int[] nums, int value) {long sum = 0;for (int i = 0; i < nums.length - 1; i++) {sum += Math.min(nums[i], value);}return sum <= nums[nums.length - 1];}
}
-
Cpp
#include <iostream> #include <vector> #include <algorithm> using namespace std;bool check(vector<int>& nums, int value) {long long sum = 0;for (int i = 0; i < nums.size() - 1; ++i) {sum += min(nums[i], value);}return sum <= nums.back(); // nums.back() 返回最后一个元素 }int main() {vector<int> nums;int x;// 读取所有输入的整数while (cin >> x) {nums.push_back(x);}int left = 0;int maxv = *max_element(nums.begin(), nums.end()); // 找到数组中的最大值int right = maxv;// 二分查找最大值while (left < right) {int mid = (left + right + 1) / 2;if (check(nums, mid)) {left = mid;} else {right = mid - 1;}}// 输出结果if (right == maxv) {cout << -1 << endl;} else {cout << left << endl;}return 0; }
-
Python
def check(nums, value):total = 0for num in nums[:-1]: # 排除最后一个元素total += min(num, value)return total <= nums[-1] # 检查条件是否满足def main():import sysinput = sys.stdin.readnums = list(map(int, input().split())) # 读取所有输入的整数left = 0maxv = max(nums) # 找到数组中的最大值right = maxv# 二分查找最大值while left < right:mid = (left + right + 1) // 2if check(nums, mid):left = midelse:right = mid - 1# 输出结果if right == maxv:print(-1)else:print(left)if __name__ == "__main__":main()