[二分查找] 在排序数组中查找元素的第一个和最后一个位置

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. 在排序数组中查找元素的第一个和最后一个位置

[LeetCode]34. 在排序数组中查找元素的第一个和最后一个位置(二分)

leetcode(34)---在排序数组中查找元素的第一个和最后一个位置(二分查找)