二分查找系列题

一.第一个错误的版本(二分基础应用)

 1.题目

 2.思路及算法(基础二分查找)

题目要求尽量减少调用检查接口的次数,所以不能对每个版本都调用检查接口,而是将调用检查接口的次数降到最低。

注意一个性质:当一个版本为正确版本,则该版本之前的所有版本均为正确版本;当一个版本为错误版本,则该版本之后的所有版本均为错误版本。

具体:将左右边界分别初始化为1和n;其中n是给定的版本数量。设定左右边界之后,每次我们都依据左右边界找到其中间的版本,检查其是否为正确版本。如果该版本为正确版本,那么第一个错误版本必然位于该版本的右侧,我们缩紧左边界;否则第一个错误版本必然位于该版本及该版本的左侧,我们缩紧右边界。

3.图解 

4.参考代码

int firstBadVersion(int n) {
    int right = n;
    int left = 1;
    while(left < right)
    {
        int mid = left + (right - left)/2;
        if(isBadVersion(mid))//此时mid >= bad
        { 
            right = mid;
        }
        else//此时的mid < bad
        {
            left = mid+1;
        }
    }
    return left;//right和left重合
}

二.正整数和负整数的最大计数

1.题目

2.算法与思路 

由于数组呈现非递减顺序,因此可通过二分查找定位第一个数值大于等于0的位置p1第一个数值大于等于1的下标p2。假定n表示数组长度,且数组下标从0,则负数的个数为p1,正数的个数为 n - p2,返回这两者的较大值即可。

3.参考代码 

int binarySearch(int* nums,int numsSize,int val)
{
    int left = 0,right = numsSize;
    while(left < right)
    {
        int mid = left+(right-left)/2;
        if(nums[mid]>=val)
        {
            right = mid;
        }
        else
        {
            left = mid+1;
        }
    }
    return left;
}
int maximumCount(int* nums, int numsSize) {
    int p1 = binarySearch(nums,numsSize,0);//找到第一个元素大于等于0的位置(负数)
    int p2 = binarySearch(nums,numsSize,1);//找到第一个元素大于等于1的位置(n-p2 == 正数)
    return p1 > numsSize-p2 ? p1:numsSize - p2;
}

三. 在排序数组中查找元素的第一个和最后一个位置

 1.题目

2.算法与思路

由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分查找来加速查找的过程。

考虑target开始和结束位置,其实我们要找的就是数组中“第一个等于target的位置”和“第一个大于target的位置减一”。

函数lowerBound找出数组中“第一个等于target的位置”,函数lowerBound2函数找出数组中“第一个大于target的位置”。

最后,因为target可能不存在数组中,因此我们需要重新校验我们得到的两个下标start和end,看是否符合条件,如果符合条件就返回[start,end],不符合就返回[-1,-1] 。 

3.图解

4.参考代码 

    // 闭区间写法
int lowerBound(int *nums, int numsSize, int target) {
    int left = 0, right = numsSize - 1; // 闭区间 [left, right]
    while (left <= right) { // 区间不为空
        // 循环不变量:
        // nums[left-1] < target
        // nums[right+1] >= target
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1; // 范围缩小到 [mid+1, right]
        } else {
            right = mid - 1; // 范围缩小到 [left, mid-1]
        }
    }
    return left;
}

    // 闭区间写法
int lowerBound2(int *nums, int numsSize, int target) {
    int left = 0, right = numsSize - 1; 
    while (left <= right) {       
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) {
            left = mid + 1; 
        } else {
            right = mid - 1; 
        }
    }
    return left;
}

int *searchRange(int *nums, int numsSize, int target, int *returnSize) {
    int *ans = malloc(2 * sizeof(int));
    *returnSize = 2;
    int start = lowerBound(nums, numsSize, target); // 使用其中一种写法即可
    if (start == numsSize || nums[start] != target) {
        ans[0] = -1; // nums 中没有 target
        ans[1] = -1;
        return ans;
    }
    // 如果 start 存在,那么 end 必定存在
    int end = lowerBound2(nums, numsSize, target)-1;
    ans[0] = start;
    ans[1] = end;
    return ans;
}

四.咒语和药水的成功对数

1.题目 

2.算法与思路 

首先,我们将potions数组进行排序,一遍能够利用有序性进行二分查找。然后对于每个咒语

spells[i],0 <= i <=n,其中n为数组spells的长度,我们计算出目标值target = 不小于

(success/spells[i])的最小整数。target代表了在当前咒语强度下,药水需要达到的最低强度。

接下来用二分查找在数组potions中找到第一个大于等于target的元素的索引res,进一步可以得到

此时表示成功组合的药水数量为potionsSize-res。

3.参考代码

static compar(const void * p1,const void* p2)
{
    return *(int*)p1 - *(int*)p2;
}

int binarySearch(int* nums,int numsSize,long long val)
{
    int ans = numsSize;
    int left = 0;
    int right = numsSize-1;
    while(left <= right)
    {
        int mid = left + (right-left)/2;
        if(nums[mid] > val)
        {
            right = mid-1;
            ans = mid;
        }
        else
        {
            left = mid+1;
        }
    }
    return ans;
}

int* successfulPairs(int* spells, int spellsSize, int* potions, int potionsSize, long long success, int* returnSize) 
{
    qsort(potions,potionsSize,sizeof(int),compar);
    int* res = (int*)calloc(spellsSize,sizeof(int));
    for(int i = 0;i < spellsSize;i++)
    {
        long long t = (success-1)/spells[i];
        res[i] = potionsSize - binarySearch(potions,potionsSize,t); 
    }
    *returnSize = spellsSize;
    return res;
}

五.和有限的最长序列

1.题目 

 2.算法与思路

由题意可知:nums的元素次序对结果无影响,因此我们对nums从小到大进行排序。显然和有限的最长子序列由最小的前几个数组成使用数组f保存nums的前缀和,其中,f[i] 表示前i个元素之和(不包括nums[i])。遍历queries,假设当前查询值为 q ,使用二分查找获取满足 f[i] > q 的最小 i,那么和小于等于q的最长子序列长度为 i - 1。

3.参考代码

static int compar (const void* a,const void* b)
{
    return *(int*)a - *(int*)b;
}
//二分找到满足f[j]>q的最小j
int binarySearch(int* nums,int numsSize,int val)
{
    int left = 1,right = numsSize;
    while(left < right)
    {
        int mid = left + (right-left)/2;
        if(nums[mid] > val)
        {
            right = mid;//开区间         
        }
        else
        {
            left = mid+1;
        }
    }
    return left;
}

int* answerQueries(int* nums, int numsSize, int* queries, int queriesSize, int* returnSize) {
    //从小到大排序
    qsort(nums,numsSize,sizeof(int),compar);
    int f[numsSize +1];
    f[0] = 0;//没有符合要求的子序列时,返回空子序列[0]
    for(int i = 0;i < numsSize;i++)
    {
        f[i + 1] = f[i] + nums[i];//f[i+1]表示前i+1个元素之和
    }
    int* answer = (int*)calloc(sizeof(int),queriesSize);
    for(int j = 0;j < queriesSize;j++)
    {//小于等于q的最长子序列长度就是j-1
        answer[j] = binarySearch(f,numsSize+1,queries[j])-1;
    }
    *returnSize = queriesSize;
    return answer;
}
  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值