自学Java之Binary Search

核能气质少年 提交于 2020-01-21 16:21:33

Binary Search

1.回顾

可以用很简单的代码实现二分查找(开始写了另一个函数用来迭代,大概是分治学多了QAQ)
public int search(int[] nums, int target) {
    int left=0, right=nums.length-1;
    int mid;
    while(left < right){
        mid=(left + right)/2;
        if(target == nums[mid])
            return mid;
        if(target < nums[mid])
            right = mid-1;
        else if(target > nums[mid])
            left = mid+1;
    }
    return -1; 
}

2.三种主要形式

官方给出的三种对比
https://leetcode.com/explore/learn/card/binary-search/136/template-analysis/935/
1.基础,和上面的代码无差别

int binarySearch(int[] nums, int target){
    if(nums == null || nums.length == 0)
        return -1;

    int left = 0, right = nums.length - 1;
    while(left <= right){
    // Prevent (left + right) overflow
        int mid = (left + right) / 2;
        if(nums[mid] == target)
            return mid; 
        else if(nums[mid] < target) 
            left = mid + 1;
        else 
            right = mid - 1; 
    }

    // End Condition: left > right
    return -1;
}
  • 平方根问题
    模仿基础情况写的代码如下,但对大数会超时
public int mySqrt(int x) {
    if(x==0) return 0;
    int mid, sqr_mid, left=1, right=x/2;//缩小范围
    while(left<=right){
        mid=(left+right)/2;
        sqr_mid=mid*mid; 
        if(sqr_mid == x || (sqr_mid < x && sqr_mid+2*mid+1 > x)) 
            return mid;
        if(sqr_mid < x)
            left=mid+1;
        else
            right=mid-1;
    }
    return 0; 
}

修改后不报错但仍会发生溢出。
AC方案:不定义mid平方(sqr_mid),用mid==x/mid代替原来的检查语句

public int mySqrt(int x) {
    if(x==0) return 0;
    int mid, left=1, right=x/2+1;//缩小范围
    while(left<=right){
        mid=(left+right)/2;
        if(mid == x/mid) 
            return mid;
        if(mid < x/mid)
            left=mid+1;
        else
           right=mid-1;
    }
    return right;
}

2.无法描述

int binarySearch(int[] nums, int target){
    if(nums == null || nums.length == 0)
        return -1;

    int left = 0, right = nums.length;
    while(left < right){
    // Prevent (left + right) overflow
        int mid = left + (right - left) / 2;
    	if(nums[mid] == target)
    		return mid; 
   	 	else if(nums[mid] < target)
   	 		left = mid + 1; 
    	else 
    		right = mid; 
    }

  	// Post-processing:
  	// End Condition: left == right
  	if(left != nums.length && nums[left] == target) return left;
  		return -1;
}

终止条件是left==right

  • 坏版本问题
    初始的代码,同样是会溢出
public int firstBadVersion(int n) {
    int left=1, right=n;
    int mid;
    while(left < right){
        mid=(left + right)/2;
        if(isBadVersion(mid)==false)
            left = mid+1;
        else
            right = mid;
    }
    return left;
}

在评论区才明白差在这一步:
start+(end-start)/2有效避免溢出,got√
在这里插入图片描述
官方AC代码

public int firstBadVersion(int n) {
    int left = 1;
    int right = n;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (isBadVersion(mid)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}
  • 寻找peek
public int findPeakElement(int[] nums) {
    int left=0, right=nums.length-1;
    int mid;
    if(right<=0) return 0;
    while(left < right){
        mid = left + (right - left)/2;
        if(nums[mid] > nums[mid+1])
            right = mid;
        else 
            left = mid + 1;
    }
    return left;
}
  • 寻找翻转一次后的peek
    ——找到比第一个元素小的那部分
public int findMin(int[] nums) {
    int left=0, right=nums.length-1;
    int mid;
    if(nums[left]<nums[right]) 
    	return nums[0];//未翻转 
    while(left < right){
        mid = left + (right - left)/2;
        if(nums[mid]>=nums[0])
            left=mid+1;
        else
            right=mid;     
    }
    return nums[left];
}

对比而言,我理解的区别大概就是——
第一种问题通常是[A,A,A,B,C,C,C]这种数组中找到B的位置;
而第二种问题是在[A,A,A,B,B,B,B]中找到第一个B的位置。

比如在“坏版本问题”中,如果mid是好的,就把左标记移到mid的下一个;反之,把右标记移到当前的mid,继续逼近…

  • 范围查找
    逻辑有点混乱,总之是分两步找到区间的左右端点
public int[] searchRange(int[] nums, int target) {
    int[] ans={-1,-1};
    int left=0, right=nums.length-1;
    int mid;
    if(right<0) return ans;
    while(left < right){    // 区间左端点
        mid=(left + right)/2;
        if(target <= nums[mid])
            right = mid;
        else
            left = mid+1;
    }
    if(nums[left]!=target)  // 找不到左端点,
        return ans;			// 表示数组中找不到target
    ans[0]=left;
     
    if(nums.length==1){
        ans[1]=left;
        return ans;
    }
     
    left=0;
    right=nums.length-1;
    while(left < right){    // 区间右端点
        mid=(left + right)/2;
        if(target >= nums[mid])
            left = mid+1;
        else
            right = mid;
    }
    ans[1]=left-1;
    if(target == nums[left]) ans[1]=left;   // 考虑target是数组的最大元素(?
    return ans; 
}

待回看
- 范围查找
- 翻转列表求最小元素
- 翻转列表求最小元素II
未解
- k个最近元素
- 求幂

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!