二分查找

0. 二分查找

正常实现

Input : [1,2,3,4,5]
target : 3
return the index : 2
def binarySearch(nums, target):
    l, r = 0, len(nums) - 1
    while l <= h:
        mid = l + (r - l) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] > target:
            r = mid - 1
        elif nums[mid] < target:
            l = mid + 1
    return -1

时间复杂度:二分查找每次将区间减半,使得时间复杂度为 O(logN)

mid 的计算:直接使用 (l + r) // 2 可能会出现加溢出,也就是说加法的结果大于整型能够表示的范围。但 l + (r - l) // 2 不会造成溢出。

1. 求开方

计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

一个数 x 的开方 sqrt 一定在 0 ~ x 之间,并且满足 sqrt == x // sqrt。可以利用二分查找在 0 ~ x 之间查找 sqrt

对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= r 并且循环退出时,r 总是比 l 小 1,也就是说 r = 2,l = 3,因此最后的返回值应该为 r 而不是 l。

class Solution:
    def mySqrt(self, x: int) -> int:
        if x <= 1: return x
        l, r = 1, x
        while l <= r:
            mid = l + (r - l) // 2
            sqrt = x // mid
            if mid == sqrt:
                return mid
            elif mid > sqrt:
                r = mid - 1
            elif mid < sqrt:
                l = mid + 1
        return r

2. 大于给定元素的最小元素

给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。

输入:
letters = ["c", "f", "j"]
target = "a"
输出: "c"

输入:
letters = ["c", "f", "j"]
target = "c"
输出: "f"
class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        n = len(letters)
        l, r = 0, n - 1
        while l <= r:
            mid = l + (r - l) // 2
            if letters[mid] <= target:
                l = mid + 1
            else:
                r = mid - 1
        return letters[l] if l < n else letters[0]

3. 有序数组的 Single Element

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

输入: [1,1,2,3,3,4,4,8,8]
输出: 2

indexSingle Element 在数组中的位置。在 index 之后,数组中原来存在的成对状态被改变。如果 mid 为偶数,并且 mid + 1 < index,那么 nums[mid] == nums[mid + 1]mid + 1 >= index,那么 nums[mid] != nums[mid + 1]

偶奇 偶奇 ... SigleElement 奇偶 奇偶...

从上面的规律可以知道,如果 nums[mid] == nums[mid + 1],那么 index 所在的数组位置为 [mid + 2, r],此时令 l = mid + 2;如果 nums[mid] != nums[mid + 1],那么 index 所在的数组位置为 [l, mid],此时令 r = mid

因为 r 的赋值表达式为 r = mid,那么循环条件也就只能使用 l < r 这种形式。

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        l, r = 0, len(nums) - 1
        while l < r:
            mid = l + (r - l) // 2
            if mid % 2 == 1:  # 保证 l、r、mid 都在偶数位,使得查找区间大小一直都是奇数
                mid -= 1
            if nums[mid] == nums[mid + 1]:
                l = mid + 2
            else:
                r = mid
        return nums[l]

4. 第一个错误的版本

给定一个元素 n 代表有 [1, 2, ..., n] 版本,在第 x 位置开始出现错误版本,导致后面的版本都错误。可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。

如果第 mid 个版本出错,则表示第一个错误的版本在 [l, mid] 之间,令 r = mid - 1;否则第一个错误的版本在 [mid + 1, r] 之间,令 l = mid + 1

# The isBadVersion API is already defined for you.
# @param version, an integer
# @return a bool
# def isBadVersion(version):

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        l, r = 1, n
        while l <= r:
            mid = l + (r - l) // 2
            if isBadVersion(mid):
                r = mid - 1
            else:
                l = mid + 1
        return l

5. 旋转数组的最小数字

假设按照升序排序的数组在预先未知的某个点上进行了旋转。请找出其中最小的元素。

输入: [3,4,5,1,2]
输出: 1
class Solution:
    def findMin(self, nums: List[int]) -> int:
        l, r = 0, len(nums) - 1
        while l < r:
            mid = l + (r - l) // 2
            if nums[mid] <= nums[r]:
                r = mid
            else:
                l = mid + 1
        return nums[l]

6. 查找区间

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def findFirst(nums, target):
            l, r = 0, len(nums)
            while l < r:
                mid = l + (r - l) // 2
                if nums[mid] >= target:
                    r = mid
                else:
                    l = mid + 1
            return l

        first = findFirst(nums, target)
        last = findFirst(nums, target + 1) - 1
        if first == len(nums) or nums[first] != target:
            return [-1, -1]
        else:
            return [first, max(first, last)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值