二分查找灵活应用 —— LeetCode-287.寻找重复数、LeetCode-378.有序矩阵中第K小的元素

二分查找描述

二分查找是一种在排序的线性表中常用的查找特定值的查找方式。它的常规过程是这样的:

  1. 对于一个长度为n的线性表nums,我们在要寻找的目标值target的可能所在的区域nums[left,right]中对它进行寻找;
  2. 最开始时,整个nums都是可能区域,因此初始化left=0,right=n-1分别指向线性表nums的最左端的下标和最右端的下标;
  3. 根据leftright计算得到mid=(left+right)/2,即可能区域的中间位置的下标,若nums[mid]==target就找到了目标值,否则将nums[mid]target进行比较,如果nums[mid]<target说明要寻找的目标值在mid右侧,因此令left=mid+1将可能区间缩小一半,如果nums[mid]>target说明要寻找的目标值在mid左侧,因此令right=mid-1将可能区间缩小一半;
  4. 在可能区间缩小、可能区间左右指针更新后继续重复上一步,直到找到targetleft>right,说明nums中不存在target,跳出循环

每一遍查找都会使可能区间缩小为当前可能区间的一半,因此称为“二分查找”。二分查找的时间复杂度为O(logn)

二分查找的灵活应用

上面是二分查找的一般使用场景和过程的描述,但是二分查找的原理和思想还可以用到其他的一些“非常规”场景下

例题1:LeetCode-287.寻找重复数

这道题有几个限制条件:

  1. 不能更改原数组(假设数组是只读的)
  2. 只能使用额外的 O(1) 的空间
  3. 时间复杂度小于 O(n2)
  4. 数组中只有一个重复数字,但他可能不止出现一次

因为这几个条件限制,我们不能使用先排序后寻找重复数的方法,因为在原数组上排序会更改原数组,使用另一个数组存储排序结果会超过O(1)的空间限制;使用hash表存储每个数字出现次数同样会超过O(1)的时间限制;每次确定数组中的一个数字,再遍历整个数组查找是否有和它相等的数字,时间复杂度为O(n2),而题目要求时间复杂度必须小于O(2);而基于位运算寻找重复数字的方法必须确定重复数字的出现次数,同样不适合本题

我们可以灵活地使用二分查找,在满足限制条件的情况下寻找到数组的重复数:

  1. 对于一个长度为n+1的线性表nums,它的数字都在1n之间,我们在要寻找的重复数字target的可能所在的区域[left,right]中对它进行寻找;
  2. 最开始时,整个[1,n]都是可能区域,因此初始化left=1,right=n分别指为线性表nums的可能的最小值和最大值;
  3. 根据leftright计算得到mid=(left+right)/2,然后遍历数组nums并统计在[left,right]范围中,比mid小、与mid相等和比mid大的数,分别记为lowequhei。若equ>1mid就是重复数,程序结束;若low>mid-left,说明在[left,right]区间中比mid小的的某一个数字不止出现了一次,重复数在[left,mid)中,因此令right=mid-1将可能区间缩小一半;否则一定有hei>right-mid,说明在[left,right]区间中比mid大的某一个数字不止出现了一次,重复数字早(mid,right]中,因此令left=mid+1将可能区间缩小一半;
  4. 在可能区间缩小、可能区间左右指针更新后继续重复上一步,直到找到重复数或left==right时,说明left指向的就是重复数,跳出循环

示例一:
在这里插入图片描述
示例二:
在这里插入图片描述

class Solution {
    public int findDuplicate(int[] nums) {
        int l=1,r=nums.length-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            int low=0,equ=0,hei=0; //记录nums中比mid小、相等和大的数字个数(只统计在[l,r]范围内的)
            for(int i=0;i<nums.length;i++)
            {
                if(nums[i]==mid)
                {
                    equ+=1;
                }
                else if(nums[i]>=l && nums[i]<mid)
                {
                    low+=1;
                }
                else if(nums[i]<=r && nums[i]>mid)
                {
                    hei+=1;
                }
            }
            if(equ>1)
            {
                return mid;
            }
            if(low>mid-l) //重复数在[l,mid)中
            {
                r=mid-1;
            }
            else //重复数在(mid,r]中
            {
                l=mid+1;
            }
        }
        return l;
    }
}

时间复杂度:共需要进行O(logn)趟二分查找,每趟查找需要O(n)的时间统计大于、小于和等于mid的数字个数,因此是O(nlogn)

空间复杂度:O(1)

我们使用这种二分查找的方法可以在满足题目中的几种限制条件的情况下成功解题。下面我们来比较一下在本题中使用的二分查找和常规二分查找的区别:

  1. 常规二分查找是寻找特定值的数字时候存在以及在数组中的索引位置。本题中的二分查找是寻找数组中存在的出现了不止一次的数字的值
  2. 常规二分查找需要提供排序的数组,也就是说随着数组索引的增加,索引对应的值也需要单调递增或单调递减。但本题中的二分查找不需要对数组排序
  3. 常规二分查找的leftright指针指向的是数组的索引值,两个指针之间的索引对应的区域是目标值对应的索引可能存在的区间。本题中两指针直线的是目标数字(要寻找的重复数字)可能的值的值区间
  4. 常规二分查找根据索引区间算出一个索引mid,并根据mid对应的值nums[mid]和目标数的大小关系确定是否找到目标数或缩小可能区间。本题中的二分查找根据值区间计算出一个值mid,并根据区间中mid的个数、比mid小的数字个数和比mid大的数字出现个数来确定是否找到重复数或缩小可能区间

例题2:LeetCode-378.有序矩阵中第K小的元素

本题的基于非常规二分查找的解法可以在LeetCode官网中找到:LeetCode-378.有序矩阵中第K小的元素-基于二分查找的题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值