leetcode-统计有序矩阵中的负数

120 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

题目描述

给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。 请你统计并返回 grid 中 负数 的数目。

示例 1:
输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵中共有 8 个负数。

示例 2:
输入:grid = [[3,2],[1,0]]
输出:0

进阶: 你可以设计一个时间复杂度为 O(n + m) 的解决方案吗?

思路

虽然难度是简单题,但是想要第一次就把 O(n + m) 的解法想出来,一点也不简单。当然,标记为简单的原因是,对每行2分求出第一个负数,假设位置在(i,j),那么这1行负数的个数col-j,然后再每行累加这样的方法也可以过,不过这样的方法我们就不介绍了,直接来O(n + m) 的方法。
我们可以注意到,对每行都二分的方法,只用到了,每一行都是非递增顺序排列,而没有用到,每一列都是非递增顺序排列。
我们的方法是这样,从右上角开始遍历:

  • 如果当前数字为负数 那么由于“每一列都是非递增顺序排列”,我们可以知道这一列接下来的数字一定是负数,而由于我们是从矩阵右上角开始遍历的,所以当前数字一定是这一列中最大的负数。如果位置在(i,j),那么这一列负数的个数就是row-i。接下来可以把这一列从矩阵中去掉,再次从剩下部分的右上角开始遍历
  • 如果当前数字不为负数 由于当前数字是剩余矩阵最右上角的数字,是剩余矩阵中当前行最小的数字,如果都不是负数,那么这一行肯定都不是负数,可以把这一行从矩阵中去掉,再次从剩下部分的右上角开始遍历

方法遍历的顺序整体上看就是一个反向的“L”,如图所示:
0.png

我们直接以示例1的数组来跟着这个方法走一遍,行和列的下标都记为从0开始的:

  1. 遍历矩阵最右上角数字-1(0,3),因为是负数,所以这一列都是负数,这里有row-i=4个,加到结果中后,去掉第3列
    1.png
  2. 去掉第3列后,遍历剩余矩阵的右上角,2(0,2)是正数,所以剩余矩阵中,第0行肯定都不是负数,直接去掉第0行
    2.png
  3. 去掉第0行后,遍历剩余矩阵的右上角,1(1,2)是正数,所以剩余矩阵中,第1行肯定都不是负数,直接去掉第1行
    3.png
  4. 去掉第1行后,遍历剩余矩阵的右上角,-1(2,2)是负数,所以这一列都是负数,这里有row-i=2个,加到结果中后,去掉第2列
    4.png
  5. 去掉第2列后,遍历剩余矩阵的右上角,1(2,1)是正数,所以剩余矩阵中,第0行肯定都不是负数,直接去掉第2行
    5.png
  6. 直接去掉第2行后,遍历剩余矩阵的右上角,-1(3,1)是负数,所以这一列都是负数,这里有row-i=1个,加到结果中后,去掉第1列
    6.png
  7. 去掉第1列后,只剩下最后一个数组,-1(3,0)是负数,结果+1,遍历结束
    7.png

Java版本代码

class Solution {
    public int countNegatives(int[][] grid) {
        int ans = 0;
        int row = grid.length;
        int col = grid[0].length;
        int i = 0, j = col-1;
        while (i < row && j >=0) {
            if (grid[i][j] < 0) {
                ans += row - i;
                j--;
            } else {
                i++;
            }
        }
        return ans;
    }
}