搞定二分法及其变体(含python二分法标准库实现剖析)

搞定二分法及其变体(含python二分法标准库实现剖析)搞定二分法及其变体 在一个有序的数组中,相比顺序索引的查找O(N)的时间复杂程度,二分法查找的时间复杂程度为O(log(N)) 二分法使用场景: 有序数组 能索引访问 有明确边界 二分法模板 基本模板

搞定二分法及其变体(含python二分法标准库实现剖析)

思想很简单,细节是魔鬼,极其考验边界条件处理的能力~

简述思想

在一个有序的数组中,相比顺序遍历的查找O(N)的时间复杂程度,二分法查找的时间复杂程度为O(log(N))

二分法使用场景:

  • 有序数组
  • 能索引访问
  • 有明确边界

Python二分法标准库实现

截止2021年10年24日,Python的Stable版本已经发布到3.10,其从Python的官方github的commit提交记录来看基本上从Python3.6-Python3.10都有一定内容的补充。

搞定二分法及其变体(含python二分法标准库实现剖析)

Python版本 实现地址
3.10 github.com/python/cpyt…
3.9 github.com/python/cpyt…
3.8 github.com/python/cpyt…
3.7 github.com/python/cpyt…
3.6 github.com/python/cpyt…

以下为Python 3.10的Python标准库源码,不得不说a这种变量感觉不专业,不过注释还是挺详细的~

"""Bisection algorithms."""


def insort_right(a, x, lo=0, hi=None, *, key=None):
    """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the right of the rightmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """
    if key is None:
        lo = bisect_right(a, x, lo, hi)
    else:
        lo = bisect_right(a, key(x), lo, hi, key=key)
    a.insert(lo, x)


def bisect_right(a, x, lo=0, hi=None, *, key=None):
    """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in a[i:] have e > x. So if x already appears in the list, a.insert(i, x) will insert just after the rightmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    # Note, the comparison uses "<" to match the
    # __lt__() logic in list.sort() and in heapq.
    if key is None:
        while lo < hi:
            mid = (lo + hi) // 2
            if x < a[mid]:
                hi = mid
            else:
                lo = mid + 1
    else:
        while lo < hi:
            mid = (lo + hi) // 2
            if x < key(a[mid]):
                hi = mid
            else:
                lo = mid + 1
    return lo


def insort_left(a, x, lo=0, hi=None, *, key=None):
    """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """

    if key is None:
        lo = bisect_left(a, x, lo, hi)
    else:
        lo = bisect_left(a, key(x), lo, hi, key=key)
    a.insert(lo, x)

def bisect_left(a, x, lo=0, hi=None, *, key=None):
    """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will insert just before the leftmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    # Note, the comparison uses "<" to match the
    # __lt__() logic in list.sort() and in heapq.
    if key is None:
        while lo < hi:
            mid = (lo + hi) // 2
            if a[mid] < x:
                lo = mid + 1
            else:
                hi = mid
    else:
        while lo < hi:
            mid = (lo + hi) // 2
            if key(a[mid]) < x:
                lo = mid + 1
            else:
                hi = mid
    return lo


# Overwrite above definitions with a fast C implementation
try:
    from _bisect import *
except ImportError:
    pass

# Create aliases
bisect = bisect_right
insort = insort_right

二分法模板

基本模板

left, right = 0, len(nums)-1
while left<=right:
    mid = (left + right) // 2

    if nums[mid] == target:
        break or return result

    elif nums[mid]<target:
        left = mid + 1
    else:
        right = mid - 1
  • 为什么while循环中的条件是<=,而不是<

因为初始化right的赋值是len(nums)-1,即最后一个元素的索引,而不是len(nums)

寻找左侧边界模板

def bisect_left(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, a.insert(x) will insert just before the leftmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else: hi = mid
    return lo

寻找右侧边界模板

def bisect_right(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in a[i:] have e > x. So if x already appears in the list, a.insert(x) will insert just after the rightmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    return lo

统一模板(只需要重点看这个模板)

这个模板统一了while的条件和循环体内的判断顺序

34. Find First and Last Position of Element in Sorted Array

int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int 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) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}

int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int 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) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}


int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int 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;
        }
    }
    // 最后要检查 right 越界的情况
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

python模板

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        return [self.bi_left_search(nums, target), self.bi_right_search(nums, target)]

    def bi_left_search(self, nums, target):
        n = len(nums)
        l,r = 0, n-1
        while l<=r:
            mid = (l+r) // 2
            if nums[mid]<target:
                l = mid+1
            elif nums[mid]>target:
                r = mid-1
            else:
                r = mid-1
        if l>n-1 or nums[l] != target:
            return -1
        return l

    def bi_right_search(self, nums, target):

        n = len(nums)
        l,r = 0, n-1

        while l<=r:

            mid = (l+r) // 2
            if nums[mid]<target:
                l = mid+1
            elif nums[mid]>target:
                r = mid - 1
            else:
                l = mid + 1
        if r<0 or nums[r] != target:
            return -1

        return r

