Loading

力扣刷题笔记 - 二分查找

前言

力扣刷题笔记第一弹:二分查找。
记录和分享刷题过程中的收获,愿我们成为更好的自己!!!
刷题顺序参考代码随想录,代码通过Python实现。(在VScode中安装LeetCode插件,编码体验远超网页)

题目1(E 704.二分查找)

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

代码

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left, right = 0, len(nums)-1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            else:
                return mid
        return -1

笔记

​ 二分法要根据区间的不同来编码,有两种区间:

  1. 左闭右闭,理解为给定一个数组的全部内容,上方代码为左闭右闭的实现
  2. 左闭右开,理解为给定一个数组的部分内容(子区间),下方代码为左闭右开实现
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left,right  =0, len(nums)
        while left < right: # 当右开时,左指针就不可以等于右指针
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid # 当中指针不符合可直接用右指针替换
            else:
                return mid
        return -1

题目2(E 35.搜索插入位置)

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

代码

同样注意区间,此处应为左闭右闭区间

class Solution(object):
    def searchInsert(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums) - 1

        while left <= right: # 当left=right时可以顾及到目标值在数组外的情况
            mid = (left + right) // 2

            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            else:
                return mid
            
        return right + 1 
  • target小于数组:程序执行到left=right=0时,nums[mid]依然大于target,执行right = mid - 1,此时left=0,right=-1,不符合循环条件跳出循环,执行return right + 1,返回位置为0
  • target大于数组:同理,记数组右边界值为n,当程序执行到left=right=n时,nums[mid]依然小于target,执行right = mid + 1,此时left=n+1,right=n,跳出循环,返回位置为n=1

题目3(M 34. 在排序数组中查找元素的第一个和最后一个位置)

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

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

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

输入:nums = [], target = 0
输出:[-1,-1]

代码

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        def binarySearch(nums, target):
            left, right = 0, len(nums)-1
            while left <= right:
                mid = left + (right - left) // 2

                if nums[mid] >= target:
                    right = mid - 1
                else:
                    left = mid + 1
            return left
        
        leftBorder = binarySearch(nums, target)
        rightBorder = binarySearch(nums, target+1) - 1

        if leftBorder == len(nums) or nums[leftBorder] != target:
            return [-1, -1]
        return [leftBorder, rightBorder]

其他方法

# 解法1,单独求解
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def getRightBorder(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1
            rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
            while left <= right:
                middle = left + (right-left) // 2
                if nums[middle] > target:
                    right = middle - 1
                else: # 寻找右边界,nums[middle] == target的时候更新left
                    left = middle + 1
                    rightBoder = left
    
            return rightBoder
        
        def getLeftBorder(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1 
            leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
            while left <= right:
                middle = left + (right-left) // 2
                if nums[middle] >= target: #  寻找左边界,nums[middle] == target的时候更新right
                    right = middle - 1;
                    leftBoder = right;
                else:
                    left = middle + 1
            return leftBoder
        leftBoder = getLeftBorder(nums, target)
        rightBoder = getRightBorder(nums, target)
        # 情况一
        if leftBoder == -2 or rightBoder == -2: return [-1, -1]
        # 情况三
        if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
        # 情况二
        return [-1, -1]
# 解法2
# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def binarySearch(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1
            while left<=right: # 不变量:左闭右闭区间
                middle = left + (right-left) // 2
                if nums[middle] > target:
                    right = middle - 1
                elif nums[middle] < target: 
                    left = middle + 1
                else:
                    return middle
            return -1
        index = binarySearch(nums, target)
        if index == -1:return [-1, -1] # nums 中不存在 target,直接返回 {-1, -1}
        # nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
        left, right = index, index
        # 向左滑动,找左边界
        while left -1 >=0 and nums[left - 1] == target: left -=1
        # 向右滑动,找右边界
        while right+1 < len(nums) and nums[right + 1] == target: right +=1
        return [left, right]
# 解法3
# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标(左边界)与第一个大于target的下标(右边界);
# 2、如果左边界<= 右边界,则返回 [左边界, 右边界]。否则返回[-1, -1]
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def binarySearch(nums:List[int], target:int, lower:bool) -> int:
            left, right = 0, len(nums)-1
            ans = len(nums)
            while left<=right: # 不变量:左闭右闭区间
                middle = left + (right-left) //2 
                # lower为True,执行前半部分,找到第一个大于等于 target的下标 ,否则找到第一个大于target的下标
                if nums[middle] > target or (lower and nums[middle] >= target): 
                    right = middle - 1
                    ans = middle
                else: 
                    left = middle + 1
            return ans

        leftBorder = binarySearch(nums, target, True) # 搜索左边界
        rightBorder = binarySearch(nums, target, False) -1  # 搜索右边界
        if leftBorder<= rightBorder and rightBorder< len(nums) and nums[leftBorder] == target and  nums[rightBorder] == target:
            return [leftBorder, rightBorder]
        return [-1, -1]

题目4(E 69. x的平方根)

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

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

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

代码

class Solution(object):
    def mySqrt(self, x):
        """
        :type x: int
        :rtype: int
        """
        left, right = 1, x 
        if x == 1: return 1 # 减少计算x=1时的资源消耗

        while left <= right:
            mid = left + (right - left) // 2

            if mid > x/(mid*1.0): # 用x/mid较mid*mid更节省内存,而mid*1.0可以防止溢出
                right = mid - 1
            elif mid < x/(mid*1.0):
                left = mid + 1
            else: 
                return mid

        return left - 1

题目5(E 367. 有效的完全平方数)

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如 sqrt 。

示例 1:

输入:num = 16
输出:true
示例 2:

输入:num = 14
输出:false

代码

与题目4代码相同,更改返回值即可

class Solution(object):
    def isPerfectSquare(self, num):
        """
        :type num: int
        :rtype: bool
        """
        left, right = 1, num
        if num == 1: return True

        while left <= right:
            mid = left + (right - left) // 2

            if mid > num/(mid*1.0):
                right = mid - 1
            elif mid < num/(mid*1.0):
                left = mid + 1
            else: 
                return True

        return False

小结

对于在一个数组内查找指定值的问题可以使用二分查找,需要注意两点:

  1. 区间:根据区间的不同,判断条件不同,对于左右指针的移动也不同。
  2. 防止溢出:编写代码时注意溢出,防止溢出可以节省时间和内存的消耗
posted @ 2022-07-03 17:22  KoiC  阅读(100)  评论(0)    收藏  举报