LeetCode题目34:在排序数组中查找元素的第一个和最后一个位置
原题描述
+
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]
。
示例 1
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
原题链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
思路解析
+
毫无疑问,时间复杂度O(log n)和升序数组,提示了我们使用二分查找的解法。
普通的二分查找在找到target后立即返回,所以我们需要做变式,情况分为以下两种。
寻找左边界
还是得举个例子。假设nums=[5, 7, 7, 8, 8, 10],target=7,那么应用一次二分查找得到:
显然不能立即返回,应该让mid作为新的边界,再做一次二分查找,mid才能指向预期结果。
那么问题来了,我们只知道当mid指向了target应该仍然继续二分查找下去,但却不知道应该经过多少次查找为止。
当nums[mid]大于或等于target时(等于的情况也必须要挪动,因为要尽可能的逼近边界),我们一定会不断让higher向左挪动,使它将不断靠近lower。只有nums[mid]小于target时,我们才会向右挪动lower。此时由于我们已经知道nums[mid]不等于target,所以lower要挪动到mid+1的位置。
那么这种情况下,当lower和higher相撞,该点一定是左边界。因为lower的左边不是target,而higher也一直在尽可能的往左挪动。
寻找右边界
与上面过程相反,我们尽可能向右挪动lower,让其与higher相撞即可。即当nums[mid]小于或等于target时,要挪动lower。但如果复用上面的逻辑,每次挪动时令lower=mid+1,那么最终lower一定会与higher相撞于最后一个target的后一个位置。此时lower-1才是所求。
这样调用两次二分查找逻辑,就可以完成题目。实现时,为了能重用二分查找逻辑,可以增加一个参数来控制寻找左边界还是右边界。
复杂度分析
+
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
C++参考代码
+
class Solution {
public:
int find_boudary(vector<int>& nums, int target, bool left) {
int lower = 0;
int upper = nums.size();
while (lower < upper) {
int mid = (lower + upper) / 2;
if ((nums[mid] > target) || (left && nums[mid] == target)) {
upper = mid;
} else {
lower = mid + 1;
}
}
return lower;
}
vector<int> searchRange(vector<int>& nums, int target) {
int left_most = find_boudary(nums, target, true);
if (left_most == nums.size() || nums[left_most] != target) {
return {-1, -1};
}
int right_most = find_boudary(nums, target, false) - 1;
return {left_most, right_most};
}
};
- Elasticsearch增删改查 之 —— Get查询
- 实现两个N*N矩阵的乘法,矩阵由一维数组表示
- Elasticsearch入门必备——ES中的字段类型以及常用属性
- C++容器与算法
- Effective c++ 小结
- Java程序员的日常—— Properties文件的读写
- Java程序员的日常——经验贴(纯干货)二
- Elasticsearch——使用_cat查看Elasticsearch状态
- 题目1003:A+B ---c_str(),atoi()函数的使用;remove , erase函数的使用
- Java初学者需掌握的30个概念
- 有趣的Linux命令行效果
- 僵尸进程
- C++ 虚拟继承
- strcpy函数
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- keras做CNN的训练误差loss的下降操作
- Python基于yaml文件配置logging日志过程解析
- Python ckeditor富文本编辑器代码实例解析
- PHP自定义错误处理的方法分析
- PHP聊天室简单实现方法详解
- phpStorm+XDebug+chrome 配置详解
- PHP面向对象程序设计之多态性的应用示例
- PHP设计模式之单例模式定义与用法分析
- PHP面向对象程序设计之接口的继承定义与用法详解
- PHP简单验证码功能机制实例详解
- php高清晰度无损图片压缩功能的实现代码
- Python自省及反射原理实例详解
- 浅谈django框架集成swagger以及自定义参数问题
- keras CNN卷积核可视化,热度图教程
- 解决tensorflow读取本地MNITS_data失败的原因