230724-czl-二分搜索

搜索篇:排序数组的搜索优先考虑二分查找

有重复元素如何借助二分优化时间

35. 搜索插入位置

考察点:

搜索算法,时间复杂度,边界

题目理解:

​ 要插入位置的选择一共有四种:

  • 目标值在数组所有元素之前
  • 目标值等于数组中某一个元素
  • 目标值插入数组中的位置
  • 目标值在数组所有元素之后

暴力解法:

 public int searchInsert(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i]>=target){
                return i;
            }
        }
        return nums.length;
    }

但是暴力解法的时间复杂度是O(n),题目要求的是O(logN),题目还给定了无重复元素的排序数组

只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。但是如果有重复元素,二分查找可能需要判断边界,下文会给出对应有重复元素的二分查找的解答

二分查找:

左闭右闭的做法:

模拟四种情况的情况,之后会发现规律

  1. 找到的target

    找到就直接return mid;

  2. 没找到

​ 没找到就会跳出循环,此时会发现规律就是right < left

  • 在数组所有元素之前:right =-1; left =0;

  • 在数组所有元素之后:right = nums.length - 1 ; left = nums.length;

  • 在数组所有元素之间:right = x ;left = x+1;

    发现要插入的索引其实就是此时对应的left的值。

public static int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;

        while (left<=right){
            //防溢出
           int   mid =  left  + (right-left)/2;
            if (nums[mid] ==target){
                return mid;
            }else if (nums[mid]>target){
                right = mid-1;
            }else {
                left = mid+1;
            }
        }
      return left;

    }

704. 二分查找

接上个题目,这里对二分查找进行一些扩展实现,比如对有重复值的有序数组进行查询。

考察点

区间的判断:

在while寻找中每一次边界的处理都要坚持根据区间的定义来操作

我个人比较喜欢的一种写法:

左闭右闭:

   public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
                return mid;
            }
        }
        //如果找不到,返回-1
        return -1;
    }
重点:
  1. 循环判断的条件 left <=right, 因为left == right是有意义的,意义就是 num[left] = num[right] 和target还需要比较一下,不然就少比较了一个元素
  2. if (nums[middle] > target) right 要赋值为 middle - 1,**因为当前这个nums[middle]一定不是target,如果是的话会直接return mid ,**所以接下来要查找的左区间结束下标位置就是 middle - 1

还有一种左闭右开,我不做讲解,给出代码:

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)
                return mid;
            else if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid;
        }
        return -1;
    }
}

剑指 Offer 53 - I. 在排序数组中查找数字 I

**注意:**本题与主站 34 题相同(仅返回值不同):https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/

考察点:

搜索算法

题目理解:

5,7,7,8,8,8,8,9,10

如上,排序数组,但是是重复的,是否仍然可以使用二分查找呢?

答案:可以

解法一:

但是需要再 nums[mid] == target 时做一些统计处理,但是这样的时间复杂度极端情况下会退化成O(n).不推荐

 //在这里统计
                ans++;
                for (int i = mid-1; i >=0 ; i--) {
                    if (nums[i]==target){
                        ans++;
                    }
                }
                for (int i = mid+1;i<nums.length;i++){
                    if (nums[i]==target){
                        ans++;
                    }
                }

题解如下:

  public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;
        int ans = 0;
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
               //在这里统计
                ans++;
                for (int i = mid-1; i >=0 ; i--) {
                    if (nums[i]==target){
                        ans++;
                    }
                }
                for (int i = mid+1;i<nums.length;i++){
                    if (nums[i]==target){
                        ans++;
                    }
                }
                break;
            }
        }
        return ans;
    }

解法二:

思路:

使用两次二分查找,找到左右边界,相减即可;

难点:

如何确定边界

如何处理结果

确定左边界为例:只需要在target==nums[mid] 的时候做如下处理。

right = mid-1 是重点,一般的二分查找,找到就停止了,但是让它继续向左寻找,通过 right = mid -1 实现。这样就可以找到左边的第一个target。

leftIndex = mid;
right = mid-1; // 重点

确定右边界同理

代码如下:

    public int search(int[] nums, int target) {
  int left = 0;
        int right = nums.length-1;
        int rightIndex = -1;
        int leftIndex = -1;
        //左边界
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
                leftIndex = mid;
                right = mid-1;
            }
        }
        //右边界

        left = 0;
         right = nums.length-1;
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
                rightIndex = mid;
                left = mid+1;
            }
        }
        //如果有边界是-1,那就说明没有这个target
        if(rightIndex == -1){
            return 0;
        }
        // 不然就返回下标 的差+1
        return rightIndex-leftIndex +1;
    }

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

题目理解:

同上解法二,两次二分,获得下标即可;

代码:

 public int[] searchRange(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;
        int rightIndex = -1;
        int leftIndex = -1;
        //左边届
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
               leftIndex = mid;
                right = mid-1;
            }
        }
           left = 0;
         right = nums.length-1;
        while (left <= right){
            int mid = left + (right-left)/2;
            if (nums[mid] > target){
                right = mid-1;
            }else if (nums[mid] < target){
                left = mid+1;
            }else {
                rightIndex = mid;
                left = mid+1;
            }
        }
        return new int[]{leftIndex,rightIndex};
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值