二分查找Go版本实现

本文会汇总常见的二分查找题型并给出相关Leetcode题目的题解

二分查找框架:

基础二分算法:

二分查找是特定对于有序的数组中寻找特定元素或者特定元素位置的算法,我一直使用的是[left,right] 闭区间的算法方式,所以我们循环退出的条件是while (l<=r) ,也就是二者错开才退出循环,当l=r的时候仍然可能搜索到数据。

二分查找

Go 复制代码
func search(nums []int, target int) int {
    l,r:=0,len(nums)-1
    //定义区间为l-r的闭区间,每一个元素都可能被搜索到
    for l<=r{
        //防止超过最大范围取中点
        mid:=(r-l)/2+l
        if nums[mid]==target{
            return mid
        }else if nums[mid]>target{
            //当前值大于target,缩小范围,也就是动右指针
            r=mid-1
        }else{
            l=mid+1
        }
    }
    return -1
}

下面针对mid的定义是为了防止溢出设置的,而针对指针的移动,因为mid在不匹配的情况下就证明了从l或者r的区间到mid是没有目标数据的,那么直接移动,使得l=mid+1或者r=mid-1。

搜索左侧边界的二分算法:

只搜索对应元素还是太基础了,我们经常会遇见搜索某一个数字的最左侧或者最右侧边界的问题,那么这类问题和上方的不同点在于数组中会含有多个target目标值。

Go 复制代码
// 将给定的数字 nums 搜索 target 的左侧边界
func left_bound(nums []int, target int) int {
    left := 0
    right := len(nums) - 1
    // 搜索区间为 [left, right]
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            // 搜索区间变为 [mid+1, right]
            left = mid + 1
        } else if nums[mid] > target {
            // 搜索区间变为 [left, mid-1]
            right = mid - 1
        } else if nums[mid] == target {
            // 收缩右侧边界
            right = mid - 1
        }
    }
    // 判断 target 是否存在于 nums 中
    if left < 0 || left >= len(nums) {
        return -1
    }
    // 如果越界,target 肯定不存在,返回 -1
    if nums[left] == target {
        // 判断一下 nums[left] 是不是 target
        return left
    }
    return -1
}

在这里,我们针对mid=target的时候需要不同的处理,因为我们在搜索边界值,那么当二者相等的时候,我们不可以直接返回,需要不断收缩右边界,看看l到mid-1还有没有目标值了。在整个循环的过程中,假设数组为[1,2,3,3,4,5,6],最后一次循环的时候,右侧指针还要向左移动到2指向的位置,而left一直指向的是最左侧3的位置,所以我们需要返回left。

搜索右侧边界的二分算法:

同理,搜索右侧边界的代码如下:

Go 复制代码
func rightBound(nums []int, target int) int {
    left, right := 0, len(nums) - 1
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else if nums[mid] == target {
            // 这里改成收缩左侧边界即可
            left = mid + 1
        }
    }
    // 最后改成返回 left - 1
    if left - 1 < 0 || left - 1 >= len(nums) {
        return -1
    }
    if nums[right] == target {
        return right
    }
    return -1
}

在排序数组中查找元素的第一个和最后一个位置

本题就是对于上面搜索左右边界的使用,只需要把两个代码拼起来即可解题。

Go 复制代码
func searchRange(nums []int, target int) []int {
    res:=[]int{-1,-1}
    left,right:=0,len(nums)-1
    for left<=right{
        mid:=left+(right-left)/2
        if target<=nums[mid]{
            right=mid-1
        }else{
            left=mid+1
        }  
    } 
    if left<len(nums)&&nums[left]==target{
        res[0]=left
    }
    left, right = 0, len(nums)-1
    for left<=right{
        mid:=left+(right-left)/2
        if target>=nums[mid]{
            left=mid+1
        }else{
            right=mid-1
        }  
    }
    if  right>=0&&nums[right]==target{
        res[1]=right
    }
    return res
}

二分查找习题运用:

统计目标成绩出现次数

