一.第一个错误的版本(二分基础应用)
1.题目![](https://img-blog.csdnimg.cn/direct/715a67346a404a5fa0019093b4b19150.png)
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;
}