目录
一 、二分查找
1 二分查找
704 二分查找https://leetcode.cn/problems/binary-search/
二分查找实际上就是:有序数组,查找目标元素
- 找到中间值mid;
- 目标值大于mid,就往右找,更新左边界;
- 目标值小于mid,往左找,更新右边界。
左右边界的更新具体要根据左闭右闭还是左闭右开来判断。
左闭右开如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
//左闭右开
int left = 0;
int right = nums.size();//右边界为开区间,这里不包含nums[right]
while(left < right){ // [ left , right)
int mid = left + (right - left)/2;//防止溢出
if(target > nums[mid]) left = mid+1;//目标值大于中间值,往右缩:[mid+1,right)
else if(target < nums[mid]) right = mid;//目标值小于中间值,往左缩:[left,mid)
else return mid;
}
return -1;
}
};
左闭右闭如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; //[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出
if (nums[mid] > target) {
right = mid - 1; // 目标值小于中间值,即目标值在左边,[left, mid - 1]
} else if (nums[mid] < target) {
left = mid + 1; // 目标值大于中间值,即目标值在右边,[mid + 1, right]
} else {
return mid; // 找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
2 搜索插入位置
35 搜索插入位置
https://leetcode.cn/problems/search-insert-position/description/
题中也是给了排序数组和目标值,可以使用二分查找:本题的关键是如何处理当目标值不存在数组中,返回将按顺序插入的位置
本题可以细分为四种情况:
- 目标值将会插在数组最前面 [0,-1]
- 目标值将会插在数组之后 [left,right]
- 目标值将会插在数组之中 [left,right]
- 目标值在数组之中 return mid
其中,前三种是需要处理的关键,第四种就可以直接利用二分法找到mid
二分法:
//左闭右闭
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
int mid;
while(left <= right){
mid = left + (right - left)/2;
if(target > nums[mid]) left = mid+1;
else if(target < nums[mid]) right = mid-1;
else return mid;
}
//目标值将会插在数组最前面 [0,-1]
//目标值将会插在数组之后 [left,right]
//目标值将会插在数组之中 [left,right]
//目标值在数组之中 return mid
return right+1;
}
};
情况1:
情况2、3和情况1类似,比较好看出来
左闭右开:
//左闭右开
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
int mid;
while(left < right){
mid = left + (right - left)/2;
if(target > nums[mid]) left = mid+1;//[left,right)
else if(target < nums[mid]) right = mid;
else return mid;
}
//1. 目标值将会插在数组最前面 [0,0)
//2. 目标值在数组之中 return mid
//3. 目标值将会插在数组之中 [left,right)
//4. 目标值将会插在数组之后 [left,right)
return right;
}
};
暴力解法:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int size = nums.size();
//for循环寻找目标值
for(int i = 0;i < size;i++){
//1.找到目标值
//2.无目标值:应该插在数组最前面,即目标值最小
//3.无目标值:插在数组中间
if(nums[i] >= target){
return i;
}
}
//4.无目标值:应该插在数组最后,即目标值最大
return size;
}
};
3 在排序数组中查找元素的第一个和最后一个位置
在排序数组中查找元素的第一个和最后一个位置https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/ 本题一看就知道用二分法,但实际操作起来反而没那么好想:
查找开始位置和结束位置,可以转换成两个二分法分别查找左右边界
三种情况:
- 目标值不在数组中,应该插入至第一个或最后一个
- 目标值不在数组中,应该插至数组中
- 目标值在数组中,返回首尾索引
计算出来的左右边界,实质上是(left-1,right+1)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftBorder = findLeftBorder(nums,target);
int rightBorder = findRightBorder(nums,target);
//这里注意不要用-1对左右边界进行初始化,因为当数组元素只有1个时,
//左边界此时正好等于-1,就返回了{-1,-1}
//而不是{0,0}
if(leftBorder == -2 || rightBorder == -2) return {-1,-1};
if(rightBorder-leftBorder > 1) return {leftBorder+1,rightBorder-1};
return {-1,-1};
}
//开始位置和结束位置:即左右边界,各自用二分法找到
private:
int findRightBorder(vector<int>& nums, int target){
int left = 0;
int right = nums.size()-1;
int rightBorder = -1;
while(left <= right ){
int mid = left + (right - left)/2;
if(nums[mid] > target){
right = mid - 1;
}
else{ //找右边界,当目标值大于等于mid
left = mid + 1;
rightBorder = left;
}
}
return rightBorder;
}
int findLeftBorder(vector<int>& nums, int target){
int leftBorder = -1;
int left = 0;
int right = nums.size()-1;
while(left <= right){
int mid = left + (right - left)/2;
if(nums[mid] >= target){ //找左边界,当目标值小于等于mid
right = mid-1;
leftBorder = right;
}else{
left =mid+1;
}
}
return leftBorder;
}
};
4 位运算
在上面的二分查找题目中计算mid时,需要注意溢出问题
可以用下面这种方法代替简单的(left+right)/2
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
或者利用位运算符
mid = left + ((right - left) >> 1)
右移运算符是将一个二进制数按指定移动的位数向右移动。
移动过程中,正数最高位补0,负数最高位补1,无符号数最高位补0。
二、移除元素
由于数组元素在内存地址中是连续的,不能删除数组中的元素。要想实现移除功能,就要用其他元素覆盖原来的元素。
暴力解法:
两层for循环,第一层寻找目标值val,第二层在找到目标值之后,将后面的元素依次向前覆盖一位。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for(int i = 0; i < size; i++){
if(nums[i] == val ){//找到目标
for(int j = i+1; j < size; j++){
nums[j-1] = nums[j];//将后面的元素依次向前覆盖
}
i--;//向前覆盖之后,即i以后的元素都向前移动了一位,那么i也要向前移动一位
size--;
}
}
return size;
}
};
双指针法:
这里利用快慢指针:
- fast在前面探路,寻找新数组的元素
- slow在后面构造新数组
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
int slow = 0;
for(int fast = 0; fast < size; fast++){
if(nums[fast] != val ) nums[slow] = nums[fast],slow++;
}
return slow;//最后slow构造新数组完成,slow就是新数组的长度
}
};