小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
题目
给定一个按照升序排列的整数数组 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] <= 109nums是一个非递减数组-109 <= target <= 109
解题思路
本文我们一起从O(n)到O(logn)找更优解。借纳兰性德《木兰花令·拟古决绝词》说明解法特点
1. 循环
遍历初见target赋值开始位和结束位。再见target只更新结束位直到当前数>目标值
代码
var searchRange = function(nums, target) {
return nums.reduce((p, v, i) => v === target ? [p[0] === -1 ? i : p[0], i] : (v > target && (nums.length = 0), p), [-1, -1])
};
2.哈希表
- 对象作哈希表
当前数作键名。初见赋值。再见只更新结束位。终得所有数的起止位置
代码
var searchRange = function(nums, target) {
return nums.reduce((h, v, i) => (h[v] ? h[v][1] = i : h[v] = [i, i], h), Object.create(null))[target] || [-1, -1]
};
- 对比
循环解法,未用到升序性质。虽大材小用,却求得所有数,是以不变应万变之解法
3.二分查找
- 每轮取中间数。大于目标值,继续查找左边一半。小于目标值,继续查找右边一半
- 找不到,返回
-1。找到,以其为中心左右扩散找不等于目标值且不越界的边界
代码
var searchRange = function(nums, target) {
var i = binarySearch(nums, target)
if (i === -1) return [-1, -1]
else {
var l = i, r = i + 1
while(nums[l] === target && l >= 0) l--
while(nums[r] === target && r < nums.length) r++
}
return [l + 1, r - 1]
};
var binarySearch = (nums, target, l = 0, r = nums.length - 1, m = l + r >>> 1) =>
l > r ? -1 :
nums[m] === target ? m :
nums[m] > target ? binarySearch(nums, target, l, m - 1)
: binarySearch(nums, target, m + 1, r)
- 候选减
半,速度加快的二分法比较- 二分查找:查找复杂度稳定
O(logn)。顺序存储,空间100%利用。删除插入难 - 二叉搜索树:查找复杂度理想
O(logn),深度为n时为O(n)
链式存储,指针占额外空间。删除插入只用移动指针
- 二分查找:查找复杂度稳定
4.二分查找
区别在于当nums[m] === target时,上解法,直接返回值- 本解法,寻
左边界时,继续向左查找。寻右边界时,继续向右查找,如图 l > r时返回右边界。这样左边界会到初见目标值左侧,右边界会到目标值最后出现位
代码
var searchRange = function(nums, target) {
var l = binarySearch(nums, target, true), r = binarySearch(nums, target, false)
return l === r ? [-1, -1] : [l + 1, r]
};
var binarySearch = (nums, target, isL, l = 0, r = nums.length - 1, m = l + r >>> 1) =>
l > r ? r : (isL ? nums[m] >= target : nums[m] > target)
? binarySearch(nums, target, isL, l, m - 1)
: binarySearch(nums, target, isL, m + 1, r)
[2,2,2,2,2,2...] target = 2该例在上解法左右扩散时,会退化成O(n)比翼鸟只有一目一翼,需两两齐飞。2个二分查找可稳定时间复杂度- 为什么是
l > r,为什么要返回r?因为要考虑4种边界情况,如图
- 以上
4种情况即本题二分查找的边界情况,正是边界帮助我们确定了条件和返回指针