主题
视频讲解
请看【基础算法精讲 04】。制作不易,欢迎点赞~
答疑
问:如何理解 end = lowerBound(nums, target + 1) - 1 这段代码?
答:要想找到 lowerBound(nums, target + 1)。然后将其减一,就得到
问:如果
答:这说明数组中的数都 lowerBound(nums, target + 1) 在这种情况下会返回
问:为什么要写 left + (right - left) / 2?
答:在面试或者实际场景中,你不一定知道输入的数组有多长,万一数组长度达到 left + right 可能会发生加法溢出。当然,如果只看本题的数据范围,写 (left + right) / 2 也可以。对于 Python 来说,由于没有溢出这个概念,所以可以直接相加。
问:怎么判断我写的是哪一种二分?
答:看 left <= right,就是闭区间;如果是 left < right,就是半闭半开区间;如果是 left + 1 < right,就是开区间。
问:关于闭区间写法,为什么 nums[mid] >= target 成立的时候要写 right = mid - 1?此时的
答:「二分范围」和「答案所在范围」是两个概念。设现在二分的范围是闭区间
问:我看到一种二分的写法,在二分的过程中,额外记录答案的值。如何评价这种写法?
答:喜欢这种写法的同学,推荐写开区间二分。开区间二分
问:关于开区间二分,如何理解
答:可以假设
闭区间写法
python
class Solution:
# lower_bound 返回最小的满足 nums[i] >= target 的下标 i
# 如果数组为空,或者所有数都 < target,则返回 len(nums)
# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
def lower_bound(self, nums: List[int], target: int) -> int:
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:
right = mid - 1 # 范围缩小到 [left, mid-1]
else:
left = mid + 1 # 范围缩小到 [mid+1, right]
# 循环结束后 left = right+1
# 此时 nums[left-1] < target 而 nums[left] = nums[right+1] >= target
# 所以 left 就是第一个 >= target 的元素下标
return left
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = self.lower_bound(nums, target)
if start == len(nums) or nums[start] != target:
return [-1, -1] # nums 中没有 target
# 如果 start 存在,那么 end 必定存在
end = self.lower_bound(nums, target + 1) - 1
return [start, end]cpp
// C++ 版待补充cpp
class Solution {
// lower_bound 返回最小的满足 nums[i] >= target 的下标 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int lower_bound(vector<int>& nums, int target) {
int left = 0, right = (int) nums.size() - 1; // 闭区间 [left, right]
while (left <= right) { // 区间不为空
// 循环不变量:
// nums[left-1] < target
// nums[right+1] >= target
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1; // 范围缩小到 [left, mid-1]
} else {
left = mid + 1; // 范围缩小到 [mid+1, right]
}
}
// 循环结束后 left = right+1
// 此时 nums[left-1] < target 而 nums[left] = nums[right+1] >= target
// 所以 left 就是第一个 >= target 的元素下标
return left;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = lower_bound(nums, target);
if (start == nums.size() || nums[start] != target) {
return {-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lower_bound(nums, target + 1) - 1;
return {start, end};
}
};左闭右开区间写法
python
class Solution:
# lower_bound 返回最小的满足 nums[i] >= target 的下标 i
# 如果数组为空,或者所有数都 < target,则返回 len(nums)
# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
def lower_bound(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) # 左闭右开区间 [left, right)
while left < right: # 区间不为空
# 循环不变量:
# nums[left-1] < target
# nums[right] >= target
mid = (left + right) // 2
if nums[mid] >= target:
right = mid # 范围缩小到 [left, mid)
else:
left = mid + 1 # 范围缩小到 [mid+1, right)
# 循环结束后 left = right
# 此时 nums[left-1] < target 而 nums[left] = nums[right] >= target
# 所以 left 就是第一个 >= target 的元素下标
return left
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = self.lower_bound(nums, target) # 选择其中一种写法即可
if start == len(nums) or nums[start] != target:
return [-1, -1] # nums 中没有 target
# 如果 start 存在,那么 end 必定存在
end = self.lower_bound(nums, target + 1) - 1
return [start, end]cpp
// C++ 版待补充cpp
class Solution {
// lower_bound 返回最小的满足 nums[i] >= target 的下标 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int lower_bound(vector<int>& nums, int target) {
int left = 0, right = nums.size(); // 左闭右开区间 [left, right)
while (left < right) { // 区间不为空
// 循环不变量:
// nums[left-1] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid; // 范围缩小到 [left, mid)
} else {
left = mid + 1; // 范围缩小到 [mid+1, right)
}
}
// 循环结束后 left = right
// 此时 nums[left-1] < target 而 nums[left] = nums[right] >= target
// 所以 left 就是第一个 >= target 的元素下标
return left;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = lower_bound(nums, target);
if (start == nums.size() || nums[start] != target) {
return {-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lower_bound(nums, target + 1) - 1;
return {start, end};
}
};开区间写法
python
class Solution:
# lower_bound 返回最小的满足 nums[i] >= target 的下标 i
# 如果数组为空,或者所有数都 < target,则返回 len(nums)
# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
def lower_bound(self, nums: List[int], target: int) -> int:
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:
right = mid # 范围缩小到 (left, mid)
else:
left = mid # 范围缩小到 (mid, right)
# 循环结束后 left+1 = right
# 此时 nums[left] < target 而 nums[right] >= target
# 所以 right 就是第一个 >= target 的元素下标
return right
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = self.lower_bound(nums, target) # 选择其中一种写法即可
if start == len(nums) or nums[start] != target:
return [-1, -1] # nums 中没有 target
# 如果 start 存在,那么 end 必定存在
end = self.lower_bound(nums, target + 1) - 1
return [start, end]cpp
// C++ 版待补充cpp
class Solution {
// lower_bound 返回最小的满足 nums[i] >= target 的下标 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int lower_bound(vector<int>& nums, int target) {
int left = -1, right = nums.size(); // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid; // 范围缩小到 (left, mid)
} else {
left = mid; // 范围缩小到 (mid, right)
}
}
// 循环结束后 left+1 = right
// 此时 nums[left] < target 而 nums[right] >= target
// 所以 right 就是第一个 >= target 的元素下标
return right;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = lower_bound(nums, target);
if (start == nums.size() || nums[start] != target) {
return {-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lower_bound(nums, target + 1) - 1;
return {start, end};
}
};cpp
class Solution {
// lower_bound 返回最小的满足 nums[i] >= target 的下标 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int lower_bound(vector<int>& nums, int target) {
int left = -1, right = nums.size(); // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
(nums[mid] >= target ? right : left) = mid; // 注:只有开区间二分可以这样写
}
// 循环结束后 left+1 = right
// 此时 nums[left] < target 而 nums[right] >= target
// 所以 right 就是第一个 >= target 的元素下标
return right;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = lower_bound(nums, target);
if (start == nums.size() || nums[start] != target) {
return {-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lower_bound(nums, target + 1) - 1;
return {start, end};
}
};附:库函数写法
注:Java 的
Arrays.binarySearch,在数组包含多个的情况下,返回的不一定是第一个 的元素下标,所以无法使用。
python
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = bisect_left(nums, target)
if start == len(nums) or nums[start] != target:
return [-1, -1]
end = bisect_right(nums, target) - 1
return [start, end]cpp
// C++ 版待补充cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 也可以用 equal_range,见【C++ 写法二】
int start = ranges::lower_bound(nums, target) - nums.begin();
if (start == nums.size() || nums[start] != target) {
return {-1, -1};
}
int end = ranges::upper_bound(nums, target) - nums.begin() - 1;
return {start, end};
}
};cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
auto [start, end] = ranges::equal_range(nums, target);
if (start == end) {
return {-1, -1};
}
return {(int) (start - nums.begin()), (int) (end - nums.begin() - 1)};
}
};复杂度分析
- 时间复杂度:
,其中 是 的长度。 - 空间复杂度:
。
二分查找常用转化表
| 需求 | 写法 | 如果不存在 |
|---|---|---|
| 结果为 | ||
| 结果为 | ||
| 结果为 | ||
| 结果为 |
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府