leetcode分类刷题:二分查找(Binary Search)(一、基于索引(定义域)的类型)

参加了下2023届秋招,不得不感叹leetcode&lintcode的题目实在太多了(也很难),特别对于我这种非科班生而言,感觉有必要分类整理一下,以便以后在工作中更好的应用。花了点时间刷了下二分查找的相关题目,刷的越多,反而越不会了,先把目前遇到的几个题目总结下吧。

基于索引(定义域)的类型的题目一般规律是:给定一维升序数组和目标值target,寻找等于或小于(大于)目标值的索引位置。
简单题目是判断目标值存在与否;
中等难度题目是判断目标值存在的左右边界,左边界需要查找的是第一个大于等于target的值索引,右边界需要查找的是第一个大于target的值索引减1,进一步根据左右边界构成的闭区间是否存在判断目标边界情况;
再难一点就是理解目标值起到将升序数组一分为二的效果:左侧数组小于目标值,右侧数组大于等于目标值,相当于是把二分法作为解题的第一步,以此为基础,再进行第二步算法及第三步算法等,常见于几种算法混合求解的困难题目。

leetcode 704. 二分查找

该题在网上的解答有很多,强烈推荐代码随想录的讲解,条理清晰。

from typing import List
'''
704. 二分查找
题目描述:给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回 -1。
示例 1:
    输入: nums = [-1,0,3,5,9,12], target = 9
    输出: 4
    解释: 9 出现在 nums 中并且下标为 4
题眼:有序数组且无重复(有序是二分法的前提)
思路:二分法,[left, right]左闭右闭区间,用middle更新左右边界,难点是准确把握循环终止时的状态是left>right。
'''


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 情况1:target小于nums[0]、target大于nums[-1](提示里有n>=1说明数组不为空)
        if target < nums[0] or target > nums[len(nums)-1]:
            return -1
        # 情况2:traget==nums[mid],使用二分法
        left, right = 0, len(nums) - 1
        while left <= right:  # [left, right],当left==right是有意义的
            middle = left + (right - left) // 2
            # [left, right] 决定了middle区间的更新原则和while循环条件
            if nums[middle] > target:
                right = middle - 1  # [left, middle-1]
            elif nums[middle] < target:
                left = middle + 1  # [middle+1, right]
            else:
                return middle
        # 情况3:以上情况全不满足(target范围在数组中间,但不存在target),此时right+1=left
        return -1


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
            target = int(in_line[2])
            print(obj.search(nums, target))
        except EOFError:
            break

leetcode 35. 搜索插入位置

from typing import List

'''
35. 搜索插入位置
题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
    输入: nums = [1,3,5,6], target = 5
    输出: 2
题眼:有序数组且无重复(有序是二分法的前提), O(log n) 的算法。
思路:“704. 二分查找” 的升级版,目标不存在时返回的不是-1了,要返回顺序插入的位置。
'''


class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # 情况1:target小于nums[0](提示里有n>=1说明数组不为空)
        if target < nums[0]:
            return 0
        # 情况2:target大于nums[-1]
        if target > nums[len(nums)-1]:
            return len(nums)
        # 情况3:traget==nums[mid],使用二分法
        left, right = 0, len(nums) - 1
        while left <= right:  # [left, right],当left==right是有意义的
            middle = left + (right - left) // 2
            # [left, right] 决定了middle区间的更新原则和while循环条件
            if nums[middle] > target:
                right = middle - 1  # [left, middle-1]
            elif nums[middle] < target:
                left = middle + 1  # [middle+1, right]
            else:
                return middle
        # 情况4:以上情况全不满足(target范围在数组中间,但不存在target),此时right+1=left,要返回顺序插入的位置
        return right + 1  # 此时,要清楚掌握二分法退出循环时的left、right取值情况


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
            target = int(in_line[2])
            print(obj.searchInsert(nums, target))
        except EOFError:
            break

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

这个题目有点难度了,当时做这个题目花了好久时间,推荐代码随想录的讲解。

from typing import List
'''
658. 找到 K 个最接近的元素
题目描述:给定一个 排序好 的数组arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
示例 1:
    输入:arr = [1,2,3,4,5], k = 4, x = 3
    输出:[1,2,3,4]
题眼:有序数组
思路:(有点难度,看了官方解答才明白)二分法找到分界点,左侧小于x,右侧大于等于x;
      然后 从分界点开始向左右两侧取最接近的k个值
'''
import bisect


