【日常系列】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+(right-left)//2
            #右区小
            if nums[mid]>nums[right]:
                left=mid+1
            #左区小
            else:
                right=mid
        #
        return nums[left]

lc 154【剑指 11】:寻找旋转排序数组中的最小值 II
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/
提示:
n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
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)
        n=len(nums)
        left=0
        right=n-1
        while left<right:
            mid=left+(right-left)//2
            if nums[mid]>nums[right]:
                left=mid+1
            elif nums[mid]<nums[right]:
                right=mid
            else:
                right-=1 #key
        #
        return nums[left]

lc 852【剑指 069】:山脉数组的峰顶索引
https://leetcode.cn/problems/peak-index-in-a-mountain-array/
提示:
3 <= arr.length <= 10^4
0 <= arr[i] <= 10^6
题目数据保证 arr 是一个山脉数组

#解法一:线性遍历
class Solution:
    def peakIndexInMountainArray(self, arr: List[int]) -> int:
        #(n),o(1)
        n=len(arr)
        i=0
        while i<n-1 and arr[i]<arr[i+1]:i+=1
        return i
        
#解法二:二分
class Solution:
    def peakIndexInMountainArray(self, arr: List[int]) -> int:
        #(logn),o(1)
        n=len(arr)
        left=0
        right=n-1
        while left<right:
            mid=left+(right-left)//2
            if arr[mid]<arr[mid+1]:
                left=mid+1
            else:right=mid
        return left          

lc 1095:山脉数组中查找目标值
https://leetcode.cn/problems/find-in-mountain-array/
提示:
3 <= mountain_arr.length() <= 10000
0 <= target <= 10^9
0 <= mountain_arr.get(index) <= 10^9

# """
# This is MountainArray's API interface.
# You should not implement it, or speculate about its implementation
# """
#class MountainArray:
#    def get(self, index: int) -> int:
#    def length(self) -> int:

class Solution:
    def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
        #
        self.n=mountain_arr.length()
        peakindex=self.search_peak(mountain_arr)
        #o(3logn)
        if mountain_arr.get(peakindex)==target:return peakindex
        index=self.search_target1(mountain_arr,0,peakindex-1,target) #有序
        if index != -1:return index
        return self.search_target2(mountain_arr,peakindex+1,self.n-1,target)#有序
    def search_peak(self,nums):
        left=0
        right=self.n-1
        while left<right:
            mid=left+(right-left)//2
            if nums.get(mid)<nums.get(mid+1):
                left=mid+1
            else:right=mid
        return left 
    def search_target1(self,nums,lo,hi,target):#升序
        left=lo
        right=hi
        while left<=right:
            mid=left+(right-left)//2
            if nums.get(mid)==target:return mid
            elif nums.get(mid)<target:
                left=mid+1
            else:right=mid-1
        return -1
    def search_target2(self,nums,lo,hi,target):#降序
        left=lo
        right=hi
        while left<=right:
            mid=left+(right-left)//2
            if nums.get(mid)==target:return mid
            elif nums.get(mid)>target:
                left=mid+1
            else:right=mid-1
        return -1   

lc 162:寻找峰值
https://leetcode.cn/problems/find-peak-element/
提示:
1 <= nums.length <= 1000
-2^31 <= nums[i] <= 2^31 - 1
对于所有有效的 i 都有 nums[i] != nums[i + 1]

#方案一:线性
class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        #o(n),o(1)
        n=len(nums)
        if n==1:return 0
        for i in range(n-1):
            if nums[i]>nums[i+1]:return i
        return n-1
#方案二:二分
class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        #o(logn),o(1)
        n=len(nums)
        left=0
        right=n-1
        while left<right:
            mid=left+(right-left)//2
            if nums[mid]<nums[mid+1]:
                left=mid+1
            else:right=mid
        return left

