题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
我的解法
思路
暴力搜索,从数组第一个值开始与目标值进行比较,如果目标值小于等于当前值则返回当前索引,否则比较下一个值;若数组遍历结束后仍未找到符合条件的值,则返回当前索引值+1
对应Java代码
class Solution {
public int searchInsert(int[] nums, int target) {
int i = 0;
for (; i < nums.length; i++) {
if (target <= nums[i]){
return i;
}
}
return i;
}
}
复杂度分析
时间复杂度:O(N) 这里的N对应的是数组长度
空间复杂度:O(1) 用到常数个临时变量
更优的解法
思路
学习于公众号代码随想录,大佬讲的很清楚
二分法
-
适用场景
(1)有序数组:二分查找的基础条件
(2)无重复元素:一旦有重复元素,使用二分查找法返回的元素下表可能不是唯一的 -
难点——边界条件
二分法的算法逻辑很容易理解,实际应用时的问题在于边界条件处理不好,例如到底是while (left < right)
还是while (left <= right)
,是right = middle
呢,还是要right = middle - 1
呢?这里弄不清楚主要是因为「对区间的定义没有想清楚,这就是不变量」。要在二 分查找的过程中,保持不变量,这也就是「循环不变量」 (感兴趣的同学可以查一查)。
【个人理解】循环不变量是指判断条件的定义不变,即下面自己定义的左闭右闭,左闭右不闭不变,根据这一点就可以选择循环中涉及到的各种边界值了。
对应Java代码
以这道题目来举例,以下的代码中定义 target 是在一个在左闭右闭的区间里,「也就是[left, right] (这个很重要)」。这就决定了这个二分法的代码如何去写,大家看如下代码:「大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1」。
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length;
int left = 0;
int right = n - 1; // 定义target在左闭右闭的区间里, [left, rigth]
while (left <= right) { // 当left==right, 区间[left,right]依然有效
int middle = left + ((right - left) / 2); // 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else {
return middle;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], return right + 1
return right + 1;
}
}
复杂度分析
时间复杂度:O(logN)
空间复杂度:O(1)
收获总结
在数组搜索方面学习到了除暴力搜索外的另一种方法——二分法
并且了解了二分法适用的场景,掌握了实际应用时主要该注意的边界值处理方法,即认为设定一个边界原则(如左闭右闭),然后在另循环中的所有边界值都符合该规则,至于返回值(right+1),不确定的情况可以举个简单的例子看一下
以上的方法并不是什么定律,但是可以解决此类问题,可以理解成某一种类型题的解法。