35. 搜索插入位置(C++)
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 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
分析与题解
暴力遍历
我们直接遍历给定排序数组nums
中查询val
值,大致会出现三种情况:
- 数组中找到目标元素,因为数组中不存在重复元素,直接返回目标元素的对应下标即可
- 数组中找不到目标元素,且存在元素大于目标值,首位大于目标值的下标就是插入的目标下标
- 数组中找不到目标元素,且所有元素都小于目标值,直接将目标值插入到数组的末尾
nums.size()
经过上述讨论,我们可以将第二、三情况判断条件归结到一起:对于排序数组,只要当nums[i] >= val
,我们就可以返回当前下标。
完整代码如下:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int num = nums.size();
for (int i = 0; i < num; i++) {
if (nums[i] >= target)
return i;
}
// 退出循环后则直接在数组末尾插入元素
return num;
}
};
二分查找(左闭右开)
注意右区间为开区间,所以初始化左右边界时,右边界可以赋值为数组的长度
实际上该下标是越界的,无法访问到
int left = 0, right = nums.size();
然后求取中值时取左中值,并且需要注意防止溢出:
int mid = left + (right - left) / 2;
对于中值下标我们与目标值进行比较:
- 当
nums[mid] == target
,我们直接返回中值下标 - 当
nums[mid] < target
,哪怕target不在数组中,需要进行插入也不可能插入到当前位置,我们直接向后移位。那么target值可能的取值区间变为[mid + 1, right)
- 当
nums[mid] > target
,此时不确定中值下标对应元素是否为首位大于目标值的元素,该下标有可能成为插入元素下标,需要进行保留。那么target值可能的取值区间变为[left, mid)
完整代码如下:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
return left;
}
};
二分查找(左闭右闭)
注意讨论的区间为左闭右闭区间,所有右边界的初始下标必须是确实能够进行访问的:
int left = 0, right = num - 1;
关于中值的计算方式类似,但是因为是闭区间,无论nums[mid]
与target
关系如何,都需要对中值mid进行加(减)一操作。
因为双闭区间的循环判断条件为while (left <= right)
,所以退出while循环后左右边界的关系为left = right + 1
,总共可分为四种情况进行讨论:
- 当数组中存在目标元素,即
nums[mid] == target
,那么循环中就可以返回目标下标 - 当数组中不存在目标元素:
- 当目标元素大于数组中所有元素,此时循环时右边界不会改变,左边界会一直向右移动,退出循环的区间为[nums.size() , nums.size() - 1]
- 当目标元素小于数组中所有元素,此时循环时左边界不会改变,右边界会一直向左移动,退出循环的区间为[0, -1]
- 当目标元素在取值在数组所有元素之间时,此时循环的左/右边界都会改变,退出循环的区间为[targetIndex, targetIndex - 1]
综上讨论,退出循环后关于插入元素下标的选择,要么选择left
,要么选择right + 1
。
完整代码如下:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int num = nums.size();
int left = 0, right = num - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return right + 1;
}
};