力扣中的二分法

二分法主要用于排序数组的分类问题

题目一:
剑指 Offer 53 - I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。

示例:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

思路:
因为数组是排序过的,所以只需要找到数字的开始位置和结束位置,相减加一即可。
遍历一遍需要的时间复杂度时O(n),而利用二分法统计需要的时间复杂度为O(log(n))。
进行两次二分法,分别找出数字的结束位置后一个right和开始位置前一个left。

代码:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        i, j = 0, len(nums)-1
        while i <= j:
            m = (i+j)//2
            if nums[m] <= target:
                i = m+1
            else:
                j = m-1
        right = i
        if j >= 0 and nums[j] != target:
            return 0
        i = 0
        while i <= j:
            m = (i+j)//2
            if nums[m] < target:
                i = m+1
            else:
                j = m-1
        left = j
        return right-left-1

题目二:
剑指 Offer 53 - II. 0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8

代码–常规解法:

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        count = 0
        for i in range(len(nums)):
            out = nums[i] - i
            if out > 0:
                return i
            else:
                count += 1
        # 若都按序排列,则返回后一个数,如输入[0,1,2],输出3  
        if count == len(nums):  
            return count

代码–二分法:

思路:跳出时,变量 i 和 j 分别指向 “右子数组的首位元素” 和 “左子数组的末位元素” 。缺失的数字等于 “右子数组的首位元素” 对应的索引,因此返回 i 即可。

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        i, j = 0, len(nums)-1
        while i <= j:
            m = (i+j)//2
            if nums[m] == m:
                i = m+1
            else:
                j = m-1
        return i

题目三:
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。

示例:
输入:[3,4,5,1,2]
输出:1

代码–常规解法:

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        array = [numbers[i+1]-numbers[i] for i in range(len(numbers)-1)]
        for i in range(len(array)):
            if array[i] < 0:
                return numbers[i+1]
        return numbers[0]

代码–二分法:

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        i, j = 0, len(numbers)-1
        while i<j:
            m = (i+j)//2
            if numbers[m] > numbers[j]:
                i = m+1
            elif numbers[m] < numbers[j]:
                j = m
            else:
                j -= 1
        return numbers[i]

解析:
https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/solution/mian-shi-ti-11-xuan-zhuan-shu-zu-de-zui-xiao-shu-3/
分三种情况:

  1. 当 nums[m] > nums[j]时: m 一定在 左排序数组 中,即旋转点索引 x 一定在 [m + 1, j] 闭区间内,因此执行 i = m + 1;
    eg: [3,4,1,2]
  2. 当 nums[m] < nums[j] 时: m 一定在 右排序数组 中,即旋转点索引 x 一定在[i, m] 闭区间内,因此执行 j = m;
    eg: [3,0,1,2]
  3. 当 nums[m] = nums[j] 时:无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i, m] 还是 [m + 1, j] 区间中。解决方案: 执行 j = j - 1 缩小判断范围。事实上,m区间 [i, m] 内所有元素相等 或 区间 [m, j] 内所有元素相等(或两者皆满足)。
    eg: [3,0,1,1,1] 旋转点x为1,在左排序数组;或 [3,4,1,1,1] 旋转点x=2,在右排序数组。

你可能感兴趣的:(力扣刷题,python)