代码随想录算法训练营第一天|704.二分查找、35、34、27.移除元素

标题
704.二分查找
题目链接:704.二分查找
思路:看到有序无重复序列查找就可以用二分查找
左闭右闭和左闭右开就像在右边是否加头节点一样

左闭右闭区间方法:
因为定义target在[left, right]区间,所以有如下两点:
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=;
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1;

class Solution {
    public int search(int[] nums, int target) {
        //定义target在左闭右闭的区间里,[left, right]
        int left=0,right=nums.length-1;
        //当left==right,区间[left, right]依然有效,所以用 <=
        while(left<=right){
            int mid=(left+right)/2;
            //找到target,返回下标
            if(nums[mid]==target)return mid;
            //target 在左区间,所以[left, middle - 1]
            else if(nums[mid]>target){
                right=mid-1;
            }
            //target 在右区间,所以[middle+1, right]
            else{
                left=mid+1;
            }
        }
        //未找到目标值
        return -1;
    }
}

左闭右开区间方法:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle];

class Solution {
    public int search(int[] nums, int target) {
        //定义target在左闭右开的区间里,[left, right)
        int left=0,right=nums.length;
        //当left==right,区间[left, right)无效,所以用 <
        while(left<right){
            int mid=(left+right)/2;
            //找到target,返回下标
            if(nums[mid]==target)return mid;
            //target 在左区间,所以[left, middle)
            else if(nums[mid]>target){
                right=mid;
            }
            //target 在右区间,所以[middle+1, right)
            else{
                left=mid+1;
            }
        }
        //未找到目标值
        return -1;
    }
}

35.搜索插入位置
题目链接:35.搜索插入位置
思路:查找序列中已有的数直接用二分查找,对不在序列中的数再作处理
比如:左闭右闭方法中在1,3,5,6中插入4,区间变化[0,3]->[2,3]->[2,1],left>right,结束循环,插入位置为2;在1,3,5,6中插入2,[0,3]->[0,0],left==right,结束循环,插入位置为1。

代码随想录讲解
这道题目,要在数组中插入目标值,无非是这四种情况。
目标值在数组所有元素之前
目标值等于数组中某一个元素
目标值插入数组中的位置
目标值在数组所有元素之后

左闭右闭方法:

class Solution {
    public int searchInsert(int[] nums, int target) {
        //定义左闭右闭区间
        int left=0,right=nums.length-1;
        //因为是[left,right]区间,left==right有效,所以是<=
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]==target)return mid;
            //target在右区间,在[mid+1,right]中
            else if(nums[mid]<target){
                left=mid+1;
            }
            //target在左区间,在[left,midt-1]中
            else{
                right=mid-1;
            }
        }
        //不在序列中
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
        return right+1;
    }
}

左闭右开方法:

class Solution {
    public int searchInsert(int[] nums, int target) {
        //定义左闭右开区间
        int left=0,right=nums.length;
        //因为是[left,right)区间,left==right无效,所以是<
        while(left<right){
            int mid=(left+right)/2;
            if(nums[mid]==target)return mid;
            //target在右区间,在[mid+1,right)中
            else if(nums[mid]<target){
                left=mid+1;
            }
            //target在左区间,在[left,midt)中
            else{
                right=mid;
            }
        }
        //不在序列中
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, 0)
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right),return  right;
        // 目标值在数组所有元素之后的情况 [left, right), 因为是右开区间,所以 return right;
        return right;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置
题目链接:34
思路:二分查找找到target,再往左,往右找,起始坐标和结束坐标,但时间复杂度为o(n)。

代码随想录讲解
寻找target在数组里的左右边界,有如下三种情况:
情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}

阅读后的理解:
利用两次二分查找分别找到大于等于tar,和小于等于tar的两个坐标。

比如:5,7,7,8,8,10,target=8。
确定左边界的过程:
[0,5]—2,7—left=mid+1;
[3,5]—4,8—right=mid-1;
leftborder=right=3,left=3;
[3,3]—3,8—right=mid-1;
leftborder=right=2;
即,从[left,right]不断查找,如果中间值大于等于target,就往左减少区间(right=mid-1),并更新leftborder=right,如果中间值小于target,就往右减少区间(left=mid+1),但不更新leftborder。
如果数组中的元素都比target小,leftborder就不赋值,没有左边界,就属于情况一。
确定右边界的过程:
[0,5]—2,7—left=mid+1;
rightborder=left=3;
[3,5]—4,8—left=mid+1;
rightborder=left=5;
[5,5]—5,10—right=mid-1;
最后leftborder在target前一位,rightborder在target后一位
依次处理三种情况,情况一:leftborder或者rightborder其中任意一个没赋值
情况二:leftborder与rightborder之差小于1
情况三:leftborder与rightborder之差大于1

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftBorder=getLeftBorder(nums,target);
        int rightBorder=getRightBorder(nums,target);
        //情况一;target 在数组范围的右边或者左边
        if(leftBorder==-2||rightBorder==-2)return new int []{-1,-1};
        //情况三
        if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
        //情况二
        return new int[]{-1, -1};

    }
    //二分查找确定左边界
    public int getLeftBorder(int[]nums,int target){
        //初始化左边界值,如果没有,则为-2
        int leftBorder=-2;
        //初始化左闭右闭查找区间
        int left=0,right=nums.length-1;
        //因为是[left,right],left==right有意义,所以<=
        while(left<=right){
            int mid=(left+right)/2;
            //target在右区间,更新区间[mid+1,right]
            if(nums[mid]<target){
                left=mid+1; 
            }
            else{
                right=mid-1;
                leftBorder=right;
            }
        }
        return leftBorder;
    }
    //二分查找确定右边界
    public int getRightBorder(int[]nums,int target){
        //初始化右边界值,如果没有,则为-2
        int rightBorder=-2;
        //初始化左闭右闭查找区间
        int left=0,right=nums.length-1;
        //因为是[left,right],left==right有意义,所以<=
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]>target){
                right=mid-1;
            }
            // 寻找右边界,nums[middle] == target的时候更新left
            else{
                left=mid+1;
                rightBorder=left;
            }
        }
        return rightBorder;
    }
}

27.移除元素

题目链接:27.移除元素
思路:因为写过很多次,直接想到快慢指针,快指针遍历原数组,慢指针修改新数组。

class Solution {
    public int removeElement(int[] nums, int val) {
        //定义快慢指针
        int left=0,right=0;
        //快指针遍历数组
        while(right<nums.length){
            //如果快指针当前指向值不等于需要移除的值
            if(nums[right]!=val){
                //令慢指针当前值等于快指针的值
                nums[left]=nums[right];
                //快慢指针同时移动
                right++;
                left++;
            }
            //如果快指针当前指向值需要移除,则快指针移动一
            else{
                right++;
            }
        }
        return left;
    }
}

今日总结
虽然对二分查找很熟悉,但以前写的很乱,没搞清楚边界条件,今天写三道题,前两道都分别用了左闭右闭和左闭右开两种方式,左闭右闭直接从数组中开始二分,左闭右开相当于在右边加了一个空的头节点,所以left=right没有意义。35题主要是搞清楚最后返回插入位置时为什么是和right有关,34题主要搞清楚二分查找怎么在O(logn)时间复杂度里找到target的首坐标和尾坐标。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值