二分查找是一个简单但是热门的方法,二分的模板也非常多,这里提供一个简单好记、且不会索引越界的写法,并在此基础上改写得到一些变体,如查找最后一个目标元素,查找第一个大于(或者小于)目标的数。
包含的题目链接:
704. 二分查找:https://leetcode.cn/problems/binary-search/
705. 搜索插入位置:https://leetcode.cn/problems/search-insert-position/
34. 在排序数组中查找元素的第一个和最后一个位置:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
题目描述
704. 二分查找。给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
2.非递归二分
二分模板,理解+记忆即可。
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
left, right = 0, len(nums) - 1
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
3.递归二分
如果非要写个递归版本的,那也只能写一个。
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
def binary_search(nums, begin, end, target):
if begin > end:
return -1
middle = begin + ((end - begin)>>1)
if nums[middle] > target:
return binary_search(nums, begin, middle - 1, target)
if nums[middle] < target:
return binary_search(nums, middle + 1, end, target)
return middle
return binary_search(nums, 0, len(nums) - 1, target)
4.变体1:搜索插入位置
寻找第一个大于target的数,或者是最后一个target,可见35. 搜索插入位置。
在普通的二分上稍作修改即可,当nums[middle] == target时,我们仍然认为没有找到,当作nums[middle] < target处理,这样left指针就会进一步向前,最后一个target一定在left - 1位置(前提是没有越界);如果找第一个大于target的位置,那就是left本身。
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
if not nums:
return -1
left, right = 0, len(nums) - 1
while left <= right:
middle = left + ((right - left)>>1)
if nums[middle] <= target: # 等于也视作小于
left = middle + 1
else:
right = middle - 1
# target可能在left - 1位置
if left - 1 >= 0 and nums[left - 1] == target:
return left - 1
return left # 第一个大于target位置
4.变体2:在排序数组中查找元素的第一个和最后一个位置
链接:在排序数组中查找元素的第一个和最后一个位置
其实和变体1没什么区别,只不过现在还要找第一个target或者第一个小于target的位置。和上面一样,nums[middle] == target也认为nums[middle]>target,另right = middle - 1, 在不越界的前提下,第一个target在right + 1位置,第一个小于target在right位置。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums:
return [-1, -1]
first = self.binary_search(nums, target, pos = 'first')
last = self.binary_search(nums, target, pos = 'last')
return [first, last]
def binary_search(self, nums, target, pos='first'):
begin, end = 0, len(nums) - 1
while begin <= end:
middle = begin + (end - begin) // 2
if pos == 'last':
if nums[middle] <= target:
begin = middle + 1
else:
end = middle - 1
if pos == 'first':
if nums[middle] >= target:
end = middle - 1
else:
begin = middle + 1
if pos == 'last':
if begin - 1 >= 0 and nums[begin - 1] == target:
return begin - 1
return -1
if pos == 'first':
if end + 1 < len(nums) and nums[end + 1] == target:
return end + 1
return -1