[二分查找] 在排序数组中查找元素的第一个和最后一个位置
Posted 热爱生活的小熊猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[二分查找] 在排序数组中查找元素的第一个和最后一个位置相关的知识,希望对你有一定的参考价值。
二分查找系列专题 LeetCode34
1 题目描述
给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]
。
示例:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
2 题解
这道题算是二分查找里面的经典题目了,如果考虑使用二分查找大致有三种思路:
2.1 一次二分
这个方法比较naive
,思路如下:
-
对nums做一次二分查找,如果nums中不存在target则返回[-1, -1]; -
如果nums中存在target,其位置为i,分别从i的左边和右边线性查询找到左右边界。
这个方法在最好的情况下时间复杂度为 ,在最差的情况下时间复杂度将退化成 ,不推荐。
2.1 两次二分
相比于一次二分,这个方法的时间复杂度更稳定,思路也很清晰:
-
改进二分查找,在[0, nums.size() - 1]搜索左边界;如果发现target不在nums中,返回[-1, -1]; -
改进二分查找,在[left, nums.size() - 1]搜索右边界,返回结果
当然,这个方法把二分的思想运用到了极致,里面有无数意想不到的边界情况。在一番面向AC编程之后,我的代码AC了(可能仍然有测试样例没考虑到的bug):
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ret(2, -1);
if (nums.size() == 0) return ret;
// 在[0, nums.size() - 1]中寻找左边界
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) right = mid;
else left = mid + 1;
}
// 边界情况:如果在nums中没有target直接返回[-1,-1]
if (left == nums.size() || nums[left] != target) return ret;
ret[0] = left;
// 在[left, nums.size() - 1]中寻找右边界
right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) left = mid + 1;
else right = mid - 1;
}
// 考虑另一种边界情况:nums最后一位也是target
ret[1] = nums[left] == target ? left : left - 1;
return ret;
}
};
2.3 多次二分
这个方法可以算作在方法1的基础上进行优化的版本,其整体思路和时间复杂度与方法2类似,思路为:
-
首先用经典的二分查找查询target是否在nums中,如果找不到target直接返回[-1, -1]; -
假设二分找到的target下标为i,在[0, i - 1]中进行二分查找,找到左边界; -
在[i + 1, nums.size() - 1]中进行二分查找,找到右边界;返回结果。
感谢鸭鸭
提供的代码:
// 一次二分找到target的位置
int findTargetIndex(vector<int> arr, int target, int left, int right)
{
if (arr.size() == 0)
return -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
vector<int> searchRange2(vector<int> &nums, int target)
{
vector<int> res(2, -1);
if (nums.size() == 0) return res;
int index = findTargetIndex(nums, target, 0, nums.size() - 1);
// 如果nums中不存在target,直接返回[-1,-1]
if (index == -1) return res;
int left = index - 1, right = index + 1;
// 二分查找左边界
while (left >= 0 && nums[left] == target)
{
left = findTargetIndex(nums, target, 0, left);
left = left - 1;
}
// 二分查找右边界
while (right < nums.size() && nums[right] == target)
{
right = findTargetIndex(nums, target, right, nums.size() - 1);
right = right + 1;
}
res[0] = min(index, left + 1);
res[1] = max(index, right - 1);
return res;
}
二分查找系列往期题目
以上是关于[二分查找] 在排序数组中查找元素的第一个和最后一个位置的主要内容,如果未能解决你的问题,请参考以下文章
34. 在排序数组中查找元素的第一个和最后一个位置-二分查找双指针
34. 在排序数组中查找元素的第一个和最后一个位置-二分查找双指针
二分查找--34. 在排序数组中查找元素的第一个和最后一个位置