二分查找
第一题 纯正二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/binary-search
python 知识复习
# 关于范围复制 for i in range(100) 是指0到99,不包括最后一个100 # 关于创建长度固定的列表 nums = [0] * 100 nums = [None] * 100 nums = [0 for i in range(100)] nums = [[0 for i in rnage(100)] for j in range(100)]
有关边界的思考
这一题我认为最关键的问题是边界问题,大体的算法逻辑是很简单的:
- 设定两个潜在范围边界,初始为最大边界(len(nums)-1)和最小边界0
- 通过不断比较(循环)中间值(二分)和目标值(target)的大小,改变边界,收缩范围
- 可能的结果:①通过不断收缩找到目标值 nums[mid]②边界收紧,未找到目标值
这其中比较讲究的点有
- 中间值的取法:存在范围中有偶数个个体和奇数个个体的不同情况,奇数个个体的情况下不存在问题,因为有实际的中间量。问题是偶数个个体下,是不存在具体的中间值的,势必要取前一个或者后一个。而因为存在target在初始边界的情况,这两种情况容易导致无法在循环内找到目标值,因此需要跳出循环单独做善后工作
# 取前一个
mid = (min + max) // 2
# 取后一个
mid = (mid + max) // 2 + 1
- 因此,需要【mid取法】【终止判定】【善后工作】三者互相配合来处理所有情况
构想解法
我们以如下配合为例
- mid取法:mid = (min + max) // 2
- 由于以上情况在会最终收缩在min = mid < max的情况,这时候目标值要么在min 和 max中间(min < mid = max同理),要么不存在,所以做一下善后工作:
while 1
# 正常二分循环 mid = (min + max) // 2
# 善后
if mid == min: # 范围已经收缩到最小
if nums[min] == target:
return min
elif nums[max] == target:
return max
else:
return -1
最终解法
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
min = 0
max = len(nums) -1
mid = max // 2
while 1:
if nums[mid]>target:
max = mid
elif nums[mid]<target:
min = mid
else:
return mid
mid=(min+max)//2
if mid == min: # 范围已经收缩到最小
if nums[min] == target:
return min
elif nums[max] == target:
return max
else:
return -1
时间复杂度O(logn)
执行用时:20 ms, 在所有 Python 提交中击败了91.16%的用户
内存消耗:13.9 MB, 在所有 Python 提交中击败了61.90%的用户
第二题 二分的应用 平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/sqrtx
构想解法
最开始简单最直接的思路当然是暴力遍历,这种O(n)的算法自然在时间表现上很不满意
sta = 0
while (sta * sta) <= x:
sta = sta + 1
return sta - 1
执行用时: 964 ms
内存消耗: 13 MB
后来想了一些小方法,也是指标不治本
n = (len(str(x)) +1)//2
sta = 1
for i in range(n-1):
sta =sta *10
sta = sta -1
while sta * sta <=x:
sta=sta+1
return sta-1
执行用时: 824 ms
内存消耗: 13.1 MB
但是既然
①有遍历的需求
②而且被遍历的目标是天然递增的
那么二分也是很自然的想法
最终解法
low = 0
high = x
mid = (low + high)//2
while mid > low:
if mid*mid > x:
high = mid
elif mid*mid < x:
low = mid
else:
return mid
mid = (low + high) // 2
return high if high*high == x else low
执行用时: 16 ms
内存消耗: 13.1 MB
总结,凡是在有序数组中寻找一个数的问题,都可以用二分查找来将复杂度从n降低到logn