二分查找
模板
def binary_search_model(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
left = mid + 1
else:
right = mid - 1
建议记牢上述模板,下列代码使用的都是上述模板
以下是罗列的一写速记指南
- while循环,left <= right
- 条件判断,左arr[mid],右target
- 先等号后小于号,else放最后
- 左加右减,
left = mid + 1
,right = mid - 1
说明:下述是该模板的一些应用,您在浏览的过程中,若发现某些点您思考起来很困难,您可选择忽略,因为上述模板已足以解决很多问题了。
Q1
从给定一个有序数组中,查找 target:
【Q 1-1】
若 target 在数组中不存在 返回 -1
def binary_search_model(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1 # 1
【Q 1-2 】
若target在数组中不存在,返回它应该插入的下标
-
解法一
def binary_search(arr, target): left, right = 0, len(arr) - 1 ans = len(arr) # 3 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return mid if arr[mid] < target: left = mid + 1 else: ans = mid # 2 right = mid - 1 return ans # 1
解释:
若
arr[mid] > target
满足,right = mid - 1
表示right
尝试往左试探,左边试探的那个值无法保证是否仍然比target大,故使用ans = mid
保存上一个满足条件的点。分析一下while循环结束的情况,此时
left == right == mid
,- 若
arr[mid] > target
,此时的mid就是正确解,虽然您会注意到 right 会继续往左试探,但这无关紧要,因为马上就会跳出while循环。 - 若
arr[mid] < target
,此时会执行left = mid + 1
后跳出while循环。显然当前mid
不是正确值,正确的ans其实是mid + 1
,您需要明白在right
移动的过程中,ans早已将上一个大于target的值保存下来了。
- 若
-
解法二
def binary_search(arr, target):
left, right = 0, len(arr) - 1
ans = 0 # 3
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
mid = mid + 1 # 2
left = mid + 1
else:
right = mid - 1
return ans # 1
此解法与解法一是统一思路,不再赘述。只简要提示一句,当arr[mid] < target
时,当前mid
不满足条件,正确解必在targer
右侧,故ans = mid + 1
。
ans初始化
笔者一般习惯最后给定ans的初始化值,因为这样无需思考,根据代码便可看出初始化值是多少
-
解法一的代码:
if arr[mid] < target: left = mid + 1
一眼看到该
if
条件没有ans记录值,故ans的初始化值就是该条件一直成立的情况,left最后会等于len(arr),故ans = len(arr)
详细解释:
若
target
比数组内所有的元素值都要大,left
会一直右移,right
不会移动。若right
不移动,那么ans
便记录不到正确值,所以ans
需要一个合理的初始值,即ans = len(arr)
,意味着新插入的值比列表内所有值都要大。 -
解法二的代码
else: right = mid - 1
根据上述的经验,假设该条件一直成立,很容易看出
ans
应初始化为 0
Q2
【Q 2】若一个数组中,有重复的元素target,找出target首次出现的下标和最后一次的下标,若该元素未出现返回插入该元素的下标。
【Q 1-2 】是【Q 2】的基础,希望您已对【Q 1-2】有了深刻的理解
首次出现下标
def binary_search_head(arr, target):
left, right = 0, len(arr) - 1
ans = 0 # 5
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
ans = mid # 2 保存上一个找到的target的位置
right = mid - 1 # 3 继续看 mid 左侧是否还有
elif arr[mid] < target:
ans = mid + 1 # 4 万一数组内无target,通过此方式定位到target的插入位置
left = mid + 1
else:
right = mid - 1
return ans # 1
最后一次出现下标
def binary_search_rear(arr, target):
left, right = 0, len(arr) - 1
ans = 0 # 5
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
ans = mid # 2 保存上一个找到的target的位置
left = mid + 1 # 3 继续看 mid 右侧是否还有
elif arr[mid] < target:
ans = mid + 1 # 4 万一数组内无target,通过此方式定位到target的插入位置
left = mid + 1
else:
right = mid - 1
return ans # 1
tips
我们希望您注意下述等号的位置:
-
while left <= right:
-
if arr[mid] < target:
并且希望您明白,加等号与不加等号的区别