提示:100道LeetCode热题-11-1主要是二分查找相关,包括三题:搜索旋转排序数组、寻找旋转排序数组中的最小值、寻找两个正序数组的中位数。由于初学,所以我的代码部分仅供参考。
前言
上次的三道二分查找题较为基础,主要是回顾和简单运用二分查找这一常见方法,这次的稍加了难度,大家一起看看吧~o(* ̄▽ ̄*)ブ
提示:以下是本篇文章正文内容,下面结果代码仅供参考
题目1:搜索旋转排序数组
1.题目要求:
题目如下:
整数数组
nums
按升序排列,数组中的值 互不相同 。在传递给函数之前,
nums
在预先未知的某个下标k
(0 <= k < nums.length
)上进行了 旋转,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如,[0,1,2,4,5,6,7]
在下标3
处经旋转后可能变为[4,5,6,7,0,1,2]
。给你 旋转后 的数组
nums
和一个整数target
,如果nums
中存在这个目标值target
,则返回它的下标,否则返回-1
。你必须设计一个时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [
4,5,6,7,0,1,2]
, target = 0
输出:4示例 2:
输入:nums = [
4,5,6,7,0,1,2]
, target = 3
输出:-1示例 3:
输入:nums = [1], target = 0
输出:-1提示:
1 <= nums.length <= 5000
-
<= nums[i] <=
nums
中的每个值都 独一无二题目数据保证
nums
在预先未知的某个下标上进行了旋转
-
<= target <=
代码框架已经提供如下:
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
2.结果代码:
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 左半部分有序
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# 右半部分有序
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
说明:
本题还算基础,为下一题做准备。由于每次都将搜索范围缩小一半,时间复杂度为 O(log n)
,满足题目要求。
- 初始化指针:
left
和right
分别指向数组的起始和结束位置。 - 二分查找:
- 计算
mid
位置。 - 如果
nums[mid]
等于target
,直接返回mid
。 - 判断左半部分是否有序:
- 如果
nums[left] <= nums[mid]
,说明左半部分有序。- 如果
target
在[nums[left], nums[mid])
范围内,则在左半部分继续查找(调整right
)。 - 否则在右半部分查找(调整
left
)。
- 如果
- 如果右半部分有序:
- 如果
target
在(nums[mid], nums[right]]
范围内,则在右半部分继续查找(调整left
)。 - 否则在左半部分查找(调整
right
)。
- 如果
- 如果
- 计算
- 未找到目标:如果循环结束仍未找到
target
,返回-1
。
题目2:寻找旋转排序数组中的最小值
1.题目要求:
题目如下:
已知一个长度为
n
的数组,预先按照升序排列,经由1
到n
次 旋转 后,得到输入数组。例如,原数组nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组
[a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组[a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。给你一个元素值 互不相同 的数组
nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。提示:
n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums
中的所有整数 互不相同
nums
原来是一个升序排序的数组,并进行了1
至n
次旋转
代码框架已经提供如下:
class Solution(object):
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
2.结果代码:
class Solution(object):
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] < nums[right]:
right = mid
else:
left = mid + 1
return nums[left]
说明:
我们需要在一个旋转后的有序数组中查找最小值。旋转后的数组不再是全局有序的,但可以被分成两个有序的子数组。例如,[4,5,6,7,0,1,2]
是由 [0,1,2,4,5,6,7]
旋转得到的,分为 [4,5,6,7]
和 [0,1,2]
两个有序部分,最小值 0
位于两个子数组的交界处。
由于数组是部分有序的,我们可以利用二分查找的思想,通过比较中间元素与右边界元素的大小关系来判断最小值的位置。具体步骤如下:
- 初始化指针:设置
left
和right
分别指向数组的起始和结束位置。 - 二分查找:
- 计算中间位置
mid
。 - 如果
nums[mid] < nums[right]
,说明最小值在左半部分或就是mid
本身,因此更新right = mid
。 - 否则,最小值在右半部分,更新
left = mid + 1
。
- 计算中间位置
- 终止条件:当
left == right
时,nums[left]
即为最小值。
题目3:寻找两个正序数组的中位数
1.题目要求:
题目如下:
给定两个大小分别为
m
和n
的正序(从小到大)数组nums1
和nums2
。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为
O(log (m+n))
。示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-
<= nums1[i], nums2[i] <=
代码框架已经提供如下:
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
2.结果代码:
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
total_len = m + n
half_len = (total_len + 1) // 2
low, high = 0, m
while low <= high:
i = (low + high) // 2
j = half_len - i
max_left1 = nums1[i-1] if i > 0 else float('-inf')
min_right1 = nums1[i] if i < m else float('inf')
max_left2 = nums2[j-1] if j > 0 else float('-inf')
min_right2 = nums2[j] if j < n else float('inf')
if max_left1 <= min_right2 and max_left2 <= min_right1:
if total_len % 2 == 1:
return max(max_left1, max_left2)
else:
return (max(max_left1, max_left2) + min(min_right1, min_right2)) / 2.0
elif max_left1 > min_right2:
high = i - 1
else:
low = i + 1
return 0.0
问题分析:
我们需要在两个有序数组 nums1
和 nums2
中找到合并后的中位数。中位数的定义是将数组分成两部分,左半部分的所有元素小于等于右半部分的所有元素。如果合并后的数组长度为奇数,中位数是中间的那个数;如果长度为偶数,中位数是中间两个数的平均值。题目要求算法的时间复杂度为 O(log(m+n))
,这意味着我们需要使用二分查找的思想来解决这个问题。
解决思路:
为了满足时间复杂度要求,我们可以利用二分查找的思想,通过每次排除一半的元素来缩小搜索范围。具体步骤如下:
- 确定中位数的位置:根据合并后数组的总长度
total_length = m + n
,如果total_length
是奇数,中位数是第k = (total_length + 1) // 2
小的元素;如果是偶数,中位数是第k1 = total_length // 2
和第k2 = k1 + 1
小的元素的平均值。 - 二分查找第 k 小的元素:
- 比较
nums1
和nums2
中第k//2
小的元素(如果数组长度不足k//2
,则取最后一个元素)。 - 如果
nums1
的第k//2
小的元素小于nums2
的第k//2
小的元素,说明nums1
的前k//2
个元素不可能是第k
小的元素,可以排除这部分元素,并更新k
为k - k//2
。 - 反之,排除
nums2
的前k//2
个元素。 - 重复上述过程,直到
k
为 1 或其中一个数组被完全排除。
- 比较
- 处理边界条件:
- 如果一个数组为空,直接返回另一个数组的第
k
小的元素。 - 如果
k
为 1,返回两个数组当前指针位置的较小值。
- 如果一个数组为空,直接返回另一个数组的第
优化点说明:
减少冗余计算:
- 直接交换
nums1
和nums2
,确保nums1
是较短的数组,减少二分查找的范围。 - 使用
half_len
直接计算中位数的位置,避免重复计算。
- 直接交换
提前终止:
- 在二分查找中,一旦找到满足条件的分割点,立即返回结果,减少不必要的迭代。
边界处理优化:
- 使用
float('-inf')
和float('inf')
处理边界情况,避免复杂的条件判断。
- 使用
总结
针对二分查找的三种题型进行了学习,了解了部分有关二分查找与python的相关知识,大家加油!