图解LeetCode——1252. 奇数值单元格的数目(难度:简单)

一、题目

给你一个 m*n 的矩阵,最开始的时候,每个单元格中的值都是0

另有一个二维索引数组 indicesindices[i] = [ri, ci] 指向矩阵中的某个位置,其中 ri 和 ci 分别表示指定的行和列(从 0 开始编号)。

对 indices[i] 所指向的每个位置,应同时执行下述增量操作:

ri 行上的所有单元格,加 1 。
ci 列上的所有单元格,加 1 。

给你 mn 和 indices 。请你在执行完所有 indices 指定的增量操作后,返回矩阵中 奇数值单元格 的数目。

二、示例

2.1> 示例1:

6c89c6eb54262844088a307af19084b0.png

【输入】m = 2, n = 3, indices = [[0,1],[1,1]]
【输出】6
【解释】最开始的矩阵是 [[0,0,0],[0,0,0]]。第一次增量操作后得到 [[1,2,1],[0,1,0]]。最后的矩阵是 [[1,3,1],[1,3,1]],里面有 6 个奇数。

2.2> 示例2:

67295e29d0c414462e8e7d39638d4768.png

【输入】m = 2, n = 2, indices = [[1,1],[0,0]]
【输出】0
【解释】最后的矩阵是 [[2,2],[2,2]],里面没有奇数。

提示:

1 <= m, n <= 50
1 <= indices.length <= 100
0 <= ri < m
0 <= ci < n

进阶:

你可以设计一个时间复杂度为 O(n + m + indices.length) 且仅用 O(n + m) 额外空间的算法来解决此问题吗?

三、解题思路

3.1> 解法1:对矩阵中元素做奇偶打标识

具体思路是,每次操作如果影响了矩阵某个元素的值时,为了作为记录,将该元素的坐标(x, y)作为key,将该元素的具体值作为value,保存到map结构中,由于最终结果是要查看奇数的个数,所以,在变更元素值的时候,一同变更奇数值总个数result这个值。

变更逻辑:因为矩阵中每个元素数值value的初始值是0,即:是偶数。所以result初始值等于0。那么当第一次执行value+1的时候,因为value值是奇数值,所以result执行加1;如果第二次执行value+1的时候,由于value值变为了偶数值,所以result同时执行减1操作。

当所有操作都执行完毕后,result值就是最终的奇数单元格个数。具体逻辑如下图所示:

d009ba8379ea6be7147e927d521b3a3e.png

这种算法的优点就是,思维逻辑比较直观,很容易第一时间想到的解法。但是它的缺点也很明显,因为题目中只是要求出奇数单元的个数,而不需要知道每个元素中具体的数值,所以这种解法无论是空间还是时间上都不是最优的。解法1的具体实现请参照4.1> 实现1:对矩阵中元素做奇偶打标识

3.2> 解法2:根据奇偶列进行计数

既然只是获取奇数单元格的个数,那么我们试图去寻找一下“奇数单元格”的规律。一个单元格是由行和列组成的。那么,indice的操作方式也是先把某一行的所有元素值都加1,然后再把某一列的所有元素值都加1。那既然是这样操作的,我们就能找到一个奇数单元格的规律——就是行和列不能同时是奇数或者偶数,也就是说行列的奇偶性应该是有差异性的,这样这个单元格(或元素)的值才会是奇数的。具体逻辑如下图所示:

37cf4bb855c43e7b2dd80b05c9eab139.png

那么,我们根据推导出来的公司来计算一下矩阵为m=3,n=5,indices = [[0,1],[1,1]],操作完毕之后,有多少奇数单元格。具体操作如下所示:

bacf7a90ed7f7591a42e6623c3c0ab98.png

该解法通过我们推导出来的公式,可以不再需要解法1中的map去存储每个单元格或元素的坐标与具体值了。只是通过行列奇偶就可以计算出来技术单元格。执行速度也快了很多。解法2的具体实现请参照4.2> 实现2:根据奇偶列进行计数

四、代码实现

4.1> 实现1:对矩阵中元素做奇偶打标识

public int result = 0;

public int oddCells_slow(int m, int n, int[][] indices) {
    Map<String, Integer> map = new HashMap();
    for (int[] indice : indices) {
        compare(n, indice[0], true, map);
        compare(m, indice[1], false, map);
    }
    return result;
}

public void compare(int num, int indice, boolean reverse, Map<String, Integer> map) {
    for (int j = 0; j < num; j++) {
        String key = reverse ? (j + "," + indice) : indice + "," + j;
        Integer value = map.get(key);
        if (value == null) {
            result++;
            map.put(key, 1);
        } else {
            result = (value % 2 == 0) ? ++result : --result;
            map.put(key, ++value);
        }
    }
}

4.2> 实现2:根据奇偶列进行计数

public static int oddCells(int m, int n, int[][] indices) {
    boolean[] row = new boolean[m], column = new boolean[n];
    int rowNum = 0, columnNum = 0;
    for (int[] indice : indices) {
        rowNum += (row[indice[0]] = !row[indice[0]]) ? 1 : -1; // 计算【奇数行】的个数
        columnNum += (column[indice[1]] = !column[indice[1]]) ? 1 : -1; // 计算【奇数列】的个数
    }

    // 【奇】行数 * 【偶】列数 + 【奇】列数 * 【偶】行数
    return rowNum * (n - columnNum) + columnNum * (m - rowNum); 
}

今天的文章内容就这些了,最后一句话:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的点赞&分享。

更多技术干活,欢迎大家关注公众号“爪哇缪斯”(^o^)/~ 「干货分享,每周更新」

题目来源:力扣(LeetCode) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值