前端算法第一五三期-统计有序矩阵中的负数

121 阅读1分钟

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

给你一个 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

二分查找

注意到题目中给了一个性质,即矩阵中的元素无论是按行还是按列,都以非递增顺序排列,可以考虑把这个性质利用起来优化暴力。已知这个性质告诉了我们每一行的数都是有序的,所以我们通过二分查找可以找到每一行中从前往后的第一个负数,那么这个位置之后到这一行的末尾里所有的数必然是负数了,可以直接统计。

  • 遍历矩阵的每一行。
  • 二分查找到该行从前往后的第一个负数,考虑第 ii 行,我们记这个位置为 posipos_i,那么第 ii[posi,m1][pos_i,m-1] 中的所有数都是负数,所以这一行对答案的贡献就是 m1posi+1=mposim-1-pos_i+1=m-pos_i
  • 最后的答案就是 i=0n1(mposi)\sum_{i=0}^{n-1}(m-pos_i)
var countNegatives = function(grid) {
    // 简单二分 只要是递增或递减 查找的 基本都可以使用二分
    let count=0;
    for(let i=0;i<grid.length;i++){
        let start=0,end=grid[i].length-1
        while(start <= end){
            let mid = Math.floor((start+end)/2)
            if(grid[i][mid] <0){
                if(mid===0){
                    count+=grid[i].length
                    break;
                }else{
                    if(grid[i][mid-1] <0){
                        end=mid-1
                    }else{
                         count+=grid[i].length-mid
                         break;
                    }
                }
            }else{
                start=mid+1
            }
        }

    }
    return count
};

图片.png

分治

方法二其实只利用了一部分的性质,即每一行是非递增的,但其实整个矩阵是每行每列均非递增,这说明了一个更重要的性质:每一行从前往后第一个负数的位置是不断递减的,即我们设第 ii 行的第一个负数的位置为 posipos_i,不失一般性,我们把一行全是正数的 pospos 设为 mm,则

pos0>=pos1>=pos2>=...>=posn1pos0>=pos1>=pos2>=...>=posn−1

所以我们可以依此设计一个分治算法。

我们设计一个函数 solve(l,r,L,R)solve(l,r,L,R) 表示我们在统计 [l,r][l,r] 行的答案,第 [l,r][l,r]pospos 的位置在 [L,R][L,R] 列中,计算 [l,r][l,r] 的中间行第 midmid 行的的 posmidpos_{mid},算完以后根据之前的方法计算这一行对答案的贡献。然后根据我们之前发现的性质,可以知道 [l,mid1][l,mid-1] 中所有行的 pospos 是大于等于 posmidpos_{mid}[mid+1,r][mid+1,r] 中所有行的 pospos 值是小于等于 posmidpos_{mid} 的,所以可以分成两部分递归下去,即:

solve(l,mid1,posmid,R)solve(l,mid−1,posmid,R)

solve(mid+1,r,L,posmid)solve(mid+1,r,L,posmid)

所以答案就是 mposmid+solve(l,mid1,posmid,R)+solve(mid+1,r,L,posmid)m-pos_{mid}+solve(l,mid-1,pos_{mid},R)+solve(mid+1,r,L,pos_{mid})

var countNegatives = function (grid) {
    return solve(0, grid.length - 1, 0, grid[0].length - 1, grid);

};
var solve = function (l, r, L, R, grid) {
    if (l > r) return 0
    let mid = l + ((r - l) >> 1), pos = -1
    for (let i = L; i <= R; ++i) {
        if (grid[mid][i] < 0) {
            pos = i;
            break;
        }
    }
    let ans=0;
    if (~pos){
            ans+=grid[0].length-pos;
            ans+=solve(l,mid-1,pos,R,grid);
            ans+=solve(mid+1,r,L,pos,grid);
        }
        else{// 说明[l..o-1]不会有负数,不用再去递归
            ans+=solve(mid+1,r,L,R,grid);
        }
        return ans;
}

图片.png