Java二分算法解析--基本框架及寻找左右端点

本文详细介绍了二分查找算法的基本思想、实现细节以及在寻找目标值首次和最后一次出现位置的应用。强调了二分查找中边界问题的重要性,并通过代码示例解释了如何处理这些问题。同时,讨论了不同终止条件对结果的影响,帮助读者深入理解二分查找算法。
摘要由CSDN通过智能技术生成

二分算法是一种分而治之的思想,这个算法的思想是挺简单的,但是细节部分,比如二分的边界问题十分让人头疼,在处理具体问题时候并不是单纯套一下模板。
正如KMP算法发明者说的一句话:

Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky...

这句话的理解就是:思路很简单,细节是魔鬼。

算法介绍

下面为百度百科的介绍,写的很严谨:
      在计算机科学中,二分搜索(binary search),也成为折半搜索(half-interval search)、对数搜索(logarithmic search),是一种在有序数组中查找某一个特定元素的搜索算法。搜索过程从数组的 中间元素开始,如果中间元素正好是查找的元素,则搜索过程结束;如果某一个特征元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样中间开始比较。如果在某一个步骤为空,则代表找不到。这种算法每一次比较都使搜索范围缩小一半

二分查找两大基本原则

  • 每次都要缩减区域
  • 每次缩减都不能排除潜在答案

二分查找基本框架

class Solution
{
    int binarySearch(int []arr,int target)
    {
        int low=0,right=arr.length()-1;
        while(...)
        {
            if(arr[mid]==target)
                return mid;
            else if(arr[mid]>target)
                high=...;
            else if(arr[mid]<target)
                low=...;
        }
        return -1;
    }
};

Tips:上面mid=left+(right-left)/2写成这种形式,不是mid=(right+left)/2,是因为后面一种写法当right和left很大的时候会导致溢出,从而数组访问出现越界的问题。

寻找一个数(基础版本)

class Solution
{
    int binarySearch(int []arr,int target)
    {
        int low=0,right=arr.length()-1;
        while(low<=right)
        {
            if(arr[mid]==target)
                return mid;
            else if(arr[mid]>target)
                high=mid-1;
            else if(arr[mid]<target)
                low=mid+1;
        }
        return -1;
    }
};
}

为什么while循环的条件是<=,而不是<?
      因为初始化right是arr.length-1,即最后一个元素的索引,而不是arr.length。这两者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间[left,right],后者相当于左闭右开区间[left,right)。上面的算法使用[left,right]两端都闭的区间,也建议使用这种区间,不容易疏忽造成越界,这个区间是每次进行搜索的区间,我们不妨称这为[搜索区间]。
当找到目标值的时候会停止搜索

if(arr[mid]==target)
	return mid;

或者当搜索区间不存在的时候,也会停止搜索,就是找不到目标。

while(left<=right)

终止条件是left==right+1,写成区间的形式就是[right+1,right],或者带一个数字进去[3,2],这个搜索区间为空,是没有这样的搜索空间的。

while(left<right)

终止条件是left==right,写成区间就是[right,right],带个数字进去就是[2,2],这个时候搜索空间非空,还有一个数2就结束了,但是此时循环语句结束了,也就是区间[2,2]被漏掉,就会造成程序返回值可能就会出现错误。

当然使用while(left<right)也是可以的,只需要

right=mid-1;====>right=mid;//即可

为什么left=mid+1,right=mid-1?有的代码写的是right=mid或者left=mid。
      这是二分查找的一个难点,因为我们使用的区间是两端闭区间,即[left,right],当我们发现mid不是要找的target时,如何确定下一步搜索区间?当然应该取[left,mid-1]或者[left+1,right]去找,mid已被搜索过,应该去掉。

理解了上面的内容,大概就了解了二分算法的基础知识了,接下来,我们来探讨一下如何来获取我们获取目标值target第一次出现的位置(即左端点):

第一种写法,比较简单,也比较好理解,直接上代码:

public int binaySearchLeftBound(int[] nums,int target){
	int low=0,high=nums.length-1,mid=0;
	while(low<=high){
		mid=low+(high-low)/2;
		if(nums[mid]==target){
			if(mid==0||nums[mid-1]<target)//如果mid此时为0,或nums[mid-1]<target,那么mid已经处于左端点处,直接返回
				return mid;
			high=mid-1;//不断向左边界压
		}
		else if(nums[mid]>target){
			high=mid-1;
		}
		else if(nums[mid]<target){
			low=mid+1;
		}
	}
	return -1;//找不到target
}

第二种写法没有第一种写法那么直白,不过也不难理解,上代码:

public int binaySearchLeftBound(int[] nums,int target){
	int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if (nums[mid]==target){
                high=mid-1;//不断逼近左边界
            }
            else if(nums[mid]>target){
                high=mid-1;
            }
            else if(nums[mid]<target){
                low=mid+1;
            }
        }
        //上述过程和第一种解法的区别在于,这种写法不需要内部做多次判断,只需要在结尾处判断一次即可
        if(low>=nums.length||nums[low]!=target)//left是否都越界了都没找到
        	return -1;
        return low;
}

寻找目标值最后一次出现的问题,即右端点,和寻找左端点思路是相同的

public int binarySearchRightBound(int[] nums,int target){
        int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if (nums[mid]==target){
                if(mid==nums.length-1||nums[mid+1]>target)
                    return mid;
                low=mid+1;
            }
            else if(nums[mid]>target){
                high=mid-1;
            }
            else if(nums[mid]<target){
                low=mid+1;
            }
        }
        return -1;
    }
public int binarySearchRightBound(int[] nums,int target){
        int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if (nums[mid]==target){
                low=mid+1;
            }
            else if(nums[mid]>target){
                high=mid-1;
            }
            else if(nums[mid]<target){
                low=mid+1;
            }
        }
        if(high<0||nums[high]!=target)
            return -1;
        return high;
    }

二分算法的入门算是讲完了,深入下去还有太多精彩的地方,这个慢慢学习,遇到困难,解决困难!

文章内容参考了一下博客:

https://www.zhihu.com/question/36132386/answer/712269942
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值