【leetcode】240.搜索二维矩阵 II (贪心+动态规划,动图详解)

240. 搜索二维矩阵 II

难度中等

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

思路分析

这道题比较容易想到的是还继续利用矩阵中的行和列有序的特性,使用二分查找法。思路不止一种,我也尝试写过,后来发现:编写二分查找法要考虑的边界问题比较多,如果对二分查找掌握得不熟练,很可能会出错。

下面介绍的这个方法,我认为是最优解,虽然它的时间复杂度并不是最优。

  • 如果我们要用二分查找法,可以发现,如果一行的开头那个元素就比目标元素大,那么这一行的所有元素,以及行号大于这一行的元素都不在考虑的范围内。
  • 我们首先尝试从左上角开始走,发现横着走数值增大,竖着走数值也增大,目标数值这在两个方向上都有可能存在。那如果我们从右上角或者左下角除法,找目标元素,那就不一样了,于是有了下面的“排除法”。

方法一:减而治之

  • 1、如果选择左下角为起点,以下展示了“减治”的过程。

0240-lower-left-corner.gif

总结出“搜索”的规律是:

如果当前数比目标元素小,当前列就不可能存在目标值,“指针”就向右移一格(纵坐标加 1);
如果当前数比目标元素大,当前行就不可能存在目标值,“指针”就向上移一格(横坐标减 1)。

在编码的过程中要注意数组下标越界的问题。

参考代码 1

public class Solution {

    public boolean searchMatrix(int[][] matrix, int target) {
        int rows = matrix.length;
        if (rows == 0) {
            return false;
        }
        int cols = matrix[0].length;
        if (cols == 0) {
            return false;
        }


        // 起点:左下角
        int x = rows - 1;
        int y = 0;
        // 不越界的条件是:行大于等于 0,列小于等于 cols - 1
        while (x >= 0 && y < cols) {
            // 打开注释,可以用于调试的代码
            // System.out.println("沿途走过的数字:" + matrix[x][y]);
            if (matrix[x][y] > target) {
                x--;
            } else if (matrix[x][y] < target) {
                y++;
            } else {
                return true;
            }
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:O(M + N),M是这个矩阵的行数,N 是这个矩阵的列数,我们看到,这种算法是“不回头”的,至多走 M + N 步就能搜索到目标数值,或者判定目标数值在矩阵中不存子啊。

  • 空间复杂度:O(1),算法使用了常数个变量。

  • 2、如果选择右上角为起点,以下展示了“减治”的过程。

0240-top-right-corner.gif

总结出“搜索”的规律是:

如果当前数比目标元素大,当前列就不可能存在目标值,“指针”就向左移一格(纵坐标减 11);
如果当前数比目标元素小,当前行就不可能存在目标值,“指针”就向下移一格(横坐标加 11)。

在编码的过程中同样要注意数组下标越界的问题。

参考代码 2

public class Solution {


    public boolean searchMatrix(int[][] matrix, int target) {
        // 特判
        int rows = matrix.length;
        if (rows == 0) {
            return false;
        }
        int cols = matrix[0].length;
        if (cols == 0) {
            return false;
        }

        // 起点:右上角
        int x = 0;
        int y = cols - 1;

        // 不越界的条件是:行小于等于 rows - 1,列大于等于 0
        while (x < rows && y >= 0) {
            // 打开注释,可以用于调试的代码
            // System.out.println("沿途走过的数字:" + matrix[x][y]);
            if (matrix[x][y] > target) {
                y--;
            } else if (matrix[x][y] < target) {
                x++;
            } else {
                return true;
            }
        }
        return false;
    }
}

复杂度分析

(同上)。

说明:这个搜索的过程也可以使用二分查找法加快,时间复杂度收缩到 O ( log ⁡ M + l o n g N ) = O ( log ⁡ M N ) O(\log M + long N) = O(\log MN) O(logM+longN)=O(logMN),但是在编码的时候会稍显麻烦,还要考虑一些边界条件,我就不展示自己写的又臭又长的代码了。如果大家有更优雅的写法,欢迎分享出来。

方法二:二分查找

首先,我们确保矩阵不为空。那么,如果我们迭代矩阵对角线,从当前元素对列和行搜索,我们可以保持从当前 (row,col)对开始的行和列为已排序。 因此,我们总是可以二分搜索这些行和列切片。我们以如下逻辑的方式进行 : 在对角线上迭代,二分搜索行和列,直到对角线的迭代元素用完为止(意味着我们可以返回 false )或者找到目标(意味着我们可以返回 true )。binary search 函数的工作原理和普通的二分搜索一样,但需要同时搜索二维数组的行和列。

image.png

事实上,因为对角线元素的右下方(包括对角线所在的那一行和那一列)的元素都大于等于对角线元素,因此对角线元素也存在一个最大索引,也可以用二分查找定位。

参考代码 3

  • Java
class Solution {
    private boolean binarySearch(int[][] matrix, int target, int start, boolean vertical) {
        int lo = start;
        int hi = vertical ? matrix[0].length-1 : matrix.length-1;

        while (hi >= lo) {
            int mid = (lo + hi)/2;
            if (vertical) { // searching a column
                if (matrix[start][mid] < target) {
                    lo = mid + 1;
                } else if (matrix[start][mid] > target) {
                    hi = mid - 1;
                } else {
                    return true;
                }
            } else { // searching a row
                if (matrix[mid][start] < target) {
                    lo = mid + 1;
                } else if (matrix[mid][start] > target) {
                    hi = mid - 1;
                } else {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean searchMatrix(int[][] matrix, int target) {
        // an empty matrix obviously does not contain `target`
        if (matrix == null || matrix.length == 0) {
            return false;
        }

        // iterate over matrix diagonals
        int shorterDim = Math.min(matrix.length, matrix[0].length);
        for (int i = 0; i < shorterDim; i++) {
            boolean verticalFound = binarySearch(matrix, target, i, true);
            boolean horizontalFound = binarySearch(matrix, target, i, false);
            if (verticalFound || horizontalFound) {
                return true;
            }
        }
        
        return false; 
    }
}

image-20200805210639215

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页