算法简介(来源于科大讯飞)
二分查找(Binary Search)是一种在有序数组中查找特定元素的高效算法,它通过每次比较将搜索范围缩小一半来工作,直到找到目标值或确定目标值不存在为止。下面将详细地探讨二分查找的各个方面:
-
基本概念
- 定义:二分查找是一种在有序数组中查找特定元素的算法。该算法每次比较都将搜索范围缩小一半,直到找到目标元素或确定目标元素不存在。
- 时间复杂度:由于每次比较都将搜索范围缩小一半,二分查找的时间复杂度为O(log n),其中n是数组中的元素数量。
-
历史和应用
- 历史背景:二分查找由John Mauchly于1946年提出,在计算机科学和各种编程领域中得到了广泛应用。
- 适用领域:适用于数据库查询、索引搜索以及任何需要快速查找的场景,尤其在处理大数据时表现出色。
-
实现方法
- 左闭右闭区间:在此方法中,查找区间定义为[left, right]。循环条件为while (left <= right),当目标元素大于中间元素时,将left设为middle + 1;反之,将right设为middle - 1。
- 左闭右开区间:此方法的查找区间为[left, right)。循环条件依然是while (left <= right),但更新left和right的策略略有不同。当目标元素小于中间元素时,将right设为middle - 1,否则将left设为middle + 1。
-
挑战和优化
- 边界处理:二分查找的难点之一在于正确处理边界条件。确保循环终止条件正确是避免常见错误的关键。
- 防止溢出:计算中值时应使用int middle = left + (right - left) / 2,以避免整数加法溢出的问题。
总的来说,二分查找作为一种高效的查找算法,依赖于元素的有序性来显著减少查找次数。理解并正确实现二分查找,不仅能提高编程效率,还能在多种应用场景中发挥重要作用。学习二分查找的关键在于掌握其基本原理和灵活处理各种边界情况,这将大大增强你的编程能力和问题解决技巧。
大白话讲算法
二分法,本质上是一个简单且高效的搜索方法,它通过不断地将搜索范围分成两半来快速定位目标。让我们通过一个猜数字游戏来直观理解这一过程。假设游戏要求你在0到1000之间猜一个数字,每次猜测后,你会得到三种可能的反馈:猜高了、猜低了或正好猜中。
在首次尝试时,大多数人可能会选择中间值,即500。如果提示“猜高了”,那么下一步的搜索范围就缩小为0到500;反之,如果提示“猜低了”,则搜索范围变为500到1000。接下来,再次选择新范围的中间值进行猜测,比如250或750。通过这种持续的对半划分,搜索范围逐渐缩小,直至最终锁定正确答案。
这种方法不仅适用于简单的游戏,还可广泛应用于生活中各种需要快速定位的场景,如查找文件、故障检测等。二分法以其高效和精确的特点,成为解决问题的有力工具。
视频展示流程
在2021年,我曾在B站发布了一部关于二分法的讲解视频。尽管当时的播放量并不理想,但我对内容的讲解还是蛮自信的。因此,这次我决定不采用手绘漫画的形式,而是将内容直接升级为视频,以更直观、生动的方式再次分享这一主题。
二分法的三种写法
三种写法都基于在一个排序好的列表内寻找目标数(列表一定包含目标数)
1、左闭右闭的写法
def dichA(nums, target):
left, right = 0, len(nums) - 1 # 闭区间 [left, right]
while left <= right: # 区间不为空
# 循环不变量:
# nums[left-1] < target
# nums[right+1] >= target
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1 # 范围缩小到 [mid+1, right]
else:
right = mid - 1 # 范围缩小到 [left, mid-1]
return left
2、左闭右开的写法
def dichB(nums, target):
left = 0
right = len(nums) # 左闭右开区间 [left, right)
while left < right: # 区间不为空
# 循环不变量:
# nums[left-1] < target
# nums[right] >= target
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1 # 范围缩小到 [mid+1, right)
else:
right = mid # 范围缩小到 [left, mid)
return left # 返回 left 还是 right 都行,因为循环结束后 left == right
3、左开右开的写法
def dichC(nums, target):
left, right = -1, len(nums) # 开区间 (left, right)
while left + 1 < right: # 区间不为空
mid = (left + right) // 2
# 循环不变量:
# nums[left] < target
# nums[right] >= target
if nums[mid] < target:
left = mid # 范围缩小到 (mid, right)
else:
right = mid # 范围缩小到 (left, mid)
return right
具体实例
1、在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 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 <= 105109 <= nums[i] <= 109nums是一个非递减数组109 <= target <= 109
解题思路
经过深度分析,我们决定采用二分查找算法进行优化。具体操作如下:以目标数为基点,设定起始搜索位置;同时,以目标数加一作为终止搜索位置。这样的设置,旨在通过缩小搜索范围,提高查找效率。
实现代码如下
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def dichB(nums, target):
left, right = 0, len(nums)
while left < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
left = dichB(nums, target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = dichB(nums, target + 1) - 1
return [left, right]