发现自己刷完题后还是印象不深,痛定思痛,开始写题解了,从数组开始。刷题顺序参考了代码随想录。

看完题后先用自己的方法尝试解决,因为是有序数组,常规思路解决其实也很容易。
遍历数组的每一个元素,找到大于等于target的数,就跳出循环,返回i值。如果都比target小,nums.size()就是插入的位置。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int i;
for(i = 0; i < nums.size(); i++){
if(nums[i] >= target){
break;
}
}
return i;
}
};
执行结果也还是可以的。

下面学习二分法。
有序数组是二分查找的前提。
基础思路:先设定左侧下标 left 和右侧下标 right,再计算中间下标mid。每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则target在右半区间, left 右移;nums[mid] > target 则target在左半区间,right 左移。
核心思想:不断排除不存在解的区间,直至最后剩下一个。
学习大神经验:从一个元素什么时候不是解开始考虑下一轮搜索区间是什么。区间 [left, right] 里可能存在目标元素,我们要在循环体里排除一定不存在目标元素的区间,用排除条件来确定下一轮包含解的区间。如果是 [mid, right] 就对应 left = mid ,如果是 [left, mid - 1] 就对应 right = mid - 1。再根据下一轮区间的取法确定mid的计算式。
注意:
- while(left < right) 退出循环的时候,有 left == right 成立,区间 [left, right] 只剩下1 个元素,这个元素有可能是我们要找的元素。可能要进行判断处理。
- 区间划分与mid取法的关系:
mid = left + (right - left) / 2,向下取整,mid在左区间,对应[left, mid] 与 [mid + 1, right] 。
mid = left + (right - left + 1) / 2,向上取整,mid在右区间,对应 [left, mid - 1] 与 [mid, right] 。
理解记忆:如果区间只有两个数,这样划分不会进入死循环。
分析此题知,如果nums中的数可能都小于target,需要单独处理。并且严格小于target的值一定不是解,从而得到下一轮区间。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if(nums.back() < target) return nums.size();
int left = 0;
int right = nums.size() - 1;
while(left < right){
int mid = left + (right - left) / 2; // 避免溢出
if(nums[mid] < target){ // 排除一定不存在的元素,确定下一轮区间
left = mid + 1; // [mid + 1, right]
} else right = mid; // [left, mid]
}
return left;
}
};
思考流程总结:
- 单独判断一定不在区间 [left,right] 里的情况
- 开始二分:判断区间里的元素什么时候不是解,从而确定下一轮搜索区间
- 判断mid的计算是否对应区间。
mid = left + (right - left) / 2,对应[left, mid] 与 [mid + 1, right] ;
mid = left + (right - left + 1) / 2,对应 [left, mid - 1] 与 [mid, right] 。
注:
循环的条件也可以为left <= right,此时为在循环体内部找目标元素,将情况分为三个分支进行讨论。当确定目标元素一定在数组中时,也可以使用这种写法。