日常系列LeetCode《8·二分查找篇》

Posted 常某某的好奇心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常系列LeetCode《8·二分查找篇》相关的知识,希望对你有一定的参考价值。

数据规模->时间复杂度

<=10^4 😮(n^2)
<=10^7:o(nlogn)
<=10^8:o(n)
10^8<=:o(logn),o(1)

总结

1.二分查找的基础知识
基本的二分查找-(规则:利用数组的有序性,查中间的元素)
普通二分查找的复杂度:o(logn),o(1)
迭代二分查找的复杂度:o(logn),o(logn)


#适用:有序数组(这里以升序列表为例)
#o(logn),o(1)
#方法1:不断找中间值
def contains(nums,target):
    if nums is None or len(nums)==0:
        return False

    left,right=0,len(nums)-1
    while left<=right:
        #溢出bug:mid=(left+right)/2(因为int最大值2^31-1=2147483647)
        mid=left+(right-right)//2
        if target==nums[mid]:
            return True
        elif target<nums[mid]:
            right=mid-1
        else:
            right=mid+1
    	return False

#方法二:递归写法
#o(logn),o(logn)
def containsR(nums,target):
    if nums is None or len(nums)==0:
        return False

    return contains_help(nums,0,len(nums)-1,target)

def contains_help(nums,left,right,target):
    #终止条件
    if left>right:
        return False
    #公式
    mid=left+(right-left)//2
    if nums[mid]==target:
        return True
    elif target<nums[mid]:
        return contains_help(nums,left,right-1,target)
    else:
        return contains_help(nums,left-1,right,target)

2.变形二分查找算法及其应用

#返回第一个等于Target的index
def firstTargetIndex(nums,target):
    if nums is None or len(nums)==0:
        return

    left,right=0,len(nums)-1
    while left<=right:
        mid=left+(right-left)//2
        if target==nums[mid]:
            if mid==0 or nums[mid-1]!=target:
                return mid
            else:
                right=mid-1
        elif target>nums[mid]:
            left=mid+1
        else:
            right=mid-1
        return -1

#返回第一个大于或等于Target的index
def firstGEtargetIndex(nums,target):
    if nums is None or len(nums)==0:
        return
    
    left,right=0,len(nums)-1
    while left<=right:
        mid=left+(right-left)//2
        if nums[mid]>=target:
            if mid==0 or nums[mid-1]<target:
                return mid
            else:
                right=mid-1
        
        else:
            left=mid+1
        
        return -1
    
#返回最后一个等于Target的index
def lastTargetIndex(nums,target):
    if nums is None or len(nums)==0:
        return
    
    left,right=0,len(nums)-1
    while left<=right:
        mid=left+(right-left)//2
        if nums[mid]==target:
            if mid==len(nums)-1 or nums[mid+1]!=target:
                return mid
            else:
                left=mid+1
        elif nums[mid]>target:
            right=mid-1
        else:
            left=mid+1
        return -1
            
            
        
# 返回最后一个小于或等于Target的index
def lastLETargetIndex(nums,target):
    if nums is None or len(nums)==0:
        return
    
    left,right=0,len(nums)
    while left<=right:
        mid=left+(right-left)//2
        if nums[mid]<=target:
            if mid==len(nums)-1 or nums[mid+1]>target:
                return mid
            else:
                left=mid+1
        else:
            right=mid-1
        #left>tight没有元素
        return -1

3.山脉数组、旋转数组

4.有的时候,需要对题目抽象,才能找出二分的性质

lc 704:二分查找
https://leetcode.cn/problems/binary-search/
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

#思路一:不断在循环体中查找目标元素
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        #
        n=len(nums)
        if n==0:return -1
        #o(logn),o(1)
        left=0
        right=n-1
        while left<=right:
            mid=left+(right-left)//2
            if nums[mid]==target:
                return mid
            elif nums[mid]>target:
                right-=1
            else:left=mid+1
        #
        return -1
#思路二:在循环体中排除一定不存在目标元素的区间
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        #
        n=len(nums)
        if n==0:return -1
        #o(logn),o(1)
        left=0
        right=n-1
        while left<right:
            mid=left+(right-left+1)//2#防止死循环(left=mid)
            if nums[mid]>target:
                right=mid-1
            else:left=mid
        #循环后处理
        if nums[left]==target:return left
        #
        return -1

lc 34【top100】:排序数组中找元素的第一个和最后一个位置
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
提示:
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
nums 是一个非递减数组
-10^9 <= target <= 10^9

