【Leetcode】二分专题

33. 搜索旋转排序数组

链接:33

方法:部分有序的二分查找

思路

每次查找时根据mid将数组一分为二,其中有一侧是有序的,另一侧可能是有序,也可能是部分有序。
此时若target位于有序部分,则用二分法查找;若位于无序部分,再一分为二,继续之前的操作。
可以利用nums[l],nums[mid],nums[r]的大小关系判断局部是否有序。
33

代码
class Solution {
    public int search(int[] nums, int target) {
        int len=nums.length;
        int l=0,r=len-1;
        while(l<=r){
            int mid=(r-l)/2+l;
            if(nums[mid]==target){
                return mid;
            }
            //左侧有序
            if(nums[mid]>=nums[l]){
                if(target>=nums[l]&&target<nums[mid]){
                    r=mid-1;
                }
                else{
                    l=mid+1;
                }
            //右侧有序
            }else{
                if(target>nums[mid]&&target<=nums[r]){
                    l=mid+1;
                }
                else{
                    r=mid-1;
                }
            }
        }
        return -1;
    }
}
复杂度分析
  • 时间复杂度O(logn)
  • 空间复杂度O(1)

81. 搜索旋转排序数组 II

链接:81

方法:部分有序的二分查找(含重复元素)

思路

和33题的区别在于,旋转前数组是非降序排列,而非升序排列。这一差异造成的影响就是,33题中判断有序区间的方法不再通用,例如如下示例:

[1,13,1,1,1,1]
13

会出现nums[l],nums[mid],nums[r]皆相等,无法判断有序区间的情况。
因此针对这一特殊情况做一下特判,此时二分查找的左右两边各缩小1;其余情况下的做法与33题相同。

代码
class Solution {
    public boolean search(int[] nums, int target) {
        int len=nums.length;
        int l=0,r=len-1;
        while(l<=r){
            int mid=(r-l)/2+l;
            if(nums[mid]==target){
                return true;
            }
            //无法确定有序部分
            if((nums[mid]==nums[l])&&(nums[mid]==nums[r])){
                r--;
                l++;
            }
            //右侧有序
            else if(nums[mid]<=nums[r]){
                if(target>nums[mid]&&target<=nums[r]){
                    l=mid+1;
                }
                else{
                    r=mid-1;
                }
            }
            //左侧有序
            else if(nums[mid]>=nums[l]){
                if(target>=nums[l]&&target<nums[mid]){
                    r=mid-1;
                }
                else{
                    l=mid+1;
                }
            }
        }
        return false;
    }
}
复杂度分析
  • 时间复杂度O(n)
  • 空间复杂度O(1)

153. 寻找旋转排序数组中的最小值

链接:153

方法:部分有序的二分查找

思路

153

此题中,数组旋转后呈现如上图所示的大小关系,二分法寻找最小值的思路很简单:

  • 若mid位于第一段升序数组,则最小值一定出现在mid右侧
  • 若mid位于第二段升序数组或数组整体有序,则最小值一定出现在mid左侧(含mid)

需要注意区分两种情况使用的条件,要用nums[mid]和nums[r]的大小关系来判断,nums[mid]和nums[l]的大小关系不能实现区分。

代码
class Solution {
    public int findMin(int[] nums) {
        int len=nums.length;
        int l=0,r=len-1;
        while(l<r){
            int mid=(r-l)/2+l;
            if(nums[mid]>nums[r]){
                l=mid+1;
            }else{
                r=mid;
            }
        }
        return nums[l];
    }
}
复杂度分析
  • 时间复杂度O(logn)
  • 空间复杂度O(1)

154. 寻找旋转排序数组中的最小值 II

链接:154

方法:部分有序的二分查找(含重复元素)

思路

此题和153的区别在于旋转前数组为非降序,而非升序。
大致思路和153相同,需要处理一下多个数组元素相等时的情况。

154-1

  • nums[mid]>nums[r]时,最小值一定出现在mid右侧
  • nums[mid]<nums[r]时,最小值一定出现在mid左侧(含mid)
  • nums[mid]==nums[r]时,如下图所示,由于数组包含相同元素,因此不能判断最小值出现在哪一侧,因此只是除去nums[r]
    154-2
代码
class Solution {
    public int findMin(int[] nums) {
        int len=nums.length;
        int l=0,r=len-1;
        while(l<r){
            int mid=(r-l)/2+l;
            if(nums[mid]<nums[r]){
                r=mid;
            }else if(nums[mid]>nums[r]){
                l=mid+1;
            }else{
                r--;
            }
        }
        return nums[l];
    }
}
复杂度分析
  • 时间复杂度O(n)
  • 空间复杂度O(1)

240. 搜索二维矩阵 II

链接:240

方法一:逐行二分查找

思路

对每一行进行二分查找。

代码
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //每行二分
        int m=matrix.length,n=matrix[0].length;
        for(int i=0;i<m;i++){
            int l=0,r=n-1;
            while(l<=r){
                int mid=(r-l)/2 +l;
                if(matrix[i][mid]==target){
                    return true;
                }
                if(matrix[i][mid]<target){
                    l=mid+1;
                }
                else{
                    r=mid-1;
                }
            }
        }
        return false;
    }
}
复杂度分析
  • 时间复杂度O(mlogn)
  • 空间复杂度O(1)

方法二:z字形查找

思路

以矩阵右上角(或者左下角)为基准,若目标值小于基准,则排除基准所在列;若目标值大于基准,则排除基准所在行;若目标值等于基准,则返回查找成功。

这里选择基准的标准在于:是否每次对比都能缩小范围。左上角和右下角是不满足的。

代码
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //z字形查找
        int m=matrix.length,n=matrix[0].length;
        int x=0,y=n-1;
        while(x<=m-1&&y>=0){
            int s=matrix[x][y];
            if(target==s){
                return true;
            }
            if(target>s){
                x++;
            }
            if(target<s){
                y--;
            }
        }
        return false;
    }
}
复杂度
  • 时间复杂度O(m+n)
  • 空间复杂度O(1)

总结

  • 重点:在部分有序、整体无序的序列中进行二分查找
  • 尽量把图画出来以便分析各种情况
  • 需要格外注意边界和相等情况的处理
  • 一般利用nums[mid],nums[r],nums[l]的大小关系来判断当前查找区间属于哪种情况
  • 若数组中存在重复元素,在处理对应情况时一般采用只排除一或两个元素的方法,无法达到log时间复杂度
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值