Leetcode_二分查找法

二分查找是在“有序数组”中利用“中值”找目标的问题

  • 时间复杂度:O(logN)

两种求中值方法:

  • med = (left + right) // 2
    • med = (left + right) >> 1 替代写法,跟上面是等价的,速度快一些
  • med = left + (right - left) // 2 #应该使用这种,因为第一种存在out of range情况
  • %:表示余数;//表示:地板除(向下取整),在python3中使用。python2没有使用浮点数,则都是地板除

二分查找相对简单,理解while循环和细节就好

  • while left<right,则跳出循环时left=right
  • while left <= right,则跳出循环时 left = right + 1
  • nums[mid]&nums[left]&nums[left]三者的比较就是二分法的核心,针对不同题目变通就好

69. x 的平方根


链接:https://leetcode-cn.com/problems/sqrtx/

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

example:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

解题思路

对于开方根求法:

  • sqrt = x // sqrt
  • 对比medium与x//medium来判断中值medium在开方根左侧还是右侧。
  • 左侧:更新left至med + 1;右侧:更新right至med - 1
  • 因退出循环时,right = left-1,即right<left。而对于2.83323,我们取2而非3。所以输出right

代码

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

374. 猜数字大小


链接:https://leetcode-cn.com/problems/guess-number-higher-or-lower

  • 我们正在玩一个猜数字游戏。 游戏规则如下:
  • 我从1到n选择一个数字。 你需要猜我选择了哪个数字。
  • 每次你猜错了,我会告诉你这个数字是大了还是小了。通过调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
    • -1 : 我的数字比较小
    • 1 : 我的数字比较大
    • 0 : 恭喜!你猜对了!

解题思路

二分查找

  • 二分查找标准解法,中间调用一次guess API比大小即可

代码

# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num: int) -> int:

class Solution:
    def guessNumber(self, n: int) -> int:
        l,r = 1,n
        while l <= r:
            med = l + ( r - l ) // 2
            dst = guess(med)
            if dst == 1:
                l = med + 1
            elif dst == -1:
                r = med - 1
            else:
                return med

744. 寻找比目标字母大的最小字母


链接:https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/

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

解题思路

主干二分查找法,本题有两点需要注意:

  1. 当中值letters[med]=target时,需要输出后一位,等价于letters[med]<target
  2. 输出后一位存在超过边界情况(out of range),所以需要判断是否大于边界
  3. 因为要找大的,最后退出循环时l是比r大的,所以输出l。

代码

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        l = 0
        r = len(letters) -1
        while l <= r:
            med = l + ( r - l ) // 2
            if letters[med] <= target:
                l = med + 1
            elif letters[med] > target:
                r = med - 1
        if l >= len(letters):
            return letters[0]
        return letters[l]

540. 有序数组中的单一元素


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

链接:https://leetcode-cn.com/problems/single-element-in-a-sorted-array/

examples:

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

解题思路

  1. %:表示余数;//表示:地板除(向下取整),在python3中使用。python2没有使用浮点数,则都是地板除
  2. 规律:只有一个单数,其余都双数,所以总数是基数。
  3. 去除中值与旁边相同的数,目标在剩余数组中基数侧。如果中值本身就是孤立直接输出。
  4. 有个坑:在elif循环里,search_odd算上了当前nums[med,所以这里找的是偶数侧。

代码

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        l,h = 0,len(nums)-1
        while l < h:
            med = l + (h - l)//2
            search_odd = (med - l)%2 == 0 
            if nums[med] == nums[med-1]:
                if search_odd:
                    h = med -2
                else:
                    l = med + 1
            elif nums[med] == nums[med+1]:
                if search_odd:
                    l = med + 2
                else:
                    h = med - 1
            else:
                return nums[med]
        return nums[l]

278. 第一个错误的版本


假设你有 n 个版本 [1, 2, …, n],在第x版本出错导致[x:n]版本都错,找到x。可以调用isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

链接:https://leetcode-cn.com/problems/first-bad-version

example:

给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。

解题思路

很标准的二分查找法

代码

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

class Solution(object):
    def firstBadVersion(self, n):
        start = 1
        end = n
        while start <= end:
            mid = start + (end - start) // 2
            if isBadVersion(mid):
                end = mid -1
            else:
                start = mid + 1
        return start

153. 寻找旋转排序数组中的最小值


链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/

升序数组在某点转折,找出数组的最小值

example:

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

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

解题思路

  1. 二分查找法
  2. 一个小坑是输出[1,2,3]这样的,旋转点就是nums[0],直接return nums[0]
  3. 循环里面:出现nums[i+1]<nums[i],i+1就是转折点,nums[i+1]也就是最小值。
  4. nums[start]<nums[mid]:意味着[start:mid]中间都是递增的没有转折点,start=mid + 1
  5. nums[start]> nums[mid]:意味着[start:mid]中间有转折点,mid也可能是转折点需要被判断,所以end = mid
  6. 循环结束时,start=end,返回nums[end]即可

代码

class Solution(object):
    def findMin(self, nums):
        start,end = 0,len(nums)-1
        if nums[end]>nums[0]:
            return nums[0]
        while start < end:
            mid = start + ( end - start ) // 2
            if nums[mid] > nums[start]:
                start = mid + 1
            else:
                end = mid
            if nums[mid+1]<nums[mid]:
                return nums[mid+1]
            if nums[mid]<nums[mid-1]:
                return nums[mid]
        return nums[end]
        

34. 在排序数组中查找元素的第一个和最后一个位置


升序排列的整数数组 nums,和一个目标值 target。找出target在nums中的开始位置和结束位置。如不存在目标值,返回 [-1, -1]。

链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

example:

解题思路

  1. 二分查找法
  2. start:左边界,end:右边界
  3. 先说特例:nums本身为空,输出[-1,-1],开头排除掉
  4. 找左边界:
    • 要找左边界,end停靠在左边界位置(nums[end]=target),利用start=mid+1来找
    • 特例:如果nums[0]=target,左边界就是0,先排除掉
    • while start<end:则跳出循环时start = end,且nums[start]=nums[end]=target,输出哪个都可以
  5. 找右边界:
    • 要找右边界,end必须停靠在右边界+1位置上(nums[end]!=target),利用start=mid+1来找
      • 这里不用start=mid因为mid是向下取整,会无法跳出循环;
      • 如[2,3],target=2时,当start和end的位置在1时才能跳出循环,但start永远在index=0处循环
    • 特例:如果nums[len(nums)-1]=target,右边界就是len(nums)-1,先排除掉
    • while start<end:则跳出循环时,start = end,且nums[start-1]=nums[end-1]=target

代码

class Solution(object):
    def searchRange(self, nums, target):
        if not nums:
            return[-1,-1]
        #find left_index
        start,end = 0,len(nums)-1
        if nums[start] == target:
            left_index = 0
        else:
            while start < end:
                mid = start + ( end -start ) // 2
                if nums[mid] >= target:
                    end = mid
                else:
                    start = mid + 1        
            left_index = start

        #find right_index
        start,end = 0,len(nums)-1
        if nums[end] == target:
            right_index = len(nums) - 1
        else:
            while start < end:
                mid = start + (end - start) // 2
                if nums[mid] > target:
                    end = mid
                else:
                    start = mid + 1
            right_index = start -1

        if left_index == len(nums) or nums[left_index] != target:
            return [-1,-1]
        return [left_index,right_index]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值