基于这几天刷的题中遇到的比较经典的二分查找题进行汇总,目前总结出了两类二分查找的题型以方便以后回顾,后续碰到的会继续更新:
1. Leetcode 704 & 35:整数数组nums有序、元素无重复,target不一定存在于数组中;
2. Leetcode 34:整数数组nums有序、元素有重复,target不一定存在于数组中。
第一类(元素无重复)
Leetcode 704. 二分查找
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示:
- 你可以假设
nums
中的所有元素是不重复的。 n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
Leetcode 35. 搜索插入位置
题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 0
输出: 0
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为无重复元素的升序排列数组
-104 <= target <= 104
思路
这两题都是二分查找经典思路,差别只是当target不存在于数组中时,一个直接返回-1,另一个则需要返回左指针,所以不多说直接上代码。
Python3代码
-
时间复杂度:。其中是数组的长度。
-
空间复杂度:。
"""
Leetcode 704. 二分查找
"""
class Solution:
def search(self, nums: List[int], target: int) -> int:
l = 0
r = len(nums) - 1
while l <= r:
middle = l + (r - l) // 2
if target < nums[middle]:
r = middle - 1
elif target > nums[middle]:
l = middle + 1
else:
return middle
return -1
-
时间复杂度:。其中是数组的长度。
-
空间复杂度:。
"""
Leetcode 35. 搜索插入位置
"""
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l = 0
r = len(nums) - 1
while l <= r:
middle = l + (r - l) // 2
if target == nums[middle]:
return middle
elif target < nums[middle]:
r = middle - 1
else:
l = middle + 1
return l
第二类(元素有重复)
Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
题目描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
思路
其实主要是想记录一下这道题... 因为数组中的元素有重复,就意味着二分查找后返回的元素下标是不唯一的。但因为数组是有序的,所以即便下标不唯一,也能保证下标是连续的,即存在区间,所以本题的实际上是寻找在中的左右边界问题。
参考实例,本题共有3种情况:
1. 在的范围中,且中存在,则返回具体区间;
2. 在的范围中,但中不存在 ,则返回[-1, -1];
3. 不在的范围中,返回[-1, -1]。
本题需要用两次二分查找,一次查找左边界,一次查找右边界。
其中查找边界的精髓是:在经典的二分查找中,当 nums[middle] == target 时,直接返回 middle,而在本题中,查找左边界时,当 nums[middle] == target,需要继续向左查找,看 nums[middle] 左边是否还有与 target 相等的元素。所以此时,应该继续 right = middle - 1,而不是返回 middle。
同理,在查找右边界时,当 nums[middle] == target 时,应该继续向右查找,确认右边是否还有与 target 相等的元素,所以需要 left = middle + 1。
Python3代码
- 时间复杂度: 。其中为数组的长度。
- 空间复杂度:。只需要常数空间存放若干变量。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums or nums[0] > target or nums[-1] < target: # 第三种情况
return [-1, -1]
lborder = rborder = -2
# 更新左边界
l, r = 0, len(nums) - 1
while l <= r:
middle = l + (r - l) // 2
if nums[middle] >= target: # nums[middle] == target时,一直往左找,看是否还有等于target的元素
r = middle - 1
else:
l = middle + 1
if nums[l] == target:
lborder = l
else: # if lborder != target,即第二种情况
lborder = -1
# 更新右边界
l, r = 0, len(nums) - 1
while l <= r:
middle = l + (r - l) // 2
if nums[middle] > target:
r = middle - 1
else: # nums[middle] == target时,一直往右找,看是否还有等于target的元素
l = middle + 1
if nums[r] == target:
rborder = r
else:
rborder = -1
return [lborder, rborder]