【刷题之路】LeetCode 1351. 统计有序矩阵中的负数
一、题目描述
原题连接: 1351. 统计有序矩阵中的负数
题目描述:
给你一个 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
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 100
-100 <= grid[i][j] <= 100
进阶:你可以设计一个时间复杂度为 O(n + m) 的解决方案吗?
二、解题
1、方法1——暴力法
1.1、思路分析
直接遍历矩阵中所有的元素,用一个变量count来记录矩阵中负数的个数,
当grid[i][j] < 0时,就让count++;
最后返回count即可。
1.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
int countNegatives1(int** grid, int gridSize, int* gridColSize) {
assert(grid && gridColSize);
int i = 0;
int j = 0;
int count = 0;
for (i = 0; i < gridSize; i++) {
for (j = 0; j < *gridColSize; j++) {
if (grid[i][j] < 0) {
count++;
}
}
}
return count;
}
时间复杂度:O(nm),n和m分别为矩阵的函数和列数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。
2、方法2——二分法
2.1、思路分析
根据题目描述,我们可以在每一行使用二分法找到第一个小于0的元素,记其坐标为pos,则这一行的负数个数为col - pos:
我们用一个变量count来记录矩阵中负数的个数,每次都让count += col - pos。
最后返回count即可。
2.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
int countNegatives2(int** grid, int gridSize, int* gridColSize) {
assert(grid && gridColSize);
int i = 0;
int left = 0;
int right = 0;
int mid = 0;
int count = 0;
for (i = 0; i < gridSize; i++) {
left = 0;
right = *gridColSize - 1;
while (left <= right) {
if (grid[i][left] < 0) {
count += *gridColSize - left;
break;
}
mid = left + (right - left) / 2;
if (grid[i][mid] < 0) {
right = mid;
}
else {
left = mid + 1;
}
}
}
return count;
}
时间复杂度:O(nlogm),n和m分别为矩阵的行数和列数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。
3、方法3——分治法
3.1、思路分析
由题目描述我们可知整个矩阵都是每行每列非递增,所以每一行从前往后第一个负数的位置是不断递减的。
所以我们就可以利用这个性质来设计一个利用二分法的分治算法。具体方法如下:
我们可以设计一个函数:find_negative(int left, int right, int L, int R, int **grid)
来寻找第left行到第right行的第L列到第R列的小矩形范围内一共有多少个负数。
这个函数内部的具体做法如下:
首先我们计算left到right的中间行mid行的第一个负数的位置pos(使用二分法完成),当我们找到pos,就可以确定pos左端的数字全都是小于0的,pos下边的数字也都是全部小于0的。
所以我们可以肯定的是,find_negative(left, mid - 1, L, pos - 1, grid)是大于或等于0的,就如下图中绿色方框部分:
所以对于上半部分,我们只能求find_negative(left, mid - 1, pos, R, grid)来确定剩下的负数的个数:
而对于下半部分,我们则可以放心的求find_negative(mid + 1, right, L, R, grid)的值。
若是出现pos == 0,则说明上半部分已不存在小于0的数字,所以我们就可以直接判断下半部分find_negative(mid + 1, right1, L, R, grid):
所以函数内的递归可以分为两种情况:
当pos不等于0时,递归:
find_negative(left, mid - 1, pos, R, grid)
和
find_negative(mid + 1, right, L, R, grid)
当pos等于0时,递归:
find_negative(mid + 1, right, L, R, grid)
而对于负数个数的统计,我们是通过在每一次的递归中累加R - pos + 1来统计的。
3.2、代码实现
// 有了以上思路,那我们写起代码来也就水到渠成了:
// 先写find_negative函数
int find_negative3(int left, int right, int L, int R, int** grid) {
assert(grid);
if (left > right) {
return 0;
}
int mid = 0;
int Mid = 0;
int pos = -1;
Mid = left + (right - left) / 2;
int l = L;
int r = R;
while (l <= r) {
if (grid[Mid][l] < 0) {
pos = l;
break;
}
mid = l + (r - l) / 2;
if (grid[Mid][mid] >= 0) {
l = mid + 1;
}
else {
r = mid;
}
}
int answer = 0;
if (pos != -1) {
answer += R - pos + 1;
answer += find_negative(left, Mid - 1, pos, R, grid);
answer += find_negative(Mid + 1, right, L, R, grid);
}
else {
answer += find_negative(Mid + 1, right, L, R, grid);
}
return answer;
}
int countNegatives3(int** grid, int gridSize, int* gridColSize) {
assert(grid && gridColSize);
return find_negative(0, gridSize - 1, 0, *gridColSize - 1, grid);
}
// 时间复杂度:O(nlogn)。
// 空间复杂度:O(1)。
4、方法4——倒序遍历
4.1、思路分析
由方法3我们应该可以得到启发,当我们算出了第i行从前往后第一个负数的位置POSi,那么第i+1行的POSi+1的位置肯定是位于[0, POSi]中的:
所以对于第i+1行我们其实可以直接就从POSi开始到这循环,直到找到POSi+1为止。
4.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
int countNegatives4(int** grid, int gridSize, int* gridColSize) {
assert(grid && gridColSize);
int i = 0;
int j = 0;
int answer = 0;
int pos = *gridColSize - 1;
for (i = 0; i < gridSize; i++) {
for (j = pos; j >= 0; j--) {
if (grid[i][j] >= 0) { // 先找到第一个负数
if (j + 1 < *gridColSize) {
pos = j + 1;
answer += *gridColSize - pos;
}
break;
}
}
if (j < 0) { // 一行没有一个正数,说明下面几行全都是负数了
answer += *gridColSize;
pos = -1;
}
}
return answer;
}
时间复杂度:O(n+m),n和m分别为矩阵的行数和列数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。