用二分算法实现Sqrt(x)函数
这道题用二分模板的第二个但是写的使用要注意mid*mid可能出现数据溢出的情况所以用mid*1ll*mid
把int类型变成long long 类型
class Solution {
public:
int mySqrt(int x) {
int l=0,r=x;
while(l<r)
{
int mid=(l+r+1ll)>>1;
if(mid*1ll*mid>x)
r=mid-1;
else
l=mid;
}
return l;
}
};
题意
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个升序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
数组可能包含重复项。
注意:数组内所含元素非负,若数组大小为0,请返回-1。
样例
输入:nums=[2,2,2,0,1]
输出:0
注意这里有重复的元素,在进行二分查找的前需要去掉重复的元素,注意这里边界判定是与nums[0]做比较
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums.size()<=0) return -1;
int n=nums.size()-1;
while(n>0&&nums[n]==nums[0]) n--;
if(nums[n]>=nums[0]) return nums[0];
int l=0,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(nums[mid]>=nums[0]) l=mid+1;
else
r=mid;
}
return nums[r];
}
};
搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
二分查找,关键是找到中间值之后,判断接下来在左半段还是右半段继续查找。根据旋转数组的规律,数组经过旋转之后会被分为两段升序段,找到中间值nums[m]之后,将它和搜索区域最右边元素nums[r]比较,处理情况如下:
如果nums[m] == target,则返回m
如果nums[m] > nums[r],则中间值位于旋转数组的前一段升序段
如果target < nums[m] && target >= nums[l],则到m的左半段继续查找
否则,到m的右半段继续查找
如果nums[m] <= nums[r],则中间值位于旋转数组的后一段升序段
如果target > nums[m] && target <= nums[r],则到m的右半段继续查找
否则,到m的左半段继续查找
时间复杂度O(log n)
空间复杂度O(1)
class Solution {
public:
int search(vector<int>& nums, int target) {
int len = nums.size();
int l = 0,r = len-1;
while(l <= r)//注意写的时候旋转数组搜索中结束while循环的条件是小于和等于
{
int m = (r + l)/2;
if(nums[m] == target)
return m;
else if(nums[m] > nums[r])
{
if(target >= nums[l] && target < nums[m])
r = m-1;
else
l = m+1;
}
else if(nums[m] <= nums[r])
{
if(target > nums[m] && target <= nums[r])
l = m+1;
else
r = m-1;
}
}
return -1;
}
};
在排序数组中查找元素的第一个和最后一个位置
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
思路先找到左区间的端点,在找到右区间的端点
二分查找,由于要找到目标值的第一个位置和最后一个位置。当中间值nums[m] == target时: 如果要找第一个位置,则向m的左半段继续二分查找,–r直到nums[m-1] != target为止; 如果要找最后一个位置,则向m的右半段继续二分查找,++l直到nums[m+1] != target为止;
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};
int len = nums.size();
int l = 0,r = nums.size() - 1;
int start = -1,end = -1;
while(l <= r)
{
int m = l + (r-l)/2;
if(nums[m] < target) l=m+1;
else if(nums[m] > target) r = m-1;
else
{
if(m == 0)//分别判定边界情况
{
start = 0;
break;
}
if(nums[m-1] != target)
{
start = m;
break;
}
else r = m-1;
}
}
if(l > r) return {-1,-1};
l = 0;
r = nums.size() - 1;
while(l <= r)
{
int m = l + (r-l)/2;
if(nums[m] < target) l = m+1;
else if(nums[m] > target) r = m-1;
else
{
if(m == len-1)
{
end = len-1;
break;
}
if(nums[m+1] != target)
{
end = m;
break;
}
else l = m+1;
}
}
return {start,end};
}
};
二分总结
对于整数域上的二分,需要注意终止边界、左右区间取舍时的开闭情况避免漏掉答案或造成死循环;对于实数域上的二分,需要注意精度问题。