LeetCode : Search a 2D Matrix 解析

来源:互联网 发布:linux sd卡自动挂载 编辑:程序博客网 时间:2024/05/29 09:48

leetcode : Search a 2D matrix

1.题目描述

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

Integers in each row are sorted in ascending from left to right.

Integers in each column are sorted in ascending from top to bottom.

For example,

Consider the following 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]

]

Given target = 5, return true.

Given target = 20, return false.


2.题目分析

题目意思很明确,要求我们从给定的矩阵(二维数组)中判断输入的目标值(target)是否存在。而且,这个二维数组有两个特殊的地方,它的列和行都是按照升序排列的,也就是说,每一个数都比它前面的数小,比它后面的数大,比它上面的数大,比它下面的数小。

知道题目的意思后,大家头脑里面应该都有一个很简单的方法了,那就是暴力求解,搜索整个矩阵,有就是有,没有那就是没有。这个解法是最简单的,但是它有个问题,这样求解会导致算法在最坏的情况下要搜索整个矩阵,而且还有很大可能性是找不到的,所以说这样做不够聪明。

要想用一个更好的办法,那我们就充分利用题目的条件:所有列和行都是按照升序排列。根据之前的几篇博客,我们可以尝试一下用分治的思想来解决。但是这个分治不是将问题分解,而是将问题规模变小。

通过观察我们可以发现,左上角的数是整个矩阵中最小的,而右下角的数是整个矩阵中最大的,我们要思考的问题是怎么利用已知条件。

通过对题目仔细盯着看(确实是这样,没办法解释这个想法),我发现:对于右上角的数来说,它前面(同一列)的数都比它小,它下面的数都比它大;对于左下角的数来说,它上面的数都比它小,它后面的数都比它大;这是一个很重要的发现,我们可以通过判断target与左下角(或者右下角)的数之间的大小关系,来不断精确定位。

比如在题目所给的矩阵中,如果target = 5,与左下角的数相比,target < left_down,那target如果存在就一定在上四行中,第五行就不用理会了;如果target = 20,与左下角的数相比,target > left_down,那target如果存在就一定在后四列中,第一列我们就不用考虑了(与右上角的数比较时同理)。

那这样,我们就可以用position来作为target的坐标,通过不断的与左下角(或者右上角)比较,来不断接近target正确的位置。如果,我们求出的position超出了矩阵的范围,那就证明target并不在矩阵中。

解题的关键就在于左下角和右上角的数的特殊性,只要理解了这一点,那题目就迎刃而解了。

接下来我会将这个思想用3种不同的代码来进行实现。


3.代码实现

按照之前的思想,我们用position_x和position_y来代表target的虚拟位置,初始的虚拟位置可以放在左下角或者右下角。但是只能从一边逼近,双管齐下的方法接下来会介绍。在逼近的时候一定要弄清楚判断大小之后的逻辑关系,也就是位置应该怎么变化。

    bool searchMatrix(vector<vector<int>>& matrix, int target) {        int position_x = matrix.size() - 1; // the num of row    int position_y = 0; // the num of column    while (position_x >=0 && position_x < matrix.size()    && position_y >= 0 && position_y < matrix[0].size()) {        int num = matrix[position_x][position_y];        if (num > target) { // 理解这里的逻辑关系是很重要的一点            position_x--;        } else if (num < target) {            position_y++;        } else {            return true;        }    }    return false;    }

在理解了这两个特殊的点之后,我们是不是应该想想怎么同时利用这两个点呢?之前我们说过缩减问题的规模(和分治法有一定的区别,但思想是类似的),那我们怎样才能不断压缩matrix的大小呢?和第一种方法类似,我们还是要利用那个特殊点。对于左下角的数来说,如果target比它小,那我们就可以移除最下面的一行,如果target比它大,那我们就可以移除第一列;对于右下角的数来说也是相似的。就这样,我们可以在一次递归中消除最少两行/列(从两个顶点),这样不就达到了我们的目的了吗?

    if (matrix.size() == 0 || matrix[0].size() == 0) {        return false;    }    if (matrix.size() == 1 && matrix[0].size() == 1 && matrix[0][0] != target) {        return false;    } // 返回条件    int row = matrix.size();    int col = matrix[0].size();    int right_up = matrix[0][col - 1]; // 右上角    int left_down = matrix[row - 1][0]; // 左下角    if (target < left_down) {        matrix.pop_back();    } else if (target > left_down) {        for (int i = 0; i < matrix.size(); i++) {            matrix[i].erase(matrix[i].begin());        }    } else {        return true;    }    if (target < right_up) {        int erase_num = matrix[0].size() - 1; // 如果直接使用matrix[0].size()会出错        // 因为当matrix[0]中有元素已经被删除时        // matrix[1]中还没有被删除        // 这就会导致删除错误元素的情况发生        for (int i = 0; i < matrix.size(); i++) {            matrix[i].erase(matrix[i].begin() + erase_num);        }    } else if (target > right_up) {        matrix.erase(matrix.begin());    } else {        return true;    }    return searchMatrix(matrix, target);

对于这种方法,我们同样必须搞清楚逻辑关系,不清楚的时候可以在纸上画一画。除了逻辑要清晰以外,还有一个必须要注意的点是数组边界和下标问题,也就是说我们一定要随时记住下标的问题。就本题而言,容易出现问题的几个地方如下:

1.对二维数组来说,matrix.size()=0,但是我们却访问了matrix[0].size()

2.比如说现在block外面定义了row或者col,然后每次都直接使用。因为我们每一次判断都会对数组进行删除,直接使用row或者col会导致数组越界,甚至会出现删除的是错误的数。

所以我们一定要随时关注数组size的问题,当程序出现bug的时候也可以从这方面入手。


既然我们可以使用递归完成,那同样的我们也能使用循环来完成。整个思想是完全一样的,只不过实现方法不同。

    if (matrix.size() == 0 || matrix[0].size() == 0) {        return false;    }    while (matrix.size() != 0 && matrix[0].size()) {        if (target < matrix[matrix.size() - 1][0]) {            matrix.pop_back();        } else if (target > matrix[matrix.size() - 1][0]) {            for (int i = 0; i < matrix.size(); i++) {                matrix[i].erase(matrix[i].begin());            }        } else {            return true;        }        if (matrix.size() == 0 || matrix[0].size() == 0)            return false;        if (matrix.size() == 1 && matrix[0].size() == 1 && matrix[0][0] != target) {            return false;        }        if (target < matrix[0][matrix[0].size() - 1]) {            int erase_num = matrix[0].size() - 1;            for (int i = 0; i < matrix.size(); i++) {                matrix[i].erase(matrix[i].begin() + erase_num);            }        } else if (target > matrix[0][matrix[0].size() - 1]) {            matrix.erase(matrix.begin());        } else {            return true;        }        if (matrix.size() == 1 && matrix[0].size() == 1 && matrix[0][0] != target) {            return false;        }    }    return false;

left_down 和 right_up在这里每一次循环中都在更新,相当于递归中每一次的更新。


4.总结

对于数组下标的问题不够重视,导致在写代码的时候出现了很多bug。同样,代码水平比较低,写不出简洁的代码,仍需要努力。


有任何问题欢迎在评论区留言。

原创粉丝点击