您的位置:首页 > 其它

二分查找的坑点与总结

2016-12-09 17:09 309 查看
二分查找足够简单,但是坑点也有不少,下面来总结一下

以下是二分查找的标准写法

以下这个函数是二分查找nums中[left,right)部分,right值取不到,如果查到的话,返回所在地,如果查不到则返回最后一个小于target值得后一个位置。

//右值点不能取到的情况
int binary_search(vector<int>& nums,int left,int right, int target) {
//坑点(1)right究竟能不能取到的问题,这里是不能取到的情况
int i = left;
int j= right;
while(i<j){
int mid = i+(j-i)/2;             //坑点(2)这里尽量这么写,因为如果写成(i+j)/2则有溢出的风险
if(nums[mid]>=target)        //坑点(3)这个地方大于还是大于等于要依据情况而定
j = mid;            //坑点(4)因为右值点反正不能取到,所以j就可以等于mid
else
i = mid+1;           //坑点(5)
}
return i;
}

//右值点能取到的情况
int searchInsert(vector<int>& nums,int left,int right, int target) {
int i = left;
int j= right;
while(i<=j ){
int mid = i+(j-i)/2;
if(nums[mid]>=target)
j = mid-1;
else
i = mid+1;
}
return i;
}


一般来说写的二分查找的标准程序应该是右边right值取不到的情况,所以while循环中要加一步判断i是否小于等于nums.size();

对于坑点2,如果是右值点可以取到情况下,必须是i<=j否则会出现查找偏差,比如数组中就一个元素[3],让查找5的位置,理应返回1,但是此时会输出成0,

对于坑点3,主要应用在如果数组中有重复元素,需要判断其具体要返回什么位置。

对于坑点,5,如果i不加1的,比如i = 0,j= 1 的情况,会出现mid = 0然后一直死循环下去

对于坑点4,依据right能不能取到而定,如果right可以取到则,right必须要-1,不减1的话,还是会出现i = j时的死循环。

另外一种二分查找,今天面试头条的时候,被要求写一个二分查找,原本以为足够的简单,但是发现坑点还是有很多的,比如面试官问,如果数组中出现了重复元素该怎么办?怎么返回重复元素的第一个位置,如果查不到则返回-1,后来想了想写了个这个方法

int searchInsert(vector<int>& nums, int target) {
int i = 0;
int j = nums.size() - 1;
while (i < j) {
int mid = i + (j - i) / 2;
if (nums[mid] > target)
j = mid - 1;
else
if (nums[mid] == target)      //即继续二分查找查找相等元素的左边界即可
j = mid;
else
i = mid + 1;
}
if (nums[i] == target)
return i;
else
return -1;
}


这个有一个简化的写法,减少比较次数【注意以下写法只适合返回最开始出现的位置,如果题目改成出现的最后的一个位置,则还是得用上面的方法】

int binarySearch(vector<int> &array, int target) {
// write your code here
int i = 0;
int j = array.size() - 1;
while (i < j) {
int mid = i + (j - i) / 2;
if (array[mid] >= target)
j = mid ;
else
i = mid + 1;
}
if (array[i] == target)
return i;
else
return -1;
}


二分查找的变种

leetcode 153.Find Minimum in Rotated Sorted Array

就是一个排好序的数组,然后将其平移之后,找最小的元素。

诸如[3 4 5 6 7 1 2]

int findMin(vector<int> &num) {
int start=0,end=num.size()-1;

while (start<end) {
if (num[start]<num[end])
return num[start];

int mid = (start+end)/2;

if (num[mid]>=num[start]) {  //这一步为什么要≥是因为如果mid = start的情况
start = mid+1;
} else {
end = mid;
}
}

return num[start];
}

//更简洁的做法,比右边,因为最右边的必然严格小于最左边,所以等于上面的方法比了一个次小的
int findMin(vector<int>& nums) {
int i = 0, j = nums.size()-1;
while(i<j){
int mid = (i+j)/2;
if(nums[mid]>nums[j])
i =mid+1;
else
j = mid;
}
return nums[i];
}


154. Find Minimum in Rotated Sorted Array II

它的进阶形式,如果有重复元素的话,该如何呢

要考虑到这种情况[3 3 3 3 3 3 3 3 1 3 ] [1 1 1 1 1 1 1 1 1 1 2 1 1]

int findMin(vector<int>& nums) {
int i = 0, j = nums.size()-1;
while(i<j){
int mid = (i+j)/2;
if(nums[mid]>nums[j])
i =mid+1;
else
if(nums[mid]<nums[j])
j = mid;
else
j--;
}
return nums[i];
}
//但实际上这种方法是有一点小问题的,虽然在这个问题中体现不出来,比如就是如果最小值是重复的,且最小值横跨数组左右两个端点的时候,诸如[1 1 1 1 1 1 1 1 1 1 2 1 1]这种情况,它找出的最小值的位置其实严格意义上不算最小值的位置。     [*]


33. Search in Rotated Sorted Array

就是在平移数组中找到一个目标数target,找不到则返回-1,找到返回下标

//这道题一开始的思路是直接二分,然后,后来看了解答,就是用上面的方法找出最小值,然后最小值的位置也就意味着其右平移的距离,所以先假设数组没有平移,二分查找,mid的位置给换成现在数组的位置 rot_mid = (mid+rotate)%n
int search(vector<int>& nums, int target) {
int i=0,j= nums.size()-1;
while(i<j){
int mid = (i+j)/2;
if( nums[mid]>nums[j])
i = mid+1;
else
j = mid;
}
int rot = i;
i = 0;
j = nums.size()-1;
while(i<=j){   //注意这个地方必须是<=,因为[1] 找1,如果是<的话就不行了。
int mid = (i+j)/2;
int rot_mid = (mid+rot)%nums.size();
if(nums[rot_mid] == target) return rot_mid;
if(nums[rot_mid]>target)
j = mid-1;
else
i = mid+1;
}
return -1;
}


进阶形式,就是数组中有重复元素了怎么办

81. Search in Rotated Sorted Array II

bool search(vector<int>& nums, int target) {
int i = 0,j = nums.size()-1;
while(i<j){
int mid = (i+j)/2;
if(nums[mid]>nums[j])
i = mid+1;
else
if(nums[mid]<nums[j])
j = mid;
else
j--;
}
//鉴于[*]处的分析,所以这种最小值是重复的,且横跨首尾的情况,需要额外处理
if(i == 0 && nums[i] == nums[nums.size()-1]){
i = nums.size()-1;
while(i>0 &&nums[i-1] == nums[i]) i--;
}

int rot = i;
i = 0;
j = nums.size()-1;
while(i<=j){
int mid = (i+j)/2;
int rot_mid = (mid+rot)%nums.size();
if(nums[rot_mid] == target) return true;
if(nums[rot_mid]>target)
j = mid-1;
else
i = mid+1;
}
return false;
}


或者采用我一开始的直接二分判断的方法

bool search(vector<int>& nums, int target) {
int i = 0,j = nums.size()-1;
while(i<=j){
int mid = (i+j)/2;
if(nums[mid] == target)
return true;
if(nums[mid]>nums[j])
if(target>nums[mid] ||target<=nums[j])
i = mid+1;
else
j = mid-1;
else
if(nums[mid]<nums[j])
if(target>nums[mid] && target<=nums[j])
i = mid+1;
else
j = mid-1;
else
j--;
}
return false;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  二分查找 搜索 函数