二分法解决力扣2141、 774、209(不知出处)

2141. 同时运行 N 台电脑的最长时间

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值