【LeetCode】小白算法成长记之二分查找

不积跬步,无以至千里;不积小流,无以成江海。

前言

内容主要是个人学习使用,题目分类以及部分参考资料来自于CyC的博客,非常感谢大佬,题目来源于LeetCode,非常感谢本站支持。

二分查找

二分查找又称折半查找,顾名思义就是每查找比较一次,就会去掉一半的不匹配项,重复执行此步骤直到找到目标元素或者不可以在分割

69. x 的平方根(Easy)??

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

示例 1:

输入: 4
输出: 2

示例 2:

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

解题思路:

计算平方根可以采用多种方法,具体可参考LeetCode官方

  1. 对于\(\sqrt{x}\)的值必定存在于0~x,更具体应该是0~\(\frac{x}{2}\),所以采用二分法在此区间查找
  2. 对于\(\sqrt{8}\) = 2.82842...,最后应该返回 2 而不是 3。

代码实现

class Solution:
    def mySqrt(self, x: int) -> int:
        if (x <= 1): # 处理0,1特殊情况
            return x;

        left = 1
        right = x // 2 # 确定区间

        while left <= right:
            mid = left + (right-left) // 2
            num = mid * mid
            if num > x:
                right = mid - 1
            elif num < x:
                left = mid + 1
            elif num == x:
                return mid

        return right

744. 寻找比目标字母大的最小字母(Easy)??

给你一个排序后的字符列表letters,列表中只包含小写英文字母。另给出一个目标字母?target,请你寻找在这一有序列表里比目标字母大的最小字母。

在比较时,字母是依序循环出现的。举个例子:

  • 如果目标字母target = ‘z‘ 并且字符列表为letters = [‘a‘, ‘b‘],则答案返回‘a‘

示例:

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

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

解题思路:

  1. 由于数组有序,可利用二分法查找target
  2. 当目标值大于等于mid值(中间值),改变区间的左边界值
  3. 否则,改变区间的右边界值,最终如果遍历所有字符都没有找到满足要求的值,则输出第一个字符

代码实现

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        left = 0
        right = len(letters) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if letters[mid] > target:
                right = mid - 1
            elif letters[mid] < target:
                left = mid + 1
            else:
                left = mid + 1

        return letters[left] if left < len(letters) else letters[0]

540. 有序数组中的单一元素(Medium)??

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

示例 1:

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

示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

解题思路:

  1. 有序数组中存在一个单一元素,所以数组个数必定是奇数,可以利用二分法分割数组
  2. 分割后的数据,左右两边元素个数相同,举例:
    1. 当分割后的左右数组都是偶数个数:[1,1,2,2,3,3,4,5,5],该数组分割后前[1,1,2,2],后为[3,4,5,5],由于都是偶数个,所以必定有一组是不含单一元素的,我们只需要比较中间元素和前一元素是否相等,不想等的则是正确一组,反之是需要下次查找
    2. 当分割后的左右数组都是奇数个数:[3,3,7,7,10,11,11],该数组分割后前[3,3,7],后为[10,11,11],由于都是奇数个,所以不能确定那一组到底是包含单一元素,我们仍然通过比较中间元素和前一元素是否相等,则相等则表示那一组是正确的,反之不正确。
  3. 通过2.可以看出来,分割为偶数,中间元素与前一元素不相等,表示左边一组正确,右边一组错误,分割为奇数,中间元素与前一元素相等,表示左边一组正确,右边一组错误,

代码实现

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1

        while left <= right:
            mid = left + (right - left) // 2
            tag = len(nums[:mid]) % 2 == 0  # 判断分割的左侧是不是偶数对

            if tag:
                if nums[mid] == nums[mid - 1]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                if nums[mid] == nums[mid - 1]:
                    left = mid + 1
                else:
                    right = mid - 1
        return nums[right]

278. 第一个错误的版本(Easy)??

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用?bool isBadVersion(version)?接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:

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

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

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

解题思路:

  1. 根据题意可知,当我们随机获取几个版本,如果当前版本是正确的,则表示该版本之前的版本都是正确版本,其实就是一个查找最左边界的问题
  2. mid版本是正确的,则表示错误版本处于[mid+1,right],不断缩减右区间
  3. mid版本是错误的,则表示错误版本处于[left,mid],不断缩减左区间

代码实现

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

        return left

153. 寻找旋转排序数组中的最小值 (Medium)??

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组?[0,1,2,4,5,6,7] 可能变为?[4,5,6,7,0,1,2]?)。

请找出其中最小的元素。你可以假设数组中不存在重复元素

示例 1:

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

示例 2:

输入: [4,5,6,7,0,1,2]
输出: 0

解题思路:

  1. 通过题目可以发现,数组经过旋转之后,左边和右边依旧是有序的
  2. 每次循环只需要比较 nums[mid] 和 nums[-1] 的大小关系。
    1. 对于例子:[4, 5, 6, 7, 0, 1, 2], mid = 3, nums[mid] = 7, nums[-1] = 2, 故 nums[mid] > nums[-1],说明数组旋转后较小的一部分升序序列在mid右边,更新区间:left = mid + 1
    2. 对于例子:[6, 7, 1, 2, 3, 4, 5], mid = 3, nums[mid] = 2, nums[-1] = 5,故 nums[mid] < nums[-1],说明数组旋转后较小的一部分升序序列在mid左边,更新区间:right = mid - 1

代码实现

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] > nums[-1]:
                left = mid + 1
            else:
                right = mid - 1

        return nums[left]

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是?O(log n) 级别。

如果数组中不存在目标值,返回?[-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例?2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

解题思路:

  1. 根据题意得知,寻找一个数的左边界和右边界,所以可以才用两次二分查找

代码实现

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        left = 0
        right = len(nums) - 1
        ret = []
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] > target:
                right = mid - 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        if left == len(nums):
            ret.append(-1)
        else:
            ret.append(left if nums[left] == target else -1)

        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] > target:
                right = mid - 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                left = mid + 1
        if left == 0:
            ret.append(-1)
        else:
            ret.append(left-1 if nums[left-1] == target else -1)

        return ret

相关推荐