剑指 Offer 04. 二维数组中的查找 | 刷题打卡

87 阅读2分钟

题目描述

  • 难度:中等

  • 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

  • 示例,现有矩阵 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
  • 限制
0 <= n <= 1000
0 <= m <= 1000

思路分析

蛮力算法

  • 用两个for循环暴力遍历,将二维数组的每一个元素与目标值进行比较
  • 显然,这样将要花费O(n*m)的时间复杂度,O(1)的空间复杂度。这样的时间复杂度实在太高,因此需要想办法去降低时间复杂度。

二分查找 + 遍历

  • 二维数组的每一行,都是已经排好序的递增数组,这正好满足二分查找的条件。只需要再配合循环遍历二维数组的每一行都是用二分查找即可

  • 二分查找时间复杂度为logm,遍历为n

    • 因此总共的时间复杂度为O(n*logm)(n是行数,m是列数)
  • 空间复杂度为O(1)

  • 那么为何二分查找的时间复杂度为logN呢?

    • 这里的N是数组长度
    • 因为二分查找,每一次都将问题规模近似划分为上一次问题规模的一半,最差的情况下,到了问题规模为1还没查找到此数
      • N N/2 N/4 ... 2 1
      • 这个问题规模的a1 = N ,an = 1,公比q = 1 / 2,根据an = a1*q^(n - 1)可得
        • 1 = N * (1 / 2)^(n - 1) -> 2^(n - 1) = N -> n - 1 = logN -> n = 1 + logN
      • 所以可以这个等比数列有logN + 1项,也就是需要比较logN + 1次,因此二分查找的时间复杂度为O(logN + 1) -> O(logN)

线性查找

  • 图片出处
  • 此处我一开始看官方解答时,有一种似懂非懂得感觉,直到我看见了这幅图,它是不是很想二叉搜索树?
    • 没错,你可以发现题目说明了 行是递增的,列也是递增的。那么在右顶点,它就对应了二叉搜索树的根节点,查找时当目标值较小,向左走(行);而当目标值较大时,向右走。最坏的情况是走到左底部,一共需要经过 n + m - 1步。
    • 那么它的时间复杂度就为O(n+m-1) -> O(n+m) -> O(max{n + m]),空间复杂度为O(1) Picture1.png

AC代码

/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var findNumberIn2DArray = function(matrix, target) {

    // 0*m   n*0  0*0 直接排除
    if (matrix.length === 0 || matrix[0].length === 0) {
        return false;
    }
    // // 得到n,m > 0
    let n = matrix.length;
    let m = matrix[0].length;

    // 1.暴力求解
    // 两个for循环遍历数组
    // 时间复杂读O(m*n)
    // for (var i = 0; i < n; i ++) {
    //     for (var j = 0; j < m; j ++) {
    //         if (matrix[i][j] === target)
    //             return true;
    //     }
    // }

    // 比较每行的matrix[i][m]与target的大小
    // 时间复杂度O(m*n)
    // 空间复杂度O(1)
    // for (var i = 0; i < n; i ++) {
    //     if (matrix[i][m - 1] === target) {
    //         return true;
    //     } else if (target < matrix[i][m - 1]) {

    //         for (var j = m - 2; j >= 0; j --) {
    //             if (matrix[i][j] === target) {
    //                 return true;
    //             }
    //         }
    //     }
    // }
    
    // 2.遍历 + 二分查找  
    // 时间复杂度O(nlogn)
    //   遍历需要n次
    //   二分查找:假设数组为[1..n],那么需要比较的位置为 n/(2^1) n/(2^2) n/(2^3) ... n/(2^X)
    //            由数组为[1..n],可得二分查找最后查找的位置必然为1,因此有2^X = n -> X = logn,需要logn次比较
    // 空间复杂度O(1)
    // for (var i = 0; i < n; i ++) {

    //     var c_low = 0;
    //     var c_high = m - 1;
    //     while (c_low <= c_high) {
    //         var middle = c_low + Math.floor((c_high - c_low) / 2);
    //         console.log(middle)
    //         if (matrix[i][middle] === target) {
    //             return true;
    //         } else if (matrix[i][middle] < target) {
    //             c_low = middle + 1;
    //         } else {
    //             c_high = middle - 1;
    //         }
    //     }    

    // }

    // 3.线性查找法
    var left_m = m - 1;
    var right_n = 0;
    while (right_n < n) {
        if (matrix[right_n][left_m] === target) {
            return true;
        } else if (target < matrix[right_n][left_m]) {
            left_m --;
        } else {
            right_n ++;
        }
    }

    return false;
};

总结

  • 坚持的第二天,在写算法的时候在刻意的让自己去思考除了蛮力算法意外的其它解决方案
  • 正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情