二分查找:用于在有序数列中查找目标元素的位置
二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
如果可以自己选择区间的话建议使用左闭右闭区间
这里查找目标值有三种情况
这个比较简单,直接上代码
具体实现细节写在注释中
int BinarySearch(vector<int> nums, int target)
{
int n = nums.size();
int left = 0;
int right = n - 1; //前闭后闭区间
while (left <= right)
{
int mid = left + (right - left) / 2; //防止直接相加溢出
if (target == nums[mid]) //找到目标值,直接输出下标
return mid;
else if (target > nums[mid]) // target比nums[mid]大,目标值在mid右侧,更新区间为右侧区间
left = mid + 1;
else
right = mid - 1; // target比nums[mid]小,目标值在左侧,更新区间为左侧区间
}
return -1; //没有找到目标值,输出-1
}
int LowerTarget(vector<int> nums, int target)
{
int n = nums.size();
int left = 0;
int right = n - 1; //前闭后闭区间
while (left < right) //这里是通过不断缩小区间来找要求的值,所以当left==right时,目标值已经找到循环就应该退出了
{
int mid = left + (right - left + 1) / 2; //这里要加1,即mid要取高位的数,否则可能出现死循环
if (nums[mid] <= target) // 如果nums[mid]小于等于target,那么最终要求的值可能是mid也可能在mid的左侧,所以left=mid而不是mid+1
left = mid;
else
right = mid - 1; // nums[mid]大于target,应该在左侧区间查找,所以right=mid-1
}
//这里left与right相等,用哪个都一样
if (nums[left] > target) //因为要找的小于等于target的最大值,如果最终得到的值比target还大的话就代表数组中的所有元素都比target大,找不到目标值输出-1
return -1;
return left;
}
这里有三个点需要注意
while(left
int mid=left+(right-left+1)/2
if(nums[mid]<=target) left=mid
具体原因在代码注释中已经写清楚了,第一第三点比较好理解,第二点可以自己带入一组数据试一下
如:在[1,9,25]中找小于等于10的最大值
如果不加第二点
- left=0,right=2
- mid=1,nums[mid]=9,9
- left=1,right=2;mid=1出现死循环
加上第二点后
- left=0,right=2
- mid=1,nums[mid]=9,9
- left=1,right=2,mid=2,nums[mid]=25>target,right=mid-1=1
- left=right 退出循环
这个与上一个类似,对比上一个看即可
int UpperTarget(vector<int> nums, int target)
{
int n = nums.size();
int left = 0;
int right = n - 1; //前闭后闭区间
while (left < right) //这里是通过不断缩小区间来找要求的值,所以当left==right时,目标值已经找到循环就应该退出了
{
int mid = left + (right - left) / 2; // mid取低位的数,否则可能出现死循环
if (nums[mid] >= target) // 如果nums[mid]大于等于target,那么要求的值可能是mid也可能在mid的左侧,所以right=mid而不是mid-1
right = mid;
else
left = mid + 1; // nums[mid]小于target,应该在右侧区间查找,所以left=mid+1
}
//这里left与right相等,用哪个都一样
if (nums[left] < target) //因为要找的大于等于target的最小值,如果最终得到的值比target还小的话就代表数组中的所有元素都比target小,找不到目标值,输出-1
return -1;
return left;
}
C++中有两个内置函数lower_bound和upper_bound,都是使用二分查找实现的
在从小到大的排序数组中:
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound():
lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
核心代码:
while (left < right)
{
mid = left + (right - left) / 2;
if (nums[mid] >= target)
right = mid;
else
left = mid + 1;
}
在从小到大的排序数组中:
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载upper_bound():
upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
核心代码:
while (left < right)
{
mid = left + (right - left) / 2;
if (nums[mid] <= target)
left = mid + 1;
else
right = mid;
}
vector<int> nums1{1, 3, 5, 7};
cout << *lower_bound(nums1.begin(), nums1.end(), 3);//3
cout << *upper_bound(nums1.begin(), nums1.end(), 3);//5
vector<int> nums2{9, 6, 3, 1};
cout << *lower_bound(nums2.begin(), nums2.end(), 6, greater<int>());//6
cout << *upper_bound(nums2.begin(), nums2.end(), 6, greater<int>());//3
34. 在排序数组中查找元素的第一个和最后一个位置
33. 搜索旋转排序数组
74. 搜索二维矩阵
162. 寻找峰值