二分法
复杂度:O(N*logN)
模板一
int binarySearch(int[] nums, int target){
if(nums == null || nums.length == 0)
return -1;
int left = 0, right = nums.length - 1;
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
// End Condition: left > right
return -1;
}
1.1区分:
- 初始条件:left = 0, right = length-1
- 终止:left > right
- 向左查找:right = mid-1
- 向右查找:left = mid+1
- 查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
1.2例题
69. x 的平方根
简述:手写sqrt
int mySqrt(int x){
if( x == 0 || x == 1)
return x;
int left = 0 , right = x;
int ans = 0;
while( left <= right )
{
int mid = (left+right)/2;
if( (long long)mid *mid <= x )
{
ans = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
374. 猜数字大小
简述:猜数字
注意 : 原题的guess函数太过SB,原题乱tm翻译,一直wa
看了评论才发现
/**
* Forward declaration of guess API.
* @param num your guess
* @return -1 if num is lower than the guess number
* 1 if num is higher than the guess number
* otherwise return 0
* int guess(int num);
*/
int guessNumber(int n){
int left = 1 , right = n;
int mid = (left + right)/2;
while( left <= right )
{
int mid = left + (right-left)/2;
if( guess(mid) == -1)
{
right = mid - 1;
}
else if( guess(mid) == 1)
{
left = mid + 1;
}
else if( guess(mid) == 0)
{
return mid;
}
}
return -1;
}
33. 搜索旋转排序数组
思路:
该数组为旋转数组,就一定旋转,后面会有不旋转的(tmd)
我们的二分一般情况下喜欢有序数组(当然也有表面无序,实则有规则,但整体来说有序)
数字条件:不重复
我们要找有序的一侧,例如:
4,5,6,7,0,1,2
我们 [4-7] 和 [0-2]为有序,所以
mid = left + (right-left)/2;
//nums[mid] = 7;
if( nums[0] > nums[mid] ) //mid 左侧有序,单调递增
{
....
}
// nums[0] < nums[mid] , 所以[0-mid]数组走过下坡路
else //右侧有序,
{
...
}
这是一个点
然后我们在判断,我们的target是否包在我们的有序区间
1、在,就在这个区间缩小范围二分
2、不在,换区间
完
int search(int* nums, int numsSize, int target){
if( numsSize == 1) return nums[0] == target ? 0 : -1;
if( numsSize == 0)return -1;
int left = 0 , right = numsSize - 1;
int mid;
while(left <= right)
{
mid = left + (right-left)/2;
if(nums[mid] == target)
{
return mid;
}
if( nums[0] <= nums[mid] ) //左侧有序
{
if( nums[left] <= target && nums[mid] > target ) //有序的区间内
{
right = mid-1;
}
else //不在这个区间,换
{
left = mid + 1;
}
}
else //右侧有序
{
if( nums[mid] < target && nums[numsSize-1] >= target) //有序的区间内
{
left = mid + 1;
}
else //不在这个区间,换
{
right = mid - 1;
}
}
}
return -1;
}
模板二
int binarySearch(int[] nums, int target){
if(nums == null || nums.length == 0)
return -1;
int left = 0, right = nums.length;
while(left < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid; }
}
// Post-processing:
// End Condition: left == right
if(left != nums.length && nums[left] == target) return left;
return -1;
}
属性
- 查找条件需要访问元素的直接右邻居。
- 保证查找空间在每一步中至少有 2 个元素。
- 当你剩下 1 个元素时,退出。也就是 left 和 right 指向同一个
区别
- 初始条件:left = 0, right = length
- 终止:left == right
- 向左查找:right = mid
- 向右查找:left = mid+1
2.1例题
278. 第一个错误的版本
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
int firstBadVersion(int n) {
int left = 0 , right = n;
while( left < right )
{
int mid = left + (right-left)/2;
if( isBadVersion(mid) ) //true;
{
right = mid;
}
else
{
left = mid + 1;
}
}
return left;
}//啊这.....
//挺简单的没啥说的
162. 寻找峰值
思路:
1、对第一个元素和最后一个元素,特判一下
条件:
num[i] != num[i+1]
我们不妨假设下特殊情况
1 2 3 4 7 5 6 8 9 //ans = 7
1 2 3 1 4 5 7 8 //ans = 3 , ans = 8
Q : 为什么二分查找大的那一半一定会有峰值呢 ?(关键)
首先已知 nums[mid+1]>nums[mid] ,
那么mid+2只有两种可能:
1、是大于mid+1
2、是小于mid+1
小于mid+1的情况,那么mid+1就是峰值
大于mid+1的情况,继续向右推。
如果一直到数组的末尾都是大于的,那么可以肯定最后一个元素是峰值,因为nums[numsSize] = 负无穷
妥了。
int findPeakElement(int* nums, int numsSize ){
int l = 0, r = numsSize - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
r = mid;
else
l = mid + 1;
}
return l;
}
153. 寻找旋转排序数组中的最小值
思路:
它来了它来了,上面说可能不旋转的
wa全是单调数据
思路都差不多,千篇一律
不过,我们分为旋转和不旋转来讨论,用:
nums[0] < nums[numsSize-1]
nums[0] > nums[numsSize-1]
来判断单增还是单减
其他的和上面旋转的思路差不多
int findMin(int* nums, int numsSize){
if( numsSize == 1 )return nums[0];
int left = 0 , right = numsSize - 1;
while( left < right )
{
int mid = left + (right-left)/2;
//旋转
if( nums[0] < nums[mid] && nums[0] > nums[numsSize-1] )//左侧有序
{
left = mid + 1;
}
//旋转
else if( nums[mid] < nums[numsSize-1] && nums[0] > nums[numsSize-1] )
{
right = mid;
}
//单增
else if( nums[0] < nums[numsSize-1] )
{
right = mid;
}
//单减
else
{
left = mid + 1;
}
}
return nums[left];
}
模板三
不大常用
int binarySearch(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
int left = 0, right = nums.length - 1;
while (left + 1 < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
// Post-processing:
// End Condition: left + 1 == right
if(nums[left] == target) return left;
if(nums[right] == target) return right;
return -1;
}
区别
- 初始条件:left = 0, right = length-1
- 终止:left + 1 == right
- 向左查找:right = mid
- 向右查找:left = mid
例题
34. 在排序数组中查找元素的第一个和最后一个位置
思路
先找到相等的位置,只要找到我们就好说了,直接暴力法
二分法查找我们需要的元素,但凡找到一个
向左向右用循环,来扩散
最后写入答案
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int *ans = (int *)malloc(sizeof(int)*2);
ans[0] = -1;
ans[1] = -1;
int left = 0 , right = numsSize-1;
while( left <= right )
{
int mid = left + (right-left)/2;
if( nums[mid] < target )
{
left = mid + 1;
}
else if( nums[mid] > target )
{
right = mid - 1;
}
else
{
left = mid;
right = mid;
while( left >= 0 && nums[left] == target )
{
left--;
}
while( right < numsSize && nums[right] == target )
{
right++;
}
ans[0] = left + 1;
ans[1] = right - 1;
break;
}
}
*returnSize = 2;
return ans;
}
658. 找到 K 个最接近的元素
思路:
这次的二分不是下标二分了,而是值二分,因为我们的值有序
while( right - left >= k )
{
if( (arr[left] + arr[right]) >= x*2 )
{
right--;
}
else
{
left++;
}
}
我们的答案长度得是K啊
然后数组值二分
下标不能移动太狠了
然后对x的值特判一下,是不是在数组外,在数组外,直接就写入
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* findClosestElements(int* arr, int arrSize, int k, int x, int* returnSize){
int *ans = (int *)malloc(sizeof(int)*arrSize);
*returnSize = k;
if( x < arr[0] )
{
for(int i = 0 ; i < k ; i++)
{
ans[i] = arr[i];
}
return ans;
}
if( x > arr[arrSize-1] )
{
int index = 0;
for(int i = arrSize - k; i < arrSize ; i++ )
{
ans[index++] = arr[i];
}
return ans;
}
int left = 0 , right = arrSize-1;
while( right - left >= k )
{
if( (arr[left] + arr[right]) >= x*2 )
{
right--;
}
else
{
left++;
}
}
return &arr[left];
}
162. 寻找峰值
int findPeakElement(int* nums, int numsSize ){
int l = 0, r = numsSize - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
r = mid;
else
l = mid + 1;
}
return l;
}
总结
模板很重要,是做题的第一思路
不过题和题差别还是需要对要求和一些条件的理解,所以模板只是帮你快速分析以及构建框架
具体的内容,要以题目来拟定
模板 #1 (left <= right)
二分查找的最基础和最基本的形式。
查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。
模板 #2 (left < right)
一种实现二分查找的高级方法。
查找条件需要访问元素的直接右邻居。
使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
保证查找空间在每一步中至少有 2 个元素。
需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。
模板 #3 (left + 1 < right)
实现二分查找的另一种方法。
搜索条件需要访问元素的直接左右邻居。
使用元素的邻居来确定它是向右还是向左。
保证查找空间在每个步骤中至少有 3 个元素。
需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素