一起养成写作习惯!这是我参与「掘金日新计划 · 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.lengthn == grid[i].length1 <= m, n <= 100-100 <= grid[i][j] <= 100
二分查找
注意到题目中给了一个性质,即矩阵中的元素无论是按行还是按列,都以非递增顺序排列,可以考虑把这个性质利用起来优化暴力。已知这个性质告诉了我们每一行的数都是有序的,所以我们通过二分查找可以找到每一行中从前往后的第一个负数,那么这个位置之后到这一行的末尾里所有的数必然是负数了,可以直接统计。
- 遍历矩阵的每一行。
- 二分查找到该行从前往后的第一个负数,考虑第 行,我们记这个位置为 ,那么第 行 中的所有数都是负数,所以这一行对答案的贡献就是 。
- 最后的答案就是 。
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
};
分治
方法二其实只利用了一部分的性质,即每一行是非递增的,但其实整个矩阵是每行每列均非递增,这说明了一个更重要的性质:每一行从前往后第一个负数的位置是不断递减的,即我们设第 行的第一个负数的位置为 ,不失一般性,我们把一行全是正数的 设为 ,则
所以我们可以依此设计一个分治算法。
我们设计一个函数 表示我们在统计 行的答案,第 行 的位置在 列中,计算 的中间行第 行的的 ,算完以后根据之前的方法计算这一行对答案的贡献。然后根据我们之前发现的性质,可以知道 中所有行的 是大于等于 , 中所有行的 值是小于等于 的,所以可以分成两部分递归下去,即:
和
所以答案就是 。
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;
}