【刷题之路】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),我们只需要用到常数级的额外空间。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林先生-1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值