文章目录
总结二分法的题目与解法
适用题型
:将数组分成两部分,要求解的答案一定在其中的一半里。左右两侧只要确定可以甩掉一边,就可以二分。
注
:二分法不一定要满足有序才能使用。只不过无序数组的写法复杂一点
二分法的时间复杂度
:O(logn)【注:O读作big O
】【底数为2】
题型1:
在一个有序数组中,找某一个数是否存在
方法:二分法
,找到就停。
.
题型2:在一个有序
数组中,找>=指定数的最左侧的位置
方法:二分法
,二分到结束才停。
.
题型3:局部最小值
。数组中array无序,任何两个相邻的数不相等。局部最小:若n-1位置数比N-2位置的数小,则N-1位置就是局部最小。要求时间复杂度小于O(N)
方法:二分到底,一定可以找到一个局部最小值的。
优化流程的方向:
1、数据状况
2、问题标准
35.简单
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
.
请必须使用时间复杂度为 O(log n) 的算法。
解:所用语言为python3
引用:轻松理解 left + ((right -left) >> 1)
问题 :为什么不直接用
(L+R) // 2
而用L + ((R-L)) >> 1)
答: 是因为
①L+R
在某种情况下可能会超过基本类型所能容纳的最大值而溢出
,因此将(L+R)//2
改写为:L+(R-L)//2
②又>>
(位运算) 比/
运算要快,>>1
等价于//2
所以:
L+(R-L)//2
=L+((R-L)>>1)
例:
left = 0, right = 3
right-left = 3 = 011(二进制)
(right-left) >> 1 = 001
注:>>
运算为整体向右移动,移出的作废,右侧补零
((right-left) >> 1)+ left = 1+0 = 1
作用等价于:(left + right)// 2 = 1
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-1
ans = len(nums)
while left <= right:
mid = ((right - left) >> 1) + left # 等价于`mid = (left + right) // 2`
# 判断语句的写法为:要判断的目标值<=给定的数组中间值
# 因为当target不在数组中时,返回的位置索引是第一个>=它的值所在的位置所引
if (target <= nums[mid]): # 目的是找出第一个>=target的值
ans = mid
right = mid - 1
else:
left = mid + 1 # 如果target>nums[mid],说明要右移
return ans
69.简单
x的平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
.
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
.
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
解:所用语言为python3
class Solution:
def mySqrt(self, x: int) -> int:
left = 0 # 初始化左边界
right = x # 初始化右边界
ans = -1 # 用于记录中间值的变量
while left <= right: # 当左边界 <= 右边界
mid = ((right - left) >> 1) + left # 中间值 = (左边界 + 右边界)// 2
if (mid*mid <= x): # 判断条件,根据题意得来:如果mid² <= x
ans = mid # 记录此时的中间值,作为下一次二分法的端点
left = mid + 1 # 右移:左边界 = mid + 1
else:
right = mid - 1 # 否则左移:右边界 = mid - 1
return ans # 返回中间值
367. 简单
有效的完全平方数
给定一个 正整数
num
,编写一个函数,如果num
是一个完全平方数,则返回true
,否则返回false
。
进阶:不要 使用任何内置的库函数,如sqrt
。
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left = 0
right = num
while left <= right:
mid = (left + right) // 2
if num >= (mid * mid):
if num == mid * mid:
return True
left = mid + 1
else:
right = mid - 1
return False
704. 简单
二分查找
给定一个
n
个元素有序的(升序)
整型数组nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
。
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
ans = -1
while left <= right:
mid = (left + right) // 2 # 可以写为:((right - left)>>1) + left
if target >= nums[mid]:
ans = mid
left = mid + 1
if nums[ans]==target:
return ans
else:
r = mid - 1
return -1
33. 中等
搜索旋转排序数组
数组
nums
按升序排列,数组中的值 互不相同 。
.
在传递给函数之前,nums
在预先未知的某个下标k(0 <= k < nums.length)
上进行了 旋转,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如,[0,1,2,4,5,6,7]
在下标3
处经旋转后可能变为[4,5,6,7,0,1,2]
。
.
给你 旋转后 的数组nums
和一个整数target
,如果nums
中存在这个目标值target
,则返回它的下标,否则返回-1
。
题目分析:题目中的旋转
旨在告诉我们,这个数组不是有序的,准确来讲是部分有序。
因此,这道题目比传统的有序数组处理复杂一点。
解1(不使用二分法):
所用语言为python3
从左往右遍历,时间复杂度是O(N)
class Solution:
def search(self, nums: List[int], target: int) -> int:
if target in nums:
return nums.index(target)
else:
return ans
解2(使用二分法):
所用语言为python3
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums: # 如果不存在【if target not in nums】
return -1 # 直接返回-1
left, right = 0, len(nums) - 1 #常规初始化
while left <= right: # 当左边界<=右边界时
mid = ((right - left) >> 1) + left # 中间值
if nums[mid] == target: # 直到找出中间值==目标值时
return mid # 返回中间值的索引
if nums[0] <= nums[mid]: # 如果第一个元素<中间元素,说明[0,mid]这一半是有序的
if nums[0] <= target < nums[mid]: # 如果目标值在其中
right = mid - 1 # 左移:右边界 - 1
else: # 否则
left = mid + 1 # 右移:左边界 + 1
else: # [mid,-1]是有序的
if nums[mid] < target <= nums[len(nums) - 1]: # 如果目标值在其中
left = mid + 1 # 右移:左边界 + 1
else: # 否则:
right = mid - 1 # 左移:右边界 - 1
return -1 # 如果当左边界>右边界时,依旧没有找到目标值,则返回-1
34. 中等
在排序数组中查找元素的第一个和最后一个位置
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 查找左边界(刚好小于target的位置)
left = 0
right = len(nums) - 1
leftIndex = -2
while left <= right:
mid = ((right - left) >>1) + left
if (nums[mid] >= target):
right = mid - 1
leftIndex = right
else:
left = mid + 1
# 查找右边界(等于target的最后一个位置,即`大于target的第一个位置`-1)
left = 0
right = len(nums) - 1
rightIndex = -2
while left <= right:
mid = ((right - left) >>1) + left
if (nums[mid] > target):
right = mid - 1
else:
left = mid + 1
rightIndex = left
# print(f"leftIndex = {leftIndex}")
# print(f"rightIndex = {rightIndex}")
if (rightIndex == -2 or leftIndex == -2): # [2,2] 3, return [-1, -1], rather than [-1, 1]
return [-1, -1]
elif (rightIndex - leftIndex)>1:
return [leftIndex+1, rightIndex-1]
return [-1, -1]