找单个值、左右边界的二分查找

765 阅读1分钟

二分查找需要给一个查找区间,该查找区间有两种表示方法。

闭区间

l, r = 0, len(arr)-1

左闭右开区间

l, r = 0, len(arr)

因此本文的代码都会给出两个版本,我个人更喜欢闭区间表示。

查找指定元素在数组中的下标值

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

闭区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)-1
    while l <= r:		# 出口是搜索区间为空
    	m = (l+r)//2
        if arr[m] == target:
            return m
        if arr[m] < target:	# 左闭所以不包括m
            l = m + 1
        else: r = m - 1		# 右闭所以不包括m
    return -1

左闭右开区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)
    while l < r:		# 出口是搜索区间为空
    	m = (l+r)//2
        if arr[m] == target:
            return m
        if arr[m] < target:	# 左闭所以不包括m
            l = m + 1
        else: r = m		# 右开所以包括m
    return -1

二分代码中纠结的点都已经在代码中注释了,l <= rl < r是因为搜索区间的表示不同,他们都表示着搜索区间为空的情况。r = mr = m - 1也一样,都是为了继续维护搜索区间右侧边界的开闭而已。

查找指定元素在数组中的左边界

闭区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)-1
    while l <= r:
    	m = (l+r)//2
        if arr[m] < target:
            l = m + 1
        else: r = m - 1
    return l

左闭右开区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)
    while l < r:
    	m = (l+r)//2
        if arr[m] < target:
            l = m + 1
        else: r = m
    return l

返回值表示其左侧所有元素全部小于target,因此有可能返回len(arr),即全部元素小于target。之所以能够找到左边界的原因在于arr[m] == target时收缩了右边界。

查找指定元素在数组中的右边界

闭区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)-1
    while l <= r:
    	m = (l+r)//2
        if arr[m] > target:
            r = m - 1
        else: l = m + 1
    return r

左闭右开区间版本

def search(arr, target) -> int:
    l, r = 0, len(arr)
    while l < r:
    	m = (l+r)//2
        if arr[m] > target:
            r = m
        else: l = m + 1
    return r - 1

返回值表示其右侧所有元素全部小于target,因此有可能返回-1,即全部元素大于target。之所以能够找到右边界的原因在于arr[m] == target时收缩了左边界。

需要注意的是在左闭右开区间版本返回的是r-1,因此更推荐使用闭区间表示搜索区间。