lc 74:搜索二维矩阵
https://leetcode.cn/problems/search-a-2d-matrix/
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 100
-10^4 <= matrix[i][j], target <= 10^4

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        #o(logm*n),o(1)
        left=0
        m=len(matrix)
        n=len(matrix[0])
        right=m*n-1
        while left<=right:
            mid=left+(right-left)//2
            if matrix[mid//n][mid%n]==target:return True #key:二维对一维-matrix[mid//n][mid%n]
            elif matrix[mid//n][mid%n]<target:left=mid+1
            else:right=mid-1
        return False

lc 240 【剑指 4】【top100】:搜索二维矩阵 II
https://leetcode.cn/problems/search-a-2d-matrix-ii/
提示:
m == matrix.length
n == matrix[i].length
1 <= n, m <= 300
-10^9 <= matrix[i][j] <= 10^9
每行的所有元素从左到右升序排列
每列的所有元素从上到下升序排列
-10^9 <= target <= 10^9

#线性
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        #
        m=len(matrix)
        n=len(matrix[0])
        #o(n+m)
        row=m-1
        col=0
        while row>=0 and col<=n-1:
            if matrix[row][col]==target:return True
            elif matrix[row][col]<target:col+=1
            else:row-=1
        return False

#二分
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        #
        m=len(matrix)
        n=len(matrix[0])
        t=min(m,n)
        #o(logn+logm)*t
        for i in range(t):
            rowfind=self.rowsearch(matrix,target,i)
            colfind=self.colsearch(matrix,target,i)
            #key
            if rowfind or colfind:return True
        return False

    def rowsearch(self,matrix,target,i):
        left=i
        right=len(matrix[0])-1
        while left<=right:
            mid=left+(right-left)//2
            if matrix[i][mid]==target:return True
            elif matrix[i][mid]>target:right=mid-1
            else:left=mid+1
        return False
        

    def colsearch(self,matrix,target,i):
        left=i
        right=len(matrix)-1
        while left<=right:
            mid=left+(right-left)//2
            if matrix[mid][i]==target:return True
            elif matrix[mid][i]>target:right=mid-1
            else:left=mid+1
        return False

lc 69 【剑指 072】:x 的平方根
https://leetcode.cn/problems/sqrtx/
提示:
0 <= x <= 2^31 - 1

class Solution:
    def mySqrt(self, x: int) -> int:
        #o(logk)
        left=0
        right=x
        ans=-1
        while left<=right:
            mid=left+(right-left)//2
            if mid*mid<=x:
                ans=mid    
                left=mid+1 #key
            else:right=mid-1
        return ans

lc 1539 :第 k 个缺失的正整数
https://leetcode.cn/problems/kth-missing-positive-number/
提示:
1 <= arr.length <= 1000
1 <= arr[i] <= 1000
1 <= k <= 1000
对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j]
在这里插入图片描述

#方案一:直接模拟
class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        #
        currnum=1
        lastmissnum=-1
        misscnt=0
        #o(n+k)
        i=0
        while misscnt<k: #key
            if arr[i]==currnum:
                if i<len(arr)-1:i+=1
            else:
                lastmissnum=currnum
                misscnt+=1
            currnum+=1
        return lastmissnum
        
#方案一:二分
'''
#nums[i]前面缺失数字的个数:num[i] - i - 1
#找到最后一个前面缺失数字数小于5的元素(或第一个大于等于5的)
'''
class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        n=len(arr)
        left=0
        right=n-1 #key
        #o(logn)
        if arr[0]-0-1>=k:return k #注:若=k,一定小于arr[0]
        if arr[n-1]-(n-1)-1<k:return arr[n-1]+k-(arr[n-1]-(n-1)-1)#注:若=k,一定小于arr[n-1]
        #
        while left<=right:
            mid=left+(right-left)//2
            if (arr[mid]-mid-1) <k:#注:若=k,一定小于arr[mid]
                if mid==n-1 or arr[mid+1]-(mid+1)-1 >=k:return arr[mid]+k-(arr[mid]-(mid)-1)  #最后一个小于k
                else:left=mid+1
            else:
                right=mid-1           

字节 - 教育部门 - 三面:截木头
在这里插入图片描述

#暴力    
class Solution:
    def cutwood(self, nums: List[int], k: int) -> int:
        #o(1)
        n=len(nums)
        maxnum=max(nums)
        #o(maxnum*n)  
        maxm=0
        for m in range(1,maxnum+1):
            cnt=0
            for num in nums:
                cnt+=num//m
            if cnt>=k:maxm=max(maxm,m) #key
        return maxm            
#二分
class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        n=len(arr)
        left=0
        right=n-1 #key
        #o(logn)
        if arr[0]-0-1>=k:return k #注:若=k,一定小于arr[0]
        if arr[n-1]-(n-1)-1<k:return arr[n-1]+k-(arr[n-1]-(n-1)-1)#注:若=k,一定小于arr[n-1]
        #
        while left<=right:
            mid=left+(right-left)//2
            if (arr[mid]-mid-1) <k:#注:若=k,一定小于arr[mid]
                if mid==n-1 or arr[mid+1]-(mid+1)-1 >=k:return arr[mid]+k-(arr[mid]-(mid)-1)  #最后一个小于k
                else:left=mid+1
            else:
                right=mid-1
        
class Solution:
    def cutwood(self, nums: List[int], k: int) -> int:
        n=len(nums)
        maxnum=max(nums)
        #o(n*log(maxnum))
        left=1
        right=maxnum  
        maxm=0
        while left<right:
            mid=left+(right-left+1)//2
            #
            cnt=0
            for num in nums:
                cnt+=num//mid
            #
            if cnt>=k:left=mid #key
            else:right=mid-1
        return left  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值