二分查找
二分查找
二分查找,一般是在一个有序的数组上的,但不一定要在一个有序的数组上(比如寻找峰值问题),总之只要可以确定答案在某一个区间,就可以使用二分查找。
-
寻找峰值
解题思路
-
- 如果数组的大小是1,根据题目的要求,它一定就是峰值,直接返回
- 判断下标0和下标n - 1是不是峰值,如果是直接返回它们的下标
- 然后我们就可以二分了
现在我们来证明一下,为什么一定存在峰值,并且可以二分
所以不一定要在有序的数组上才能二分,只要能确定答案一定在某一侧,那么我们就可以去二分。
int findPeakElement(int* nums, int numsSize) {
if (numsSize == 1) {//如果只有一个数,那么这个数必定是峰值,返回下标0
return 0;
}
if (nums[0] > nums[1]) {//如果下标0的值大于下标1的值,那么下标0就是峰值,返回下标0
return 0;
}
if (nums[numsSize - 1] > nums[numsSize - 2]) {//如果最后一个数,大于最后第二个数,那么最后一个数就是峰值
return numsSize - 1;//返回最后一个数的下标
}
int l = 1;//已经验证过了0位置不是峰值点,所有从1开始
int r = numsSize - 2;//已经验证过了n - 1位置不是峰值点,所有从n - 2开始
int res = 0;
while(l <= r) {
int mid = l + (r - l >> 1);
if (nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) {
res = mid;
break;
}
else if (nums[mid] < nums[mid - 1]) {
r = mid - 1;
}
else if (nums[mid] < nums[mid + 1]) {
l = mid + 1;
}
}
return res;
}
-
在一个有序的数组中找>=target的最左位置
解题思路
- 因为是要找>=target的最左位置,那么如果中点的值也>=target,那么中点位置可能是最左位置,也可能不是,所以我们先记录>=target的位置,然后再往左边去二分
- 如果左边还有>=target的位置,那么不就代表刚刚的位置不是最左位置吗,更新最左位置。继续往左边去二分。
- 直到不能再二分了,最后返回我们记录最左位置的变量。
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define ll long long
#define sc scanf
#define pr printf
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 1000000
ll a[N];
ll find(ll *b, ll size, ll target);
int main(int argc, char* argv[])
{
ll n;
ll target;
sc("%lld%lld", &n, &target);
fr(i, 0 ,n) {
sc("%lld", a + i);
}
pr("%d", find(a, n, target));
return 0;
}
ll find(ll *b, ll size, ll target)
{
ll l = 0;
ll r = size - 1;
ll res = -1;//用来记录我们的最左位置,如果不存在就返回-1。
while(l <= r) {
ll mid = l + (r - l >> 1);
if (b[mid] >= target) {//如果中点>=target,那么它可能是最左位置
res = mid;//记录它的位置
r = mid - 1;//要找最左位置所以往左边去二分
}
else {
l = mid + 1;
}
}
return res;
}
找一个有序数组中 <= target 的最右位置,也是差不多的逻辑,只需要往右二分就行了。
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define ll long long
#define sc scanf
#define pr printf
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 1000000
ll a[N];
ll find(ll *b, ll size, ll target);
int main(int argc, char* argv[])
{
ll n;
ll target;
sc("%lld%lld", &n, &target);
fr(i, 0 ,n) {
sc("%lld", a + i);
}
pr("%d", find(a, n, target));
return 0;
}
ll find(ll *b, ll size, ll target)
{
ll l = 0;
ll r = size - 1;
ll res = -1;//用来记录最右位置
while(l <= r) {
ll mid = l + (r - l >> 1);
if (b[mid] >= target) {//如果中点的值>=target,那么它还是可能是最右位置所以记录中点的位置
res = mid;
l = mid + 1;//因为是要找到最右位置,所以我们需要往右二分,所以是l = mid + 1;
}
else {
l = mid + 1;
}
}
return res;
}
至此我们就实现了二分查找的大部分题目所需要的功能,并且我们要知道二分查找不一定是要在一个有序数组中进行二分查找的,只要我们能够确定答案在某一个区间,那么我们就可以使用二分查找。二分查找的时间复杂度是O(logN)。