找出目标值在数组中的开始和结束位置(二分查找)

给你一个按照非递减顺序排列的整数数组 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 <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

方法一:

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> result = {-1, -1};
        int left = 0;
        int right = nums.size() - 1;

        while (left <= right) {  //二分查找算法的核心部分
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        if (left < nums.size() && nums[left] == target) {
            result[0] = left;  //找到的话就把起始值记录为left
        } else {
            return result;    //到了数组结尾还没找到那就直接返回-1,-1
        }

        right = nums.size() - 1;    //重置 right 为数组的最后一个索引。
        while (left <= right) {    //第二次二分查找
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {    //这里条件不一样需要注意
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        result[1] = right;    //更新终止值
        return result;
    }
};

这道题可以使用二分查找的原因主要在于题目中的数组是非递减顺序排列的整数数组

vector<int> result = {-1, -1};

初始化一个整数向量 result,其初始值为 {-1, -1}

int mid = left + (right - left) / 2;

计算当前查找范围的中间索引 mid。这里采用的计算方式是为了避免可能的整数溢出。

if (nums[mid] < target)

如果 nums[mid] 小于 target,说明目标值位于 mid 的右侧,因此将 left 移动到 mid + 1,缩小查找范围。

如果 nums[mid] 大于或等于 target,则目标值位于 mid 的左侧(包括 mid 本身),所以将 right 移动到 mid - 1,缩小查找范围。

left < nums.size()

  • 这个条件确保 left 不会超出 nums 数组的范围。因为 left 在查找的过程中可能已经移动到了数组的末尾,如果 left 超过了数组的索引范围,直接访问 nums[left] 会导致运行时错误。

nums[left] == target

  • 这个条件检查 nums[left] 是否等于 target。如果 left 所指向的元素等于目标值,说明找到了目标值的起始位置。此时,将 result[0] 更新为 left,即目标值在数组中的起始索引。

如果 left 不在有效范围内,或者 nums[left] 不等于 target,这说明数组中不存在目标值。此时直接返回 result,它的值仍然是 [-1, -1],表示未找到目标值。

为什么两次二分查找的 if 语句不一样:

在第一次二分查找中,我们的目标是找到 target起始位置。如果存在起始值,当 leftright 相遇时的相遇点即为起始值 target,这个时候需要保证 left 为 target,就需要right 左移来退出循环。

而在第二次二分查找中,我们的目标是找到 target结束位置。我们需要保证right 为 target,就需要 left右移来退出循坏。

二分查找的精髓通过每次比较中间值来逐步缩小查找范围,保证时间复杂度为 O(log⁡n)

方法二:

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int start = find(nums, target);
        int end = find(nums, target + 1) - 1;
        
        if (start == -1 || end < start) {
            return {-1, -1};
        }
        
        return {start, end};
    }

    int find(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
};

利用 find(nums, target) 找到 target 的第一个位置,再利用 find(nums, target + 1) 找到比 target 大的第一个元素的位置,从而间接确定 target 的结束位置。

如果数组中不存在第一个大于 target 的元素,那么 find(nums, target + 1) 的结果将会是 nums.size(),end的值为 nums.size() - 1

假设 nums = [5, 7, 7, 8, 8, 10]target = 8

  • find(nums, 8) 返回 3,因为 8 的第一个位置在索引 3
  • find(nums, 9) 返回 5,因为 98 大,且索引 5 是第一个大于 8 的位置。
  • end = find(nums, 9) - 1 等于 4,所以返回 [3, 4]
相关推荐
阑梦清川5 分钟前
数学建模启发式算法篇(一)---遗传算法
算法·数学建模·启发式算法
ErvinHowell19 分钟前
文件MD5生成性能大提升!如何实现分片与Worker优化
前端·vue.js·算法
用户405478783748220 分钟前
深度学习笔记 - Pytorch自搭建VGG-16模型实现人脸识别
算法
很透彻26 分钟前
【网络】传输层协议TCP(下)
网络·c++·网络协议·tcp/ip
益达爱喝芬达33 分钟前
力扣11.3
算法·leetcode
y_m_h33 分钟前
leetcode912.排序数组的题解
数据结构·算法
passer__jw76734 分钟前
【LeetCode】【算法】406. 根据身高重建队列
算法·leetcode
1 9 J34 分钟前
数据结构 C/C++(实验三:队列)
c语言·数据结构·c++·算法
sweetheart7-735 分钟前
LeetCode17. 电话号码的字母组合(2024秋季每日一题 59)
算法·深度优先·力扣·dfs
想做白天梦35 分钟前
LeetCode :150. 逆波兰表达式求值(含求后缀表达式和中缀转后缀表达式)
java·前端·算法