数据规模->时间复杂度
<=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