目录
leetcode-二分查找
原理
二分查找也叫折半查找。
基本思想:
1 在有序表中,取中间记录作为比较对象
1.1 若给定与中间记录的关键字相等,则查找成功;
1.2 若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;
1.3 若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。
2 不断重复1过程,直到查找成功,或在查找区域无记录,查找失败为止。
特点
-
数据结构需要排好序(如单调递增,单调递减)
-
数据结构可随机访问(如数组)
-
根据中间元素可推测两侧元素性质(可缩小问题规模)
-
元素多数为整型
代码实现
基于原理场景-在有序数组中查找是否存在值为key的元素。实现代码如下。
def binary_search(nums: list, key: int) -> bool:
left = 0
right = len(nums) - 1
# <= 而不是 < ,当nums只有一个元素的时候不会进入
while left <= right:
mid = (left + right) >> 1
value = nums[mid]
if value == key:
return True
if value < key:
left = mid + 1
else:
right = mid - 1
return False
代码模板
def binary_search(nums: list, key: int) -> bool:
left = xxx1
right = xxx2
while left <= right:
mid = (left + right) >> 1
value = nums[mid]
if yyy1:
return True
if yyy2:# value < key
left = mid + 1
else:
right = mid - 1
return False
确定查找边界xxx1/xxx2
确定查找的终止条件yyy1/yyy2
典型例题分解
410
https://leetcode-cn.com/problems/split-array-largest-sum/
难点:如何将解题思路转化到使用二分查找。
解题思路(二分)
此题转化为二分的思路如下:
在[max(nums),sum(nums)]中找到一个值value,满足将nums切分成m个非空连续子数组,每个连续子数组的和小于等于value。
关键点:
-
找区间内找值,二分查找
-
数组切分,统计每个子数组值并判断小于某值。
代码实现
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
# 判断查找值是否满足
def f(x: int) -> bool:
total = 0
nums_spilt = 1
for num in nums:
if total + num <= x:
total += num
else:
nums_spilt += 1
total = num
if nums_spilt > m:
return False
return True
# 二分查找
left = max(nums)
right = sum(nums)
while left <= right:
mid = (left + right) // 2
if not f(mid):
left = mid + 1
else:
right = mid - 1
return left
33
https://leetcode-cn.com/problems/search-in-rotated-sorted-array/
题意
有一个旋转后的数组nums,问nums是否存在值为target的元素。
nums数组:
[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
解题思路(二分)
查找nums是否存在target元素(二分查找变体)
本体主要难点是在,怎么在一个经过旋转之后的数组中进行二分查找。
按照二分查找原理,当mid索引处元素值与target值不相等时,我们应该向左缩小搜索范围还是向右缩小搜索范围呢?
引用官方解答:
判断以mid作为分割符左右两子数组,哪边有序。判断target是否在有序数组范围内,在则指针往有序数组移,否则往相反方向移动。
代码实现
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) >> 1
value = nums[mid]
if value == target:
# return check(mid)
return mid
if nums[0] <= value:
if nums[0] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[len(nums)-1]:
left = mid + 1
else:
right = mid - 1
return -1
if __name__ == '__main__':
print(Solution().search([4, 5, 6, 7, 0, 1, 2], 0))
print(Solution().search([1, 3], 3))
print(Solution().search([1, 3, 5], 3))
print(Solution().search([1, 3, 5], 1))
print(Solution().search([3, 1], 1))
1552
与410题类似。
区别在于
540
https://leetcode-cn.com/problems/single-element-in-a-sorted-array/
解题思路(二分)
二分查找典型题。
问题为,出现一次的元素在左半区间还是右半区间。
唯有一个数只会出现一次 -- 数组元素为奇数,该与左右两边数不相等。
如果mid处元素与左右两边不相等,则返回该元素值。
如果mid处元素与左右两边元素一者相等,则存在以下四种情况
-
mid处元素与左边元素相等
-
mid处右边数组元素个数为奇数,则指针右移1位
-
mid处右边数组元素个数为偶数,则指针左移2位
-
-
mid处元素与右边元素相等
-
mid处右边数组元素个数为奇数,则指针左移1位
-
mid处右边数组元素个数为偶数,则指针右移2位
-
代码实现
from typing import List
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) >> 1
is_odd_right = (right - mid) % 2 == 1
if nums[mid] == nums[mid - 1]:
if is_odd_right:
left = mid + 1
else:
right = mid - 2
elif nums[mid] == nums[mid + 1]:
if is_odd_right:
right = mid - 1
else:
left = mid + 2
else:
return nums[mid]
if __name__ == '__main__':
print(Solution().singleNonDuplicate([1, 1, 2, 3, 3, 4, 4, 8, 8]))
print(Solution().singleNonDuplicate([3, 3, 7, 7, 10, 11, 11]))
总结
二分查找算法不难,难的是如何将题目的解题思路牵引至二分查找上。
基于以下,可考虑是否可以使用二分查找
-
关键字:排序、搜索
-
模式识别:有序或部分有序,基本使用二分搜索及其变种
-
算法描述:“丢弃”一半的数据