数组-二分类
数组
二分法查找
前提
- 数组为有序数组;
- 数组中没有重复元素。
优点
逻辑简单
难点
涉及很多边界条件,对区间定义不清楚,二分法则容易写乱
解决方法:
原则: 循环不变量规则
二分查找中,保持区间不变量,在循环寻找中每一次边界的处理都要坚持区间的定义来操作,
方式:
-
左闭右闭
-
左闭右开
左闭右闭
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution:
def search(self, nums: List[int], target: int)->int:
"""
左闭右闭
"""
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return -1
左闭右开
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
有如下两点:
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
class Solution:
def search(self, nums: List[int], target: int)->int:
"""
左闭右开
"""
if not nums:
return -1
l = 0
r = len(nums) - 1
while l <= r:
m = round(l+(r-l)/2)
if nums[m] == target:
return m
elif nums[m] > target:
r=m-1
else:
l=m+1
return -1
分治 (分而治之)
二分查找也是分治思想中的一种
分治: “分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的子问题,子问题继续按照这样划分,直到问题可以被轻易解决;
“治”指的是将子问题单独进行处理。经过分治后的子问题,需要将解进行合并才能得到原问题的解,因此整个分治过程经常用递归来实现。
eg: 在二维数组中查找目标值
二维数组每一行单调递增,每一列单调递增,实现时间复杂度为O(n+m)的算法;
class Solution:
def Find(self , target: int, array: List[List[int]]) -> bool:
n = len(array)
if n <= 0 or n > 500:
return False
m = len(array[0])
if m <=0 or m > 500:
return False
i = n - 1
j = 0
while i >= 0 and j < m:
# 大于目标值往上走
if array[i][j] > target:
i -= 1
# 小于,目标值往右走
elif array[i][j] < target:
j += 1
else:
return True
return False