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 = 0 for t in time: sum += x // t if sum >= totalTrips: return True return False while l < r: mid = (l + r) // 2 if check(mid): r = mid else: l = mid + 1 return 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; else l = 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 = 0 for num in nums[:-1]: # 排除最后一个元素 total += min(num, value) return total <= nums[-1] # 检查条件是否满足 def main(): import sys input = sys.stdin.read nums = list(map(int, input().split())) # 读取所有输入的整数 left = 0 maxv = max(nums) # 找到数组中的最大值 right = maxv # 二分查找最大值 while left < right: mid = (left + right + 1) // 2 if check(nums, mid): left = mid else: right = mid - 1 # 输出结果 if right == maxv: print(-1) else: print(left) if __name__ == "__main__": main()