#思路一:
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        #o(1)
        if len(nums) == 0: return [-1, -1]
        #o(logn)
        id1=self.firstsearch(nums,target)
        id2=self.secondsearch(nums,target)
        #
        if id1==-1 or id2==-1:return [-1,-1]
        else:return [id1,id2] 

    def firstsearch(self,nums,target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right-left) // 2
            if nums[mid] == target:
                if mid == 0 or nums[mid - 1] != target: return mid
                else: right = mid - 1
            elif nums[mid] < target: left = mid + 1
            else: right = mid - 1
        return -1
    def secondsearch(self,nums,target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right-left) // 2
            if nums[mid] == target:
                if mid == len(nums) - 1 or nums[mid + 1] != target: return mid
                else: left = mid + 1
            elif nums[mid] < target: left = mid + 1
            else: right = mid - 1
        #
        return -1
 
 #思路二(超时+不推荐):       
 class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        #o(1)
        if len(nums) == 0: return [-1, -1]
        #o(logn)
        id1=self.firstsearch(nums,target)
        id2=self.secondsearch(nums,target)
        #
        if id1==-1 or id2==-1:return [-1,-1]
        else:return [id1,id2] 

    def firstsearch(self,nums,target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right-left) // 2
            if nums[mid] < target: #key:第一个
                left=mid+1
            else: right = mid
        if nums[left]==target:return left
        else:return -1
    def secondsearch(self,nums,target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right-left+1) // 2
            if nums[mid] > target: #key:最后一个
                right=mid-1
            else: left = mid
        if nums[left]==target:return left
        else:return -1                       

lc 35 【剑指 53-1】【top100】:搜索插入位置
https://leetcode.cn/problems/search-insert-position/
提示:
1 <= nums.length <= 10^4
-104 <= nums[i] <= 10^4
nums 为 无重复元素 的 升序 排列数组
-10^4 <= target <= 10^4

#本质:找到第一个大于等于target 的元素的下标
#思路一
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        #
        n=len(nums)
        if n==0:return 0
        if target>nums[n-1]:return n
        #o(logn),o(1)
        left=0
        right=n-1
        while left<=right:
            mid=left+(right-left)//2
            if  nums[mid]>=target:
                if mid==0 or nums[mid-1]<target:return mid
                else:right=mid-1
            else:left=mid+1
        return -1
#思路二:优化
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        #
        n=len(nums)
        if n==0:return 0
        #if target>nums[n-1]:return n
        #o(logn),o(1)
        left=0
        right=n#n-1
        while left<right:
            mid=left+(right-left)//2
            if nums[mid]<target: #‘第一等于target’
                left=mid+1
            else:right=mid
        return left

lc 278:第一个错误的版本
https://leetcode.cn/problems/first-bad-version/
提示:
1 <= bad <= n <= 2^31 - 1

#key:在一个升序的数组中,找到第一个等于n的位置
#思路一:
# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:

class Solution:
    def firstBadVersion(self, n: int) -> int:
        #o(logn),o(1)
        left=1
        right=n
        while left<=right:
            mid=left+(right-left)//2
            if isBadVersion(mid):
                if mid==1 or isBadVersion(mid-1)==False:return mid
                else:right=mid-1
            else:left=mid+1
        return -1
        
#思路二:
# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:

class Solution:
    def firstBadVersion(self, n: int) -> int:
        #o(logn),o(1)
        left=1
        right=n
        while left<right:
            mid=left+(right-left)//2
            if isBadVersion(mid):
                right=mid #key
            else:left=mid+1
        return left if isBadVersion(mid) else -1 #key

lc 33【top100】:搜索旋转排序数组
https://leetcode.cn/problems/search-in-rotated-sorted-array/
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-10^4 <= target <= 10^4

'''
key:部分有序性
nums[1eft]<= nums[mid]:[left,mid]有序
nums[1eft]> nums[mid]:[mid,right]有序
'''
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        #o(n),o(1)
        n=len(nums)
        if n==0:return -1
        #
        left=0
        right=n-1
        while left<=right:
            mid=left+(right-left)//2
            #
            if nums[mid]==target:return mid
            #左有序
            if nums[left]<=nums[mid]:
                if nums[left]<=target<nums[mid]:
                    right=mid-1
                else:left=mid+1
            #右有序
            else:
                if nums[mid]<target<=nums[right]:
                    left=mid+1
                else:right=mid-1
        #
        return -1        

lc 153:寻找旋转排序数组中的最小值
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/
提示:
n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 中的所有整数 互不相同
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

#暴力
class Solution:
    def findMin(self, nums: List[int]) -> int:
       #o(n),o(1)
        n=len(nums)
        for i in range(1,n):
            if nums[i]<nums[i-1]:
                return nums[i]
        return nums[0]
#二分
class Solution:
    def findMin(self, nums: List[int]) -> int:
        #o(n),o(1)
        if len(nums)==0:return 0
        left=0
        right=len(nums)-1
        #
        while left<right:
            mid=left

以上是关于日常系列LeetCode《8·二分查找篇》的主要内容,如果未能解决你的问题,请参考以下文章

日常系列LeetCode《13·综合应用1篇》

卷进大厂系列之LeetCode刷题笔记:二分查找(简单)

[LeetCode] 240. 搜索二维矩阵 II ☆☆☆(二分查找类似)

[二分查找] 在排序数组中查找元素的第一个和最后一个位置

日常系列LeetCode《2·一维数组篇》

日常系列LeetCode《5·数学篇》