class Solution:
    def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
        # 第一步,将数组一分为二,左侧位置[0,left]元素全部小于x,右侧位置[right,len(arr)-1]元素全部大于等于x
        right = bisect.bisect_left(arr, x)  # 返回第一个大于等于x的元素索引 0~len(arr)
        left = right - 1
        # right = self.binarySearchLeftBorder(arr, x)
        # 第二步,此时left和right位置分别对应各自部分最接近x的元素(在left和right有效时)
        for _ in range(k):
            if left < 0:  # 左侧已经到达边界,更新right指针
                right += 1
            elif right > len(arr) - 1:  # 右侧已经到达边界,更新left指针
                left -= 1
            elif x - arr[left] <= arr[right] - x:  # 左侧值属于答案范围,更新left指针
                left -= 1
            elif x - arr[left] > arr[right] - x:  # 右侧值属于答案范围,更新right指针
                right += 1
        # 此时得到答案区间[left+1: right-1],为了取到右侧边界,最终返回
        return arr[left+1: right]

    def binarySearchLeftBorder(self, arr: List[int], x: int) -> int:
        if x < arr[0]:
            return 0
        elif x > arr[len(arr)-1]:
            return len(arr)
        else:
            left, right = 0, len(arr) - 1
            while left <= right:
                mid = left + (right - left) // 2
                if arr[mid] >= x:  # 寻找左边界,用right指针,所以等于时更新right
                    right = mid - 1
                elif arr[mid] < x:
                    left = mid + 1
            return left


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
            k = int(in_line[2].split(',')[0])
            x = int(in_line[3])
            # print(nums, k, x)
            print(obj.findClosestElements(nums, k, x))
        except EOFError:
            break

leetcode 658. 找到 K 个最接近的元素

from typing import List
'''
658. 找到 K 个最接近的元素
题目描述:给定一个 排序好 的数组arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
示例 1:
    输入:arr = [1,2,3,4,5], k = 4, x = 3
    输出:[1,2,3,4]
题眼:有序数组
思路:(有点难度,看了官方解答才明白)二分法找到分界点,左侧小于x,右侧大于等于x;
      然后 从分界点开始向左右两侧取最接近的k个值
'''
import bisect


class Solution:
    def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
        # 第一步,将数组一分为二,左侧位置[0,left]元素全部小于x,右侧位置[right,len(arr)-1]元素全部大于等于x
        right = bisect.bisect_left(arr, x)  # 返回第一个大于等于x的元素索引 0~len(arr)
        left = right - 1
        # right = self.binarySearchLeftBorder(arr, x)
        # 第二步,此时left和right位置分别对应各自部分最接近x的元素(在left和right有效时)
        for _ in range(k):
            if left < 0:  # 左侧已经到达边界,更新right指针
                right += 1
            elif right > len(arr) - 1:  # 右侧已经到达边界,更新left指针
                left -= 1
            elif x - arr[left] <= arr[right] - x:  # 左侧值属于答案范围,更新left指针
                left -= 1
            elif x - arr[left] > arr[right] - x:  # 右侧值属于答案范围,更新right指针
                right += 1
        # 此时得到答案区间[left+1: right-1],为了取到右侧边界,最终返回
        return arr[left+1: right]

    def binarySearchLeftBorder(self, arr: List[int], x: int) -> int:
        if x < arr[0]:
            return 0
        elif x > arr[len(arr)-1]:
            return len(arr)
        else:
            left, right = 0, len(arr) - 1
            while left <= right:
                mid = left + (right - left) // 2
                if arr[mid] >= x:  # 寻找左边界,用right指针,所以等于时更新right
                    right = mid - 1
                elif arr[mid] < x:
                    left = mid + 1
            return left


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
            k = int(in_line[2].split(',')[0])
            x = int(in_line[3])
            # print(nums, k, x)
            print(obj.findClosestElements(nums, k, x))
        except EOFError:
            break

个人总结体会

二分法从宏观思想上来说是将一个有序序列对半查找,落实到具体的题目里掌握其关键点:准确把握循环终止时的状态,即left和right指针的相对位置关系(左闭右闭区间法则下是left-right=1),从而能够轻松应对和处理确定等于(小于或大于)某个target值在有序序列的边界位置,像"leetcode 658. 找到 K 个最接近的元素"这种复合型的题目,第一步用二分法找到二分位置、第二步用双指针寻找答案区间就很难想到对应解法,只能不断练习…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

22世纪冲刺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值