二分查找是一种可以大大减少时间复杂度的算法,使用时数组需要有二段性(左右两侧不一样),那么,来刷题
可以提炼模板的一道题,注意mid的算法防止溢出
class Solution {
public:
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(target==nums[mid])
{
return mid;
}
else if(target>nums[mid])
{
left=mid+1;
}
else{
right=mid-1;
}
}
return -1;
}
};
2.34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
上一题我们得出了求中间节点的模板为
while(left<=right)
{
int mid=left+(right-left)/2;(防止溢出)
if()
{
left=mid+1;
}
else if()
{
right=mid-1;
}
else
{
}
}
现在我们来解决寻找左右节点的问题,首先,这道题由于是寻找最左侧和最右侧的节点,所以不能让left==right的情况进入循环,这样会导致死循环,所以条件为left<right,并且,如果存在寻找左右两个节点的情况,那么left+(right-left)/2和left+(right-left+1)/2就格外重要,这决定了mid是向左取整还是向右取整,我们的思路为查找某一侧的节点,就让另一侧节点移动,为了防止移动错过了目标节点,需要将其移动到mid位置,而结束条件为left与right相遇,这里相遇的关键在于让异侧的指针必须指向目标位置,所以要让异侧的指针想办法多走一步,所以找左左侧取整,找右右侧取整,以下为找左和右的模板
while(left<right)
{
int mid=left+(right-left)/2;
if()
{
left=mid+1;
}
else
{
}right=mid;
}
while(left<right)
{
int mid=left+(right-left+1)/2;
if()
{
left=mid;
}
else
{
}right=mid-1;
}
完整代码
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
int begin=0;
if(nums.size()==0)
{
return {-1,-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 {-1,-1};
}
else
{
begin=left;
}
right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]<=target)
{
left=mid;
}
else
{
right=mid-1;
}
}
return{begin,right};
}
};
这是一个明显的找左节点的问题,这个题注意一点,就是注意模板中left+(right-left+1)与mid-1的绑定
class Solution {
public:
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)
{
right=mid-1;
}
else
{
left=mid;
}
}
return left;
}
};
这个题很明显是找右
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
if(nums[left]>=target)
{
return 0;
}
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]<target)
{
left=mid;
}
else
{
right=mid-1;
}
}
return right+1;
}
};
5.852. 山脉数组的峰顶索引 - 力扣(LeetCode)
这个不是经典的有序数组,但是是一个二段性数组,这个找左或者找右模板都可以
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int left=1;
int right=arr.size()-2;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(arr[mid]>arr[mid-1])
{
left=mid;
}
else
{
right=mid-1;
}
}
return left;
}
};
这个二段性比较难发现,left与right是由峰顶的寻找移动的
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]>nums[mid+1])
{
right=mid;
}
else
{
left=mid+1;
}
}
return left;
}
};
7.153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
这个题的重点在于关系不一定是相邻元素,也可能是其他关系,比如最后一个元素,这一点可以通过样例观察得出
class Solution {
public:
int findMin(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
int x=nums[right];
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]>x)
{
left=mid+1;
}
else
{
right=mid;
}
}
return nums[right];
}
};
这道题要使用二分做也很简单
class Solution {
public:
int missingNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]==mid)
{
left=mid+1;
}
else
{
right=mid;
}
}
return nums[left]==left?left+1:left;
}
};
9.来总结一下:使用二分要先判断数组是否具有二段性,如果具有二段性,则选择找左或者找右的模板,并且观察如果出现了-1则算中的时候+1