对于本题,数组非严格递增,如果有多个符合条件的目标成绩需要找到左右端点,下标相减+1就是总共的成绩数。

Go 复制代码
func searchLeft(scores []int,target int)int{
    l,r:=0,len(scores)-1
    for l<=r{
        mid:=(r-l)/2+l
        if scores[mid]==target{
            r=mid-1
        }else if scores[mid]<target{
            l=mid+1
        }else{
            r=mid-1
        }
    }
    if l<0||l>=len(scores){
        return -1
    }
    if scores[l]==target{
        return l
    }
    return -1
}
func searchRight(scores []int,target int)int{
    l,r:=0,len(scores)-1
    for l<=r{
        mid:=(r-l)/2+l
        if scores[mid]==target{
            l=mid+1
        }else if scores[mid]<target{
            l=mid+1
        }else{
            r=mid-1
        }
    }
    if r<0||r>=len(scores){
        return -1
    }
    if scores[r]==target{
        return r
    }
    return -1
}
func countTarget(scores []int, target int) int {
    res:=0
    left:=searchLeft(scores,target)
    right:=searchRight(scores,target)
    if left==-1&&right==-1{
        return 0
    }
    res=right-left+1
    return res

}

搜索旋转排序数组

观察题目给定数据,我们发现在旋转点前和旋转点之后的两个数组都是有序的,那么只需要对这两个区间分别进行二分查找,再重新旋转返回对应下标即可。

Go 复制代码
func binsearch(nums []int,target int)int{
    l,r:=0,len(nums)-1
    for l<=r{
        mid:=(r-l)/2+l
        if nums[mid]==target{
            return mid
        }else if nums[mid]<target{
            l=mid+1
        }else{
            r=mid-1
        }
    }
    return -1
}
func search(nums []int, target int) int {
    index:=0
    for i:=0;i<len(nums)-1;i++{
        if nums[i]>nums[i+1]{
            //index找到的是下标K
            index=i+1
        }
    }
    leftNum:=nums[:index]
    rightNum:=nums[index:]
    ll:=binsearch(leftNum,target)
    rl:=binsearch(rightNum,target)
    if ll!=-1{
        return ll
    }
    if rl!=-1{
        return (rl+index)%len(nums)
    }
    return -1
}

搜索二维矩阵

题目给的条件是二维数组每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。所以直接遍历存在nums当中二分查找。

Go 复制代码
func searchMatrix(matrix [][]int, target int) bool {
    m,n:=len(matrix),len(matrix[0])
    nums:=[]int{}
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            nums=append(nums,matrix[i][j])
        }
    }
    l,r:=0,m*n-1
    for l<=r{
        mid:=(r-l)/2+l
        if nums[mid]==target{
            return true
        }else if nums[mid]<target{
            l=mid+1
        }else{
            r=mid-1
        }
    }
    return false
}
相关推荐
每天要多喝水9 分钟前
nlohmann/json 的使用
c++·json
蓁蓁啊30 分钟前
C/C++编译链接全解析——gcc/g++与ld链接器使用误区
java·c语言·开发语言·c++·物联网
XX風31 分钟前
3.2K-means
人工智能·算法·kmeans
D_evil__1 小时前
【Effective Modern C++】第四章 智能指针:19. 对于共享资源使用共享指针
c++
czxyvX1 小时前
016-二叉搜索树(C++实现)
开发语言·数据结构·c++
蒟蒻的贤1 小时前
leetcode链表
算法·leetcode·链表
Funny_AI_LAB1 小时前
AI Agent最新重磅综述:迈向高效智能体,记忆、工具学习和规划综述
人工智能·学习·算法·语言模型·agi
阿猿收手吧!1 小时前
【C++】volatile与线程安全:核心区别解析
java·c++·安全
Trouvaille ~2 小时前
【Linux】网络编程基础(三):Socket编程预备知识
linux·运维·服务器·网络·c++·socket·网络字节序
执着2592 小时前
力扣hot100 - 94、二叉树的中序遍历
数据结构·算法·leetcode