数据结构与算法----学习笔记(7)二叉搜索树实战

二分查找

问题定义:

给定一个由数字组成的有序数组nums ,并给你一个数字 target 。问 nums 中是否存在target 。如果存在, 则返回其在 nums 中的索引。如果不存在,则返回 1 。

常见变体有:

如果存在多个满足条件的元素,返回最左边满足条件的索引。
如果存在多个满足条件的元素,返回最右边满足条件的索引。
数组不是整体有序的,比如先升序再降序,或者先降序再升序。
将一维数组变成二维数组。


二分查找的前提

数组必须时有序的(如果数组时无序的,我们可以考虑对数组进行排序,但是要考虑排序的复杂度)

常见题型

查找一个数
算法描述:

  • 先从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;

  • 如果目标元素大于中间元素,则在数组大于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。

  • 如果目标元素小于中间元素,则在数组小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。

  • 如果在某一步骤数组为空,则代表找不到。


一般的二分查找

思维框架

首先定义搜索区间为 [left, right],注意是左右都闭合,之后会用到这个点
由于定义的搜索区间为[left, right],因此当 left <= right 的时候,搜索区间都不为空,此时我们都需要继续搜索。 也就是说终止搜索条件应该为 left <= right 。
循环体内,我们不断计算mid ,并将 nums [mid] 与 目标值比对。
如果nums [mid] 等于目标值, 则提前返回 mid (只需要找到一个满足条件的即可)
如果nums [mid] 小于目标值, 说明目标值在 mid 右侧,这个时候搜索区间可缩小为[mid + 1, right](mid 以及 mid 左侧的数字被我们排除在外)
如果nums [mid] 大于目标值, 说明目标值在 mid 左侧,这个时候搜索区间可缩小为[left, mid-1] (mid 以及 mid 右侧的数字被我们排除在外)
循环结束都没有找到,则说明找不到,返回-1 表示未找到。

代码模板

def binary_search(nums, target):
    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = (left + right) >> 1
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    return -1

寻找最左边的满足条件的值

思维框架

首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。终止搜索条件为left <= right 。
循环体内,我们不断计算mid ,并将 nums [mid] 与 目标值比对。
如果nums [mid] 等于目标值, 则收缩右边界,我们找到了一个备胎,继续看看左边还有没有了(注意这里不一样)
如果nums [mid] 小于目标值, 说明目标值在 mid 右侧,这个时候搜索区间可缩小为 [mid + 1,right]
如果nums [mid] 大于目标值, 说明目标值在 mid 左侧,这个时候搜索区间可缩小为 [left, mid-1]
由于不会提前返回,因此我们需要检查最终的left ,看 nums[left]是否等于 target 。
如果不等于target ,或者 left 出了右边边界了,说明至死都没有找到一个备胎,则返回 -1.否则返回
left 即可,备胎转正。

代码模板

def binary_search_left(nums,target):
    left = 0
    right = len(nums)-1
    while left<=right:
        mid = (left+right)>>1
        # 使得right在target的左边一个
        if nums[mid] == target:
            right = mid-1
        elif nums[mid] >target:
            right = mid -1
        elif nums[mid]<target:
            left = mid+1
    # 循环结束时,left>right,如果left没有超出数组,此时right又在target左边一个,
    # 即left=right+1,之前找到target时的坐标就是right+1
    if left>= len(nums) or nums[left] !=target :
        return -1
    return left

寻找最右边的满足条件的值

思维框架

首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。
由于我们定义的搜索区间为[left, right],因此当 left <= right 的时候,搜索区间都不为空。 也就
是说我们的终止搜索条件为 left <= right 。
循环体内,我们不断计算mid ,并将 nums [mid] 与 目标值比对。
如果nums [mid] 等于目标值, 则收缩左边界,我们找到了一个备胎,继续看看右边还有没有了
如果nums [mid] 小于目标值, 说明目标值在 mid 右侧,这个时候搜索区间可缩小为 [mid + 1,right]
如果nums [mid] 大于目标值, 说明目标值在 mid 左侧,这个时候搜索区间可缩小为 [left, mid-1]
由于不会提前返回,因此我们需要检查最终的right ,看 nums[right]是否等于 target 。
如果不等于target ,或者 right 出了左边边界了,说明至死都没有找到一个备胎,则返回-1.
否则返回right 即可,备胎转正。

