文章目录
- 1.基本的二分查找
- 2.使用二分 查找左右端点
- 2.1 左右端点
- 2.2 二分模板
- 3.搜索插入位置
- 4.x的平方根
- 5.山脉数组的顶峰
- 6.寻找峰值
- 7.寻找旋转排序数组中的最小值
对于二分查找,相信大家都再熟悉不过了。一旦数据是有序的,我们大概率会想到二分,但是有时候数据不是有序时,也可以使用二分。下面我们就来写几道关于二分的题目。
1.基本的二分查找
题目链接
二分的精髓就在于它一次就可以决定一半数据的归属,该题就是一个最简单的二分题。
由于left和right相遇时可能就是target,所以循环的条件应该是left <= right。
当left与right错开时,就说明没有target这个元素,返回-1。
如果数据太多,left和right较大时,我们计算mid时,left + right可能会溢出;
所以我们可以这样 mid = left + (right - left) / 2 ;
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 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
return mid;
}
return -1;
}
2.使用二分 查找左右端点
2.1 左右端点
题目链接
对于该题目,就是在朴素二分的基础上,让你在区间中选择两个位置,该位置标记了target元素的开始与结束。
但是我们这一题需要在朴素二分的基础上进行变化。
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
if(nums.size() == 0)
return {-1,-1};
int begin = -1;
int end = -1;
while(left < right)
{
int mid = left + (right - left) / 2;
//小于target,去掉[left,mid]区间
if(nums[mid] < target)
left = mid + 1;
//大于等于target,去掉(mid,right]
else
right = mid;
}
if(nums[left] != target)
return {-1,-1};
begin = left;
left = 0;
right = nums.size() - 1;
while(left < right)
{
int mid = left + (right - left + 1) / 2;
//小于等于target,去掉[left,mid)区间
if(nums[mid] <= target)
left = mid;
//大于target,去掉[mid,right]区间
else
right = mid - 1;
}
if(nums[left] != target)
return {-1,-1};
end = left;
return {begin,end};
}
2.2 二分模板
求左端点
while (left < right)
{
int mid = left + (right - left) / 2;
//小于target,去掉[left,mid]区间
if (...)
left = mid + 1;
//大于等于target,去掉(mid,right]
else
right = mid;
}
求右端点
while (left < right)
{
int mid = left + (right - left + 1) / 2;
//小于等于target,去掉[left,mid)区间
if ()
left = mid;
//大于target,去掉[mid,right]区间
else
right = mid - 1;
}
3.搜索插入位置
由于该题目也具有二段性,因此也使用二分。、
- 当left与right相遇时
- 如果 nums[left] < target,则表明数组中没有target元素,则target元素应插入到left后面
- 如果 nums[left] > target,则表明数组中没有target元素,left位置就是target要插入的位置
- 如果 nums[left] == target,则表明数组中有target元素,left位置就是target的下标
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] < target)
left = mid + 1;
else
right = mid;
}
if(nums[left] < target)
return left + 1;
else
return left;
}
4.x的平方根
题目链接
解法一:暴力查找
使用 i 依次遍历1-x的数,直到 i*i >= x时,就可以出现结果。为了防止溢出,应使用long long
int mySqrt(int x) {
for(long long i=1; i<=x; i++)
{
if(i* i == x)
return i;
else if(i *i > x)
return i-1;
}
return 0;
}
解法二:二分查找
对于1-x之间的数,它平方的结果具有二段性,要么> x,要么 <= x,因此可以使用二分优化暴力解法。
int mySqrt(int x) {
if(x == 0)
return 0;
int left = 1;
int right = x;
while(left < right)
{
long long mid = left + (right - left + 1) / 2;
if(mid * mid <= x)
left = mid;
else
right = mid - 1;
}
return left;
}
5.山脉数组的顶峰
题目链接
根据题目可知,山脉递增以后再递减,那么一定会存在相邻的两个数前者大于后者的情况,此时前者就是峰顶。
解法一:暴力求解
从i = 1下标依次遍历数组,如果nums[i-1] > nums[i],此时i-1位置就是峰顶。
解法二:二分求解
- 如果nums[i] > nums[i-1],那么nums[i]之前一定是递增的,峰值还再后面,因此可以排除 i 之前的区间 left = mid;
- 如果nums[i] < nums[i-1],那么i及后面的都不可能是峰值,right = mid - 1;
- 未避免死循环和mid-1越界,求mid时应选择后面的一个
int peakIndexInMountainArray(vector<int>& arr) {
int left = 0;
int right = arr.size()-1;
while(left < right)
{
int mid = left + (right - left + 1)/ 2;
if(arr[mid] > arr[mid-1])
left = mid;
else
right = mid - 1;
}
return left;
}
6.寻找峰值
任取⼀个点 i ,与前⼀个点 i - 1,会有如下两种情况:
- arr[i] > arr[i - 1] :此时
「右侧区域」⼀定会存在山峰(因为最右侧是负⽆穷)
,那么我们可以去右侧去寻找结果; - arr[i] < arr[i - 1] :此时
「左侧区域」⼀定会存在山峰(因为最左侧是负⽆穷)
,那么我们可以去左侧去寻找结果。 - 注意边界情况
int findPeakElement(vector<int>& nums)
{
int left = 0;
int right= nums.size()-1;
while(left < right)
{
//防止下标越界和死循环,mid总是取后一个
int mid = left + (right - left + 1) / 2;
if(nums[mid] > nums[mid-1])//去右边区域找
left = mid;
else
right = mid - 1;
}
return left;
}
7.寻找旋转排序数组中的最小值
题目链接
由于数组原本是一个有序的数组,那么旋转后数组就会变成含有两段有序元素的数组。
对于任意一个点 i 而言,我们就是要找C的位置
- 如果nums[i]落在AB区间中,也就是nums[i] > nums[D],此时应该去 [i+1,D]区间中找,即left = mid + 1
- 如果nums[i]落在CD区间中,也就是nums[i] < nums[D],此时应该去[C,i]区间中找,即right = mid;
- 当区间长度变为1时,就是结果
int findMin(vector<int>& nums) {
int n = nums.size()-1;
int left = 0;
int right = n;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] > nums[n])
left = mid + 1;
else
right = mid;
}
return nums[left];
}