二分法题目汇总

Problem 中文题 解题描述
【LeetCode】69. Sqrt(x) 稍微有变体
【LeetCode】367. Valid Perfect Square
【LeetCode】704. Binary Search
【LeetCode】33. Search in Rotated Sorted Array 二分法变体
【LeetCode】34. Find First and Last Position of Element in Sorted Array 先二分再向两端生长
【LeetCode】74. Search a 2D Matrix
【LeetCode】658. Find K Closest Elements 比较大的变体

【LeetCode】69. Sqrt(x)


class Solution:
    def mySqrt(self, x: int) -> int:

        left, right = 0, x

        while right-left >= 0.000001:

            mid = (left+right) / 2

            if mid*mid == x:
                return int(mid)

            if mid*mid < x:
                left = mid
            else:
                right = mid
        
        return int(right)

【LeetCode】367. Valid Perfect Square

class Solution:
    def isPerfectSquare(self, num: int) -> bool:

        left, right = 0 ,num


        while right>=left:

            mid = (left+right) // 2

            if mid*mid == num:
                return True
            
            if mid*mid < num:
                left = mid+1
            else:
                right = mid-1

        return False

【LeetCode】704. Binary Search

class Solution:
    def search(self, nums: List[int], target: int) -> int:

        left, right = 0, len(nums) - 1

        while left<=right:

            mid = (left+right) // 2
            if nums[mid] == target:
                return mid

            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1

        return -1

【LeetCode】33. Search in Rotated Sorted Array

分段有序使用二分法的技巧

class Solution:
    def search(self, nums: List[int], target: int) -> int:

        if not nums:
            return -1

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

        while l<=r:
            mid = (l+r) // 2

            if nums[mid] == target:
                return mid

            if nums[0]<= nums[mid]:
                # 左边有序
                if nums[0] <= target < nums[mid]:
                    r = mid - 1
                else:
                    l = mid + 1

            else:
                # 右边有序
                if nums[mid] < target <= nums[len(nums)-1]:

                    l = mid + 1
                else:
                    r = mid - 1

        return -1

【LeetCode】34. Find First and Last Position of Element in Sorted Array

向两端生长


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:

        if len(nums) == 0:
            return [-1,-1]

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

        ans_l = -1
        ans_r = -1


        while l<=r:
            mid = (l+r) // 2
            if nums[mid] == target:
                ans_l, ans_r = mid, mid
                break

            if nums[mid]<target:
                l = mid + 1
            else:
                r = mid - 1
        print(ans_l, ans_r)

        while ans_l>0:
            if nums[ans_l] == nums[ans_l-1]:
                ans_l-=1
            else:
                break

        while ans_r<len(nums)-1 and ans_r != -1:
            if nums[ans_r] == nums[ans_r+1]:
                ans_r+=1
            else:
                break
        return [ans_l,ans_r]

两次使用二分法


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:


        return [self.getPos(nums,target,0,nums.__len__()-1,0),self.getPos(nums,target,0,nums.__len__()-1,1)]


    def getPos(self, nums, target, left, right, sign = 0):

        if not nums: return -1

        while left <= right:

            mid = (left+right) // 2

            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            elif nums[mid] == target:

                if sign == 0:
                    if mid == 0 or nums[mid - 1] < target:
                        return mid
                    right = mid - 1
                else:
                    if mid == len(nums)-1 or nums[mid+1] > target:
                        return mid
                    left = mid + 1

        return -1

【LeetCode】35. Search Insert Position

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left, right = 0, nums.__len__()-1
        while left<=right:
            mid = (left+right) // 2
            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            elif nums[mid] == target:
                return mid
        return left

【LeetCode】74. Search a 2D Matrix

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:

        self.m = matrix.__len__()
        if self.m == 0: return False

        self.n = matrix[0].__len__()
        if self.n == 0: return False

        l,r = 0, self.m*self.n-1

        while l<=r:

            mid = (l+r) // 2
            mid_x,mid_y = self.get_x_y(mid)

            if matrix[mid_x][mid_y]<target:
                l = mid + 1
            elif matrix[mid_x][mid_y]>target:
                r = mid - 1
            elif matrix[mid_x][mid_y] == target:
                return True
        
        return False


    def get_x_y(self, pos):

        x = pos // self.n
        y = pos % self.n

        return x,y

总结

思想很简单,细节是魔鬼,极其考验边界条件处理的能力~

参考

labuladong.github.io/algo/1/6/

leetcode-cn.com/leetbook/de…

今天的文章搞定二分法及其变体(含python二分法标准库实现剖析)分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16659.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注