一起养成写作习惯!这是我参与「掘金日新计划 · 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”,如图所示:
我们直接以示例1的数组来跟着这个方法走一遍,行和列的下标都记为从0开始的:
- 遍历矩阵最右上角数字-1(0,3),因为是负数,所以这一列都是负数,这里有row-i=4个,加到结果中后,去掉第3列
- 去掉第3列后,遍历剩余矩阵的右上角,2(0,2)是正数,所以剩余矩阵中,第0行肯定都不是负数,直接去掉第0行
- 去掉第0行后,遍历剩余矩阵的右上角,1(1,2)是正数,所以剩余矩阵中,第1行肯定都不是负数,直接去掉第1行
- 去掉第1行后,遍历剩余矩阵的右上角,-1(2,2)是负数,所以这一列都是负数,这里有row-i=2个,加到结果中后,去掉第2列
- 去掉第2列后,遍历剩余矩阵的右上角,1(2,1)是正数,所以剩余矩阵中,第0行肯定都不是负数,直接去掉第2行
- 直接去掉第2行后,遍历剩余矩阵的右上角,-1(3,1)是负数,所以这一列都是负数,这里有row-i=1个,加到结果中后,去掉第1列
- 去掉第1列后,只剩下最后一个数组,-1(3,0)是负数,结果+1,遍历结束
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;
}
}