代码

def binary_search_right(nums,target):
    left = 0
    right = len(nums)-1
    while left<=right:
        mid = (left+right)>>1
        if nums[mid] == target:
            left  = mid+1
        elif nums[mid]>target:
            right = mid -1
        elif nums[mid]<target:
            left = mid+1
    if right<0 or nums[right] != target:
        return -1
    return right

寻找最左插入位置

比如一个数组nums : [1,3,4 ],target 是 2 。
我们应该将其插入(注意不是真的插入)的位置是索引 1 的位置,即 [1,2,3,4] 。因此寻找最左插入位置应该返回 1 ,而寻找最左满足条件应该返回 -1 。

思维框架

如果你将寻找最左插入位置看成是寻找最左满足大于等于x 的值,那就可以和前面的知识产生联系,使
得代码更加统一。唯一的区别点在于前面是最左满足等于 x ,这里是最左满足大于等于 x 。
具体算法:
首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。
由于我们定义的搜索区间为[left, right],因此当 left <= right 的时候,搜索区间都不为空。 也就是
说我们的终止搜索条件为 left <= right 。
当nums [mid] >= x ,说明找到一个备胎,我们令 right = mid-1 将 mid 从搜索区间排除,继续看看
有没有更好的备胎。
当nums [mid] < x ,说明 mid 根本就不是答案,直接更新 left = mid + 1 ,从而将 mid 从搜索区间排
除。
最后搜索区间的left 就是最好的备胎,备胎转正。

def bisect_left(nums,target):
    left = 0
    right = len(nums)-1
    while left<= right:
        mid = (left+right)>>1
        if nums[mid]>=target:
            right = mid-1
        elif nums[mid]<target:
            left = mid+1
    return left

寻找最右插入位置

如果你将寻找最右插入位置看成是寻找最右满足大于x 的值,那就可以和前面的知识产生联系,使得代码更加统一。唯一的区别点在于前面是最右满足等于 x ,这里是最右满足大于x 。

def bisect_right(nums,target):
    left = 0
    right = len(nums)-1
    while left<=right:
        mid = (left+right)>>1
        if nums[mid] > target:
            right = mid-1
        if nums[mid]<=target:
            left = mid+1
    return left

局部有序(先降后升或先升后降)

33搜索旋转排序数组
81搜索旋转排序数组 II
面试题10.03. 搜索旋转数组
34排序数组中查找元素的第一个和最后一个位置
35搜索插入位置

33.搜索旋转排序数组
在这里插入图片描述

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        n = len(nums)
        
        def find_min():
            begin = 0
            end = n-1
            # 第一个数小于等于最后一个数,则说明数组有序
            if nums[begin]<=nums[end]:
                return begin
            #
            while begin<=end:
                mid = (begin + end)>>1
                #如果某个数大于后面的一个数,则后面的这个数就是最小的数,
                #否则有序数组后一个数一定大于前一个数
                if mid <n-1 and nums[mid]>nums[mid+1]:
                    return mid+1
                # 前半段有序,则在后半段中继续找
                if nums[0] <= nums[mid]:
                    begin = mid+1
                else :
                    end = mid-1
            return 0
            
        def binarty_search(start,end):
            while start<= end:
                mid = (start+end)>>1
                if nums[mid] == target:
                    return mid
                elif nums[mid] > target:
                    end = mid -1
                else :
                    start = mid+1
            return -1
        # 找到最小值的索引
        min_index = find_min()
        # 如果该数组是一个有序数组,则直接进行二分查找
        if min_index == 0:
            return binarty_search(0,n-1)
        # 如果数组的第一个小于target,则只能在前半段的升序查找,即在最小的索引之前
        if nums[0]<=target:
            return binarty_search(0,min_index-1)
        return binarty_search(min_index,n-1)

在这里插入图片描述

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        left = 0
        right 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值