[二分查找 BST思想] 240. 搜索二维矩阵 II(二分查找可疑行 → 模拟二叉搜索树)

9 篇文章 0 订阅
8 篇文章 0 订阅

240. 搜索二维矩阵 II

题目链接:https://leetcode-cn.com/problems/search-a-2d-matrix-ii/


分类:

  • 二分查找:
    • 矩阵上做二分查找、
    • 通过每一行的min,max缩小可疑行范围、
    • 对可疑行做二分查找
  • 二叉搜索树:
    • 矩阵上模拟二叉搜索树:根 = 右上角元素,左孩子 = 根的左侧,右孩子 = 根的下侧
    • BST上的二分查找迁移到矩阵上(递归实现、迭代实现)

在这里插入图片描述

思路1:寻找可疑行范围 + 对可疑行做二分查找

先拿每一行左右端点的min,max和target比较,排除不可能存在target的行,找出可疑行的范围;

再对可疑行的每一行都做二分查找,如果找到target,则返回true;如果所有可疑行都没查找到target,返回false。

算法流程:
设可疑行的起点行为start = 0,终点行为end = n - 1。

首先,拿target和每一行的左端点left[i]比较(i表示行序号,从0开始计):

  • 如果target=left[i],则返回true;
  • 如果target<left[i],说明target只可能存在于第0~i-1行,得到可疑行的终点行end=i-1;
  • 如果target>left[i],则无法缩小行范围。

继续拿target和前面得到的所有可疑行(0~end)的右端点right[j]比较:

  • 如果target=right[j],则返回true;
  • 如果target>right[j],则说明target只可能存在于第j+1 ~ end行,所以得到可疑行的起点行start=j+1;
  • 如果target<right[j],无法缩小行范围。

最后对找到的所有可疑行(第start行 ~ 第end行)进行二分查找。

  • 二分查找迭代实现就不再赘述了。

实现代码:

//思路1
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
        int rows = matrix.length, cols = matrix[0].length;
        int start = 0, end = rows - 1;
        //target和每一行的左端点比较,确定查找的终点行
        for(int i = 0; i < rows; i++){
            //寻找第一个 > target的左端点
            if(target == matrix[i][0]) return true;
            if(target < matrix[i][0]){
                if(i == 0) return false;
                end = i - 1;
                break;//找到第一个>target的左端点就立即退出,避免end被覆盖
            }
        }
        //target和每一行的右端点比较,确定查找的起点行
        for(int i = 0; i <= end; i++){
            //寻找最后一个<target的右端点
            if(target == matrix[i][cols - 1]) return true;
            if(target > matrix[i][cols - 1]){
                if(i == end) return false;
                start = i + 1;
                //这里要找的是最后一个<target的右端点,所以找到一个满足条件的元素也不能立即退出
            }
        }
        //再对第start行~第end行做二分查找。
        for(int i = start; i <= end; i++){
            if(binarySearch(matrix[i], target)) return true;
        }
        return false;
    }
    //二分查找功能函数(迭代实现)
    public boolean binarySearch(int[] nums, int target){
        int left = 0, right = nums.length - 1;
        while(left <= right){
            int mid = (left + right) / 2;
            if(nums[mid] == target) return true;
            else if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
        }
        return false;
    }
}
  • 时间复杂度:确认可疑行的范围对效率的提升比较有限。最差情况下所有行都是可疑行,对每一行都做一次二分查找,一共m行,每一行有n个元素,一行做二分查找需要O(logn),所以整体需要O(m*logn)。
  • 空间复杂度:O(1)。

思路2:模拟二叉搜索树(推荐)

观察矩阵可以发现,如下图,把矩阵的右上角元素看做二叉搜索树的根节点root,它左边的数都小于它,它下边的数都大于它,所以可以把它所在的行和列分别看做它的左右子树:

更进一步的,对矩阵上的每个节点,都有节点的左侧都 < 该节点, 下侧都 > 该节点,所以每个节点都有对应的二叉搜索树,我们就可以把矩阵看做一个个二叉搜索树,在二叉搜索树的查找步骤就可以迁移到矩阵上:

  • 如果target == matrix[i][j],返回true;
  • 如果target < matrix[i][j], 就取它的左孩子继续查找,在这里就是取当前元素的左侧元素,即matrix[i][j-1];
  • 如果target > matrix[i][j],就取它的右孩子继续查找,在这里就是取当前元素的下侧元素,即matrix[i+1][j];

不断重复上面的步骤,直到找到目标元素返回true,或到达矩阵边界返回false。

也可以换个角度理解:matrix[i][j]是它右边、下边部分矩阵的最小值,所以如果target<matrix[i][j],就只需要查找matrix[i][j]的左边区域,如果target>matrix[i][j],target就不可能存在于和matrix[i][j]同一行中,所以就继续查找matrix[i][j]的下方区域。(和前面那个理解角度的效果是一样的)

实现代码(递归实现)
//思路2的递归实现
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //特殊用例
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;

        return search(matrix, 0, matrix[0].length - 1, target);
    }
    //递归寻找当前元素的左、下方向
    public boolean search(int[][] matrix, int i, int j, int target){
        //下标越界判断
        if(i >= matrix.length || j < 0) return false;
        if(matrix[i][j] == target) return true;
        else if(matrix[i][j] < target) return search(matrix, i + 1, j, target);
        else if(matrix[i][j] > target) return search(matrix, i, j - 1, target);
        
        //不可能执行到这里,前面不用else而用elseif是为了使每个条件都清晰直观。
        return false;
    }
}
实现代码(迭代实现,推荐)
//思路2的迭代实现
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //特殊用例
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
        int rows = matrix.length, cols = matrix[0].length;
        //当前查找的坐标,初始从右上角开始
        int left = 0, right = cols - 1;
        while(left < rows && right >= 0){
            if(matrix[left][right] == target) return true;
            else if(matrix[left][right] < target) left++;
            else right--;
        }
        return false;
    }
}
  • 时间复杂度:观察上述算法流程可以发现,我们从矩阵右上角元素出发,每次只做向左或向下移动,最差情况下从矩阵右上角一直查找到矩阵左下角,所以最多移动m+n次,时间复杂度为O(m+n)。
  • 空间复杂度:递归实现一直往下递归而没有返回,所以递归最大深度为O(m+n);迭代实现为O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值