所谓二分查找,就是对区间进行分解从而缩小区间范围进而找到所需值,二分查找的条件是有序,优点是一般时间复杂度为O(logn).
下面看题:
第一题
二分查找的难点在于哪里,就在于while(left<right)还是while(left<=right)还有right = middle还是right = middle - 1。这两者怎么区分呢,其实主要就是看你的区间的定义,看你定义的区间是左闭右闭的还是左闭右开的(按道理其他也行,但是一般不用)。
如果区间定义为左闭右闭,那么就是while(left <= while),因为这个时候等于有意义了,而且right = middle -1,因为middle因为比较过了,之后不用再比较了,如果不减1则middle又进入有效区间了。
两种都挺好,具体都可以写,不过写的时候要保持这个定义不变,而且要看最后的left和right代表的是不是自己想要的,需不需要进一步处理。
所以存在下面两种写法:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0, right=0, middle = 0;
right = nums.size() - 1;
while (left <= right) {
middle = left + (right - left) / 2; // 这样写是为了防止溢出
if (nums[middle] == target) {
return middle;
}
else if (nums[middle] < target) {
left = middle + 1;
}
else if (nums[middle] > target) {
right = middle - 1;
}
}
return -1;
}
};
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0, right=0, middle = 0;
right = nums.size();
while (left < right) {
middle = left + (right - left) / 2; // 这样写是为了防止溢出
if (nums[middle] == target) {
return middle;
}
else if (nums[middle] < target) {
left = middle + 1;
}
else if (nums[middle] > target) {
right = middle;
}
}
return -1;
}
};
第二题
我感觉这个题有点莫名其妙的,好像和二分查找真没啥区别,不过可能最好还是用左闭右开的区间,因为插入有可能插入到最后一个位置,用左闭右闭不好表示了。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0, right=0, middle = 0;
right = nums.size();
while (left < right) {
middle = (left + right) / 2;
if (nums[middle] == target) {
return middle;
}
else if (nums[middle] < target) {
left = middle + 1;
}
else if (nums[middle] > target) {
right = middle;
}
}
return right;
}
};
第三题
其实这里面的题都是环环相扣的,好多都可以用前一道题的东西。例如这题吧,其实很明显是要用两次二分查找了,因为一次二分查找肯定解决不了问题了,一次二分查找最多找到一个位置,找到一个位置后又怎么确定左右两边有没有target呢。
用两次二分查找,而且这个二分查找吧,是可以很灵活的变换的,例如这里我们就变换了一下,把middle = target也变成了往左移,这样每次二分查找找到的就是最左边的和target相等的元素了,当然我们可以再写一个函数把middle = target变成往右移,不过我们这里可以直接去找target+1的元素,如果左边找到了则把右边位置减1即可。注意这里找target+1找的也是最左边的元素。
不过我觉得其实写两个函数也没啥,方法笨一点也没事。
第四题
这个也可以用二分查找,就看区间中间那个数的平方和x大小的比较,如果小于等于且中间数加1大于x,说明就是它了,否则改变区间。
/**
* 解题思路:设定左边界(0)和右边界(x),左闭右闭,开始循环,求出中间位置元素值,若中间元素小于等于x且中间元素加1大于x,则返回该元素。
* 若中间元素大于x,则将右边界设置为中间位置减1,若中间元素小于x,则将左边界设置为中间位置加1.
*
*/
class Solution {
public:
int mySqrt(int x) {
int left = 0, right = x;
if(x == 0 || x == 1)
return x;
while (true) {
int middle = left + (right - left) / 2;
int temp1 = middle + 1;
if (middle <= (x / middle) && temp1 > (x / temp1))
return middle;
else if (middle < (x / middle))
left = middle + 1;
else if (middle > (x / middle))
right = middle - 1;
}
}
};
上面这个版本为啥要用除法呢,主要是编译器提醒我int溢出了,所以我就用了除法,也可以用long long
class Solution {
public:
int mySqrt(int x) {
int left = 0, right = x;
while (true) {
long long middle = left + (right - left) / 2;
if ((middle * middle) <= x && ((middle + 1) * (middle + 1)) > x)
return middle;
else if (middle * middle < x)
left = middle + 1;
else if(middle * middle > x)
right = middle - 1;
}
}
};
第五题
这题咋说呢,要先做了第四题,这题就是送的,直接用第四题的代码,然后在算出算术平方根后看看是不是平方得x,主要思想还是二分法吧,对了,第四题和第五题都可以用牛顿迭代法,不过我计算方法当初学的时候就没认真,现在更是忘得差不多了。
总结
总之,这个二分查找吧,前提条件是有序区间,作用是提高查找速率,用法就是要注意你的区间的定义不要变,是左闭右闭还是左闭右开。还有最后得到的结果要不要再处理一下。写完收工。