class Solution {
public:
bool check(vector<int>& batteries,long long n,long long mid){
long long sum = 0;
for(int i = 0;i < batteries.size();++i){
sum += min((long long)batteries[i],mid);
}
return (sum / mid) >= n;
}
long long maxRunTime(int n, vector<int>& batteries) {
long long right = 0;
for(int i = 0;i < batteries.size();++i){
right += batteries[i];
}
long long left = 1;
while(left < right){
long long mid = left + (right - left + 1) / 2;
if(check(batteries,n,mid)){
left = mid;
}
else{
right = mid - 1;
}
}
return left;
}
};
题解:
假设:n台电脑同时最长运行t时间,每个电池供给时间是min(t,batteries[i]),因为电池电量大于等于t时,她所能提供的时间也最多是t(因为同时运行t时间),小于t时,他所能提供的时间最多就是自身能提供的最大电量,也就是说每一个电池都要用上,只是不一定全部耗光。
那么假设每个电池的使用时长是S,那么S >= t * n,t * n就是n个电脑同时运行t时间所要消耗的总电池时间,必须要小于等于电池所能给到的时间
代码:
使用二分法找到符合要求的同时运行时间,左边界是0,右边界是所有电池所能给到的总时间(因为这个是不可能提供的,因为每个电池只能同时给一个电脑供电,不可能所有电池都给一台电脑供电,当然在有一台电脑的情况下所有电池可以给)(有的题目会将右边界设置1e15,我认为这个值应该是可以随便设置的,只要它大于等于所有电池的时间总量即可)
二分法找到合适的最长运行时间mid,此时需要检查符不符合要求,就是前面提到的等式,当此时 S >= t * n时,S > t * n,证明此时t太小了,这样的分配方式可以给予更多的电脑同时运行,为了使得S / t变小,t需要更大,因此需要将左边界设置为mid + 1,又因为此时S == t * n时是我们需要的结果,因此边界范围内需要包含这个结果,所以left = mid;反之right = mid - 1;(这里有一个问题那就是S / t >= n时,为了让S / t变小,为什么t变大即可?t变大的时候由于S == min(t,barries[i]),这样S不也会变大吗?我认为那是因为首先t变大的时候并不是所有的电池提供时间都会变大,因为还有电池本身电量的限制,其次就是可以看作S就是一堆t和barries[i]的和,那么分母是t,可以化简为一条常数 + 常数 / t的式子,因此增大t就可以使整个式子变小)
这里可能会出现这样的错误:
1、测试用例有一些非常大的数,有的时候sum >= mid * n的时候可能会超出限制,就算没有超出限制也有可能在测试用例有着大量数位的数据时出错,这里就留意到了主函数返回值是long long,数值虽然是10的九次方以下,但sum可能就会超过int,所以改为long long
2、也是最关键的,就是二分查找的部分一定要用mid = left + (right - left + 1) / 2,不能用left + (right - left) / 2,原因在于判断条件,check里的判断条件是(sum / mid) >= n,当true的时候我们需要将mid变大,因此left = mid,否则就是right = mid - 1,这样的条件如果搭配了left + (right - left) / 2的话就会死循环,因此不行
[LeetCode] 774. Minimize Max Distance to Gas Station 最小化去加油站的最大距离
这题没有原题,因为没有plus会员。。。。。
思路也是和上图一样的
class Solution {
public:
double minmaxGasDist(vector<int>& stations, int K) {
double left = 0, right = 1e8;
while (right - left > 1e-6) {
double mid = left + (right - left) / 2;
if (helper(stations, K, mid)) right = mid;
else left = mid;
}
return left;
}
bool helper(vector<int>& stations, int K, double mid) {
int cnt = 0, n = stations.size();
for (int i = 0; i < n - 1; ++i) {
cnt += (stations[i + 1] - stations[i]) / mid;
}
return cnt <= K;
}
};
209.二维有序数组中第k个最小值(Kth Smallest Element in a Sorted Matrix)
寻找n*n有序二维数组中第K小的数
大致题意就是寻找二维数组中的第k大数字或者是第k小数字
只能说这种寻找最长最大、第几大小的题目除了贪心算法、动态规划,还得想想二分法(尤其是这种有着有序数组的题目)
有的时候题目如果觉得穷举可以解决,那就可以试试二分法,对答案可能出现的范围进行二分
下面这道题是求第k小的数字,如果是求第k大的数字,就将check中的遍历方向改为右上角往下
这题解的意思就是不管你求什么,反正我知道答案在一个区间内,这就行)。
第一种做法:就是将二维数组变成一维数组,也就是逐个遍历,时间复杂度为O(n2)
第二种做法:基于穷举的基础上,用二分法,最小数字是二维数组的第一个数字,最大数字是最后一个数字,所以范围就确定了,在这个范围内进行二分法,每得到一个可能的值,就check它是否在第k小个位置上
这里的check方法是:因为是找第k个小的数字,而现在数组的特性是下一行的数字比上一行大,下一列的数字比上一列大,因此我要计算比当前小的数字一共有多少个,从最大的一行的最小数字开始算起,也就是左下角,如果左下角数字小于等于当前数字,证明此时包括左下角一整列的数字都会小于当前数字,因此计数加上一整列的个数;如果大于,证明这一行的数字都大于当前数字,因此找到上一行当前列的数字进行比较。
O(nlogn)
#include <iostream>
#include <vector>
using namespace std;
/*
寻找有序二维数组(n * n)中第K小的数
*/
bool check(int(*matrix)[4], int mid, int k, int n) {
int i = n - 1;
int j = 0;
int num = 0;
while (i >= 0 && j < n) {
if (matrix[i][j] <= mid) {
num += i + 1;
j++;
}
else {
i--;
}
}
return num >= k;
}
int kthSmallest(int(*matrix)[4], int matrixSize, int k) {
int left = matrix[0][0];
int right = matrix[matrixSize - 1][matrixSize - 1];
while (left < right) {
int mid = left + ((right - left) >> 1);
if (check(matrix, mid, k, matrixSize)) {
right = mid;
}
else {
left = mid + 1;
}
}
return left;
}
int main() {
int nums[4][4] = { {1,2,3,4},
{9,10,11,12},
{13,14,15,16},
{20,22,31,41} };
int num;
while (1) {
cout << "select your num: ";
cin >> num;
if (num == -1||num > 16)break;
cout << kthSmallest(nums, 4, num) << endl;
}
return 0;
}
215
这道题就是一维数组找第k大数字
0、直接排序
1、堆 时间复杂度O(nlogn)又或者是O(nlogk)
2、快速选择 O(n)
这里就不能用二分法了,因为待排序列不是有序的,硬要做的话在check里就需要逐个遍历,时间复杂度就是nlogn