2021-06-02 二分法

二分法

题目

几个变形(力扣 34,153, 154,81)

定义

在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界,重复直到找到目标元素。

时间复杂度:O(logn)

空间复杂度:O(n),额外空间O(1)

算法思想

利用单调有序的性质,根据中间元素判断 答案在哪边,缩小答案所在的范围,每次减少一半的区间范围

例子

例子1

找有序无重复元素数组 [1 3 7 8 9 21 54 64 69] 中是否存在元素 val ,存在返回 index,否则返回 -1。

当 val = 1 时

因为数组有序,先看中间的数 9,是大于 1 的。所以 9 及其右边的数一定都大于 1,所以可以不必考虑 9 及其右边的数,即找 [1 3 7 8] 内是否有 1

然后继续二分区间 [1 3 7 8] 再看中间数 3,是大于 1 的。所以 3 及其右边的数一定都大于 1,所以可以不必考虑 3 及其右边的数,即找 [1] 内是否有1.

然后继续二分区间 [1] 再看中间数 1,等于 val,返回 index。

代码

标准二分

def erfen(nums, val):
    # l左边的数都小于val
    # r右边的数都大于val
    l = 0
    r = len(nums) -1
    while l <= r:
        mid = (l + r) // 2
        # 每次舍掉一半的数不必考虑,找到直接return 其index,while结束还没找到,即不存在val。
        if nums[mid] < val:
            l = mid + 1
        elif nums[mid] > val:
            r = mid - 1
        else:
            return mid
    return -1

if __name__ == '__main__':
    a = [1, 3, 7, 8, 9, 21, 54, 64, 69]
    ans = erfen(a, 1)
    print(ans)

例子2

找有序数组 [1, 3, 7, 8, 9, 21, 54, 64, 69] 大于 2 的 最小的数,不存在,返回 -1。

代码

模板:

因为数组有序,先看中间的数 9,是大于 2 的。所以 9 右边的数一定都大于 2,所以可以不必考虑 9 右边的数,即找[1 3 7 8 9]内大于 2 的 最小的数。

然后继续二分区间[1, 3, 7, 8, 9]… …

特点:

相对↓标准二分的缺点:while循环可能会多走几次;出了 while 后,需要特判一下 l,r的值,不同的题特判的点不同,不好辨别的可以强行l,r都判一下,保证结果正确。

相对↓标准二分的优点:while内部 l = ,r = 赋值比较简单,不同题意修改较小,不容易出错。

def erfen(nums, val):
    # l 及其左边是小于等于 val 的,r 及其右边是大于 val 的。
    l = 0
    r = len(nums) -1
	# 比如要找 >val 的第一个数,因为单调性质,那就可以分成两队,>val 的是一队的,<=val 的是一队的,
	# 当 mid 是哪对的就把他赋给那队的,例如:mid<=val 时,mid 及其左边应该是左队的,就 l = mid
    while l + 1 < r:
        mid = (l + r) // 2
        if nums[mid] < val:
            l = mid
        elif nums[mid] > val:
            r = mid
        elif nums[mid] == val:
            l = mid
    # 出来特判是否一定分成两队:
	# 可能只有右队,左队为空,即没有 l=mid 过,l 仍是初始index,那这时需要考虑 l,这个时候要考虑 l 是否是右队的。如果 l 是右队的,应该返回 l;
	# 还可能都是左队的,即没有 r=mid 过,这时要看 r,如果 r 是左队的,即不存在 >val 的值,返回 -1.
    return nums[l] if nums[l] > val else nums[r] if nums[r] > val else -1

if __name__ == '__main__':
    a = [1, 3, 7, 8, 9, 21, 54, 64, 69]
    ans = erfen(a, 2)
    print(ans)

标准二分

循环次数最少,出while后返回结果好判断。

但是不同的变形题,判断不同的要求时 l,r 赋值时不一定是 mid+1,mid-1。需要根据题意自行判断,容易出错,容易陷入死循环。

def erfen(nums, val):
# l 左边都是小于等于 val 的数,r 右边都是大于 val 的数,当出 while 时,l>r,一定 l 等于 r+1,即判断 nums[l] 是否溢出即可
    l = 0
    r = len(nums) -1
    while l <= r:
        mid = (l + r) // 2
        if nums[mid] <= val:
            l = mid + 1
        elif nums[mid] > val:
            r = mid - 1
    return nums[l] if l < len(nums) else -1

if __name__ == '__main__':
    a = [1, 3, 7, 8, 9, 21, 54, 64, 69]
    ans = erfen(a, 2)
    print(ans)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值