Problem: 367. 有效的完全平方数
problem: 704.二分查找
思路
二分查找的整体思路
二分查找模板(左闭右闭)
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1; // 定义target在左闭右闭的区间里,即:[left, right]
while (left <= right) { // 因为left == right的时候,在[left, right)是有效的空间,所以使用 <=
int middle = left + ((right - left) >> 1);
if nums[middle] == target{
return middle; // 数组中找到目标值,直接返回下标
}else if(nums[middle] > target) {
right = middle-1; // target 在左区间,在[left, middle]中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right]中
}
}
// 未找到目标值
return -1;
}
二分查找模板(左闭右开)
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if nums[middle] == target{
return middle; // 数组中找到目标值,直接返回下标
}else if(nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
}
}
// 未找到目标值
return -1;
}
二分查找思路不难,就是在有序无重复元素中,通过一个left和一个right,确定一个middle,从而将数据一分为二
再将midddle与 目标值target进行比较。
如果middle等于target ,显然我们要就返回对应middle下标。
如果middle大于target ,显然我们要将left右移。(注意!我们这里的数据是升序的!!!)
如果middle小于于target ,显然我们要将left左移。
再更新middle值,在目标值所在区间继续进行二分查找,不断循环下去
这里主要的是一些细节大家容易出错。
循环的条件
在while( left ? right ) 里面用 > 还是 >= 呢?
left right移动的位置
if( 表达式 ){ right = ? } 里面是middle 还是 midlle-1/middle-1 呢?
要想解决这样的细节问题,最重要的就是先解决区间问题
(一)区间问题 right取值
一般常用的就是两种区间
左闭右闭 [left,right]
左闭右开 [left,right)
在区间中,想要查找到每个元素,就要先想想自己用的是 左闭右闭 还是 左闭右开?
其实不管是左闭右闭还是左闭右开,都需要明确,我们要查询过每一个要查元素。
开始,我们比较的是始末元素和中值。这里就需要注意哪个right=nums.size()-1哪个是nums.size()
左闭右闭
如果是左闭右闭,由于左右边界我们都可以取到,我们的left和right开始就应该在数组的始末。所以right=nums.size()-1
左闭右开
如果是左闭右闭,由于右边界我们取不到,我们的right开始应该在数组的末尾的后一位。所以right=nums.size()
(二)left right移动的位置
解决了开始的 right取值,下面解释left right移动的位置就方便多了
还是刚才的图 , 由于每一次middle都会被查找过, 所以我们下次的查找的范围都应该是从left到middle-1或者middle+1到right
因为开区间right取不到自己,所以范围相当于从middle+1到 right-1,而我们之前初始化的时候已经给 right加过1了,所以范围也是middle+1到 right
左闭右闭
由于是左闭右闭,左右边界left和right我们都可以取到,所以找到目标区间后,我们的left和right应该跳过middle, 从middle-1或者middle+1开始查找,即right=nums.size()-1 / left = middle+1
左闭右开
由于是左闭右闭,右边界right我们不可以取到,所以找到目标区间后,我们的left应该跳过middle,即left = middle+1 ,而right应该等于middle,从而使查找的范围,也是从left到middle-1,middle+1到right,即right=middle / left = middle+1
(三)循环条件
知道了left right移动的位置,然后就该讨论下一个细节问题,循环条件。
在之前都没有找到目标值的情况下,最后一次循环结束的条件应该是,我们把目标区间里的数,也就是最后一次的left 和right里的数都应该查询了一遍。
左闭右闭
(本例为假设)
如果target = 5,上次left = 4 right = 5 middle = 4。
由于nums[middle] < target,left = middle+1,所以left = right = 5
这时left = right,我们该不该继续循环呢?
答案时肯定的,因为我们还没有找到5,意味着middle还没有取到5,所以我们应该继续循环让middle=5.
此时,middle刚好时5,结束返回下标结束。
但是,如果这时不是5,我们又取完了区间内所有的数,下一次就会结束循环,也就是left>right时,结束循环,所以循环条件是left<=right
(还有一种情况就是left右移,也会结束循环)
左闭右开
(本例为假设)
如果target = 5,上次left = 5 right = 7(下标) middle = 6(下标)。
由于nums[middle] < target,right = middle
这时middle = left,由于我们没有取过left对应元素,所以循环应该继续。
再循环一次,由于nums[middle] < target,right = middle,所以 left = right = middle = 5
由于这时已经取过了所有区间内元素,所以left = right时就应该结束循环,所以循环的条件是left<right
解题方法
掌握了 二分查找就好解决了
区间选择左边右闭。
循环条件left <= right
left = middle+1 , right =middle-1
只是把比较的middle 变成了 middle*middle,要注意类型转换,防止数据溢出。
复杂度
时间复杂度:
O(logn)O(logn) O( logn)
Code
C++
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0, right = num;
while(left <= right){
int middle = left + (right - left)/2;
long square = (long)middle * middle; //要强制类型转换为 long 类型
if( square == num){
return true;
}else if(square > num){
right = middle - 1;
}else if(square < num){
left = middle + 1;
}
}
return false;
}
};