1:704.二分查找
使用二分查找的前提条件是:数组是有序的、且无重复元素,因为一旦存在重复元素,那么使用二分查找返回的元素下标可能就不唯一了。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
//target是在[left,right]这个区间的
while(left<=right){ //这个是有效区间
int middle = left + ((right - left)/2); //需要用更新后的left和right来更新middle
if(nums[middle]>target){
right = middle-1;
}else if(nums[middle]<target){
left = middle+1;
}else{
return middle;
}
}
return -1;
}
};
2:35搜索插入的位置
这个题可以分为暴力解法和二分查找两种解法。
2.1暴力解法
我只说一个字:我是呆瓜。插入位置一共有的情况:
情况1.在数组的最前面插入
情况2.在数组中找到target
情况3.数组中插入target
情况4.在数组最末尾插入target
思路:遍历数组,直到找到一个跟target相等或者大于target的数,如果第一个数大于target直接返回下标,其他情况同理,代码如下:
class Solution {
public:
//第一种写法
int searchInsert(vector<int>& nums, int target) {
for(int i = 0;i<nums.size();i++){
if(nums[i]>=target){
return i;
}
}
return nums.size();
}
};
2.2二分法
思路:最原始的二分法在数组中相等的target是没问题的,关键在于其他三种情况如何考虑,第一种情况:在数组最前面插入,这种情况下right = -1,情况3:在数组中插入,这种情况下right会在目标target前一个位置,情况四同情况1类似。代码如下:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int left = 0;
int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
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 { // nums[middle] == target
return middle;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right + 1;
}
};
3:34.在排序数组中查找元素的第一个和最后一个位置
题目给的条件是:数组是升序的,且有重复元素
示例 1:
- 输入:nums = [5,7,7,8,8,10], target = 8
- 输出:[3,4]
思路:通过确定左右边界,最终实现查找目标元素的第一个和最后一个位置,存在三种情况:
情况1 target在数组范围之外,即数组的左边或者右边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6
情况2 target在数组范围内,但是数组中不存在target
情况3 target在数组范围内,且数组中存在target
3.1 寻找右边界
int getRightBorder(vector<int>&nums,int target){
int left = 0;
int right = nums.size()-1;
int rightBorder = -2;
while(left<=right){
int middle = left+ ((right-left)/2);
//右边界只能靠left来更新
if(nums[middle]>target){
right = middle-1;
}else { //if(nums[middle]<=target)
left = middle+1; //就算相等,我也移动到middle后面一位了
rightBorder = left;
}
}
return rightBorder;
}
上述代码中:else 后续代码,我有个疑惑是当nums[middle]<target时,也会更新left,更新left = 更新 rightBorder。
3.2 寻找左边界
// 二分查找,寻找target的左边界leftBorder(不包括target)
// 如果leftBorder没有被赋值(即target在数组范围的右边,例如数组[3,3],target为4),为了处理情况一
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,就要在nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
3.3 计算完左右边界之后就是主题代码了
class Splution{
public:
vectot<int> searchRange(vector<int>&nums,int target){
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
}
private:
int getRightBorder(vectot<int>&nums,int target){
int left = 0;
int right = nums.size()-1;
int rightBorder = -2;
while(left<=right){
int middle = left + ((right - left)/2);
if(nums[middle]>target){
right = middle-1;
}else{
left = middle+1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vectot<int>&nums,int target){
int left = 0;
int right = nums.size()-1;
int leftBorder = -2;
while(left<=right){
int middle = left + ((right - left)/2);
if(nums[middle]>=target){
right = middle-1;
leftBorder = right;
}else{
left = middle+1;
//rightBorder = left;
}
}
return leftBorder;
}
};
上述疑惑的解决是通过
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
这两行代码解决的,如果target在数组范围内,但是不存在target,就算leftBorder和rightBorder更新了,他们两个之间也不存在元素。