「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0
提示:
- 1 <= nums.length <= 104
- -104 <= nums[i] <= 104
- nums 为无重复元素的升序排列数组
- -104 <= target <= 104
二分法
思路
写对「二分查找」的重点,从来不在于「二分查找」我们用的是哪一个模板(所有的模板背后的逻辑都一样),更不在于我们设置的区间是「左闭右闭」还是「左开右闭」。而在于认真看题、仔细分析题意,根据题目的条件和要求思考如何缩减区间,清楚地知道每一轮在什么样的情况下,搜索的范围是什么,进而设置左右边界。
我们需要分析清楚题目的意思,分析清楚要找的答案需要满足什么性质。应该清楚模板具体的用法,明白需要根据题意灵活处理、需要变通的地方,不可以认为每一行代码都是模板规定死的写法,不可以盲目套用、死记硬背。
二分查找只有一个思想,那就是:逐步缩小搜索区间。
本题解向大家介绍的,使用 left 和 right 向中间靠拢的方法,有一个非常强的语义,那就是:当 left 与 right 重合的时候,我们就找到了问题的答案,使用这种写法有一个巨大的好处,那就是返回值不需要考虑返回 left 还是 right,因为退出循环以后,它们是重合的。
在做题的过程中,会遇到两个难点:
- 取 mid 的时候,有些时候需要 +1,这是因为需要避免死循环;
- 只把区间分成两个部分,这是因为:只有这样,退出循环的时候才有 left 与 right 重合,我们才敢说找到了问题的答案(这两个难点,在练习的过程中,会逐渐清晰,不用着急一下子搞懂,事实上并不难理解)。
代码
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int middle;
while (left <= right) {
middle = (left + right) / 2;
if (target < nums[middle]) {
right = middle - 1;
} else if (target > nums[middle]) {
left = middle + 1;
} else {
return middle;
}
}
return left;
}
}
结语
业精于勤,荒于嬉;行成于思,毁于随。