二分查找BinarySearch

我们接触到的二分查找,都是在一个有序数组找一个target,这个显然比较容易。但是如果我们换一种思路,在一个有序数组中找一个比target小的最大一个值,或者比target大的最小的一个值会怎么写呢?

给定一个数组nums = [1,2,3,4,6,7,19,22] target = 7。现在的条件是查找比target小且最大的一个值。在nums数组中6就是小于target且最大的一个值。那么大于target且最小的值就是19。
如果条件是大于等于target的一个值,这里需要考虑的点就是target可能不存在nums,要求你找到一个>=target的值。不存在返回nums.length就可以。

我们先回顾一下传统的二分查找在一个有序数组找等于target的val。

package main

func binarySearch(nums []int, target int) int {
    //定义left,right
    left , right := 0 , len(nums)

    for left < right {
        mid := (left + right) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else {
            return nums[mid]
        }
    }

    return -1
}

1.1给定一个nums = [1,2,3,4,5,7,10] target = 6请找出大于等于target的第一个数.

package main
// binarySearch 在nums中找到第一个不target大的元素
func binarySearch(nums []int, target int) int {
    left , right := 0, len(nums)
    //这里是关键点,是否会死循环
    //假设我们要的值是[9, 10]里面的10,L = 9, R = 10,(L + R) / 2 = mid,mid=9
    //下一步mid + 1 最后left和right必然相等。
    //如果不存在大于target的第一个元素就会返回数组最后一个元素。
    for left < right {
        mid := (left + right) / 2
        //这里关键,判断是要往左还是往右。因为nums[mid] >= target 那就意味着 mid + 1 ~ right都不会成为答案。
        if nums[mid] >= target {
            right = mid
        } else {
        //假设小于的情况 left ~ mid都不会成为答案
            left = mid + 1
        }
    }

    return nums[right]
}

1.2给定一个nums = [1,2,3,4,5,7,10] target = 6请找出小于等于target的第一个数.

package main

func binarySearch(nums []int, target int) int {
    left, right := 0, len(nums) - 1
    for left < right {
    //给定一个数组[5,6] 我要的是6 L=5,R=6,(L+R+1)/2 这样方式就不会导致死循环。
    //最终的结果让left=right才会退出循环。所以最后返回left和right都可以。
    mid := (left + right + 1) / 2
    if nums[mid] <= target {
        left = mid
    } else {
        right = mid - 1
    }
   }

    return nums[right]
}

总结:

简单来总结一下以上两种方式的区别。两个最终的结果都是在left=right才会退出循环。

找一个target的后继和前驱。

1.2前驱,当mid > target,那么mid + 1都不会是答案,剩下就在left ~ mid - 1这个区间。

1.1后继,当mid < target,那么left ~ mid都不会成为答案。剩下就在mid + 1 ~ right这个区间。

带着以上我们学习的二分搜索来看到一道题。leetcode 153. 寻找旋转排序数组中的最小值。

题目讲的是,给定一个排好序的旋转数组nums = [3,4,5,6,1,2]。不告诉你旋转了多少次。请你找出有序数组最小值。这个数组最小值就是1。

我先写一下朴素的做法。

package main 

func findMin(nums []int) int {
    ans := 1<<31 - 1
    for i := range nums {
        if ans > nums[i] {
            ans = nums[i]
        }
    }

    return ans
}

这样写出来的结果是O(n),假设要求时间复杂度是O(logn)

package main

func findMin(nums []int) int {
    
    left, right := 0, len(nums) - 1
    for left < right {
        mid := left + (right - left) / 2
        if nums[mid] > nums[right] {
            left = mid + 1
        } else {
            right = mid
        }    

    return nums[right]
}

先来分析一下这个二分查找的假定给定一个数组nums = [3, 4, 5, 6, 7, 0, 1, 2]

如何寻找旋转数组中最小值呢!首先可以看看这个数组有什么特征,每一段都满足单调性。(L + R) / 2取mid,那就是中间这个位置,假设中间这个位置nums[mid] > nums[right] 说明最小的一段在mid + 1 ~ right这里。left = mid + 1,某一个时刻(L + R) / 2 = mid < right的情况,让right = mid,因为mid有可能是我们要的答案。当数组内只有两个元素[0,1] 0 -> Left, 1 -> Right,当mid 取到0 < right ,我们让right = mid,此时退出循环。返回left或right都可以。

退出循环最终是停留在left=right。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值