代码随想录01 | 704二分查找和27移除元素

目录

一 、二分查找

1 二分查找

2 搜索插入位置 

3  在排序数组中查找元素的第一个和最后一个位置 

二、移除元素


一 、二分查找

1 二分查找

704 二分查找icon-default.png?t=MBR7https://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 搜索插入位置icon-default.png?t=MBR7https://leetcode.cn/problems/search-insert-position/description/

题中也是给了排序数组和目标值,可以使用二分查找:本题的关键是如何处理当目标值不存在数组中,返回将按顺序插入的位置

本题可以细分为四种情况:

  1.  目标值将会插在数组最前面  [0,-1]
  2. 目标值将会插在数组之后 [left,right]
  3. 目标值将会插在数组之中 [left,right]
  4. 目标值在数组之中 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  在排序数组中查找元素的第一个和最后一个位置 

在排序数组中查找元素的第一个和最后一个位置icon-default.png?t=MBR7https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/ 本题一看就知道用二分法,但实际操作起来反而没那么好想:

查找开始位置和结束位置,可以转换成两个二分法分别查找左右边界

三种情况:

  1. 目标值不在数组中,应该插入至第一个或最后一个
  2. 目标值不在数组中,应该插至数组中
  3. 目标值在数组中,返回首尾索引

计算出来的左右边界,实质上是(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。

二、移除元素

27 移除元素

由于数组元素在内存地址中是连续的,不能删除数组中的元素。要想实现移除功能,就要用其他元素覆盖原来的元素。

暴力解法:

两层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就是新数组的长度
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值