每日一刷经验分享:1252简单. 奇数值单元格的数目

130 阅读4分钟

1252简单. 奇数值单元格的数目

题意

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

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

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

ri 行上的所有单元格,加 1 。 ci 列上的所有单元格,加 1 。 给你 m、n 和 indices 。请你在执行完所有 indices 指定的增量操作后,返回矩阵中 奇数值单元格 的数目。

🥖示例 1:

image.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:

image.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) 额外空间的算法来解决此问题吗?

因为这道题看一眼就知道暴力能解决,所以为了提升难度,直接上进阶版,控制时间和空间范围。

AC代码

👀Java版本:这个代码版本和思路也是经过推敲出来的

class Solution {
    public int oddCells(int m, int n, int[][] indices) {
        int[] rows= new int[m];
        int[] cols = new int[n];
        int len = indices.length;
        for(int i=0;i<len;i++){
           rows[indices[i][0]]++;
           cols[indices[i][1]]++;
        }
        int row=0,col=0;
        for(int i=0;i<m;i++) if((rows[i]&1)==1) row++;
        for(int j=0;j<n;j++) if((cols[j]&1)==1) col++;
        return (n-col)*row+(m-row)*col;
    }
}

👀C++版本:

class Solution {
public:
    int oddCells(int n, int m, vector<vector<int>>& indices) {
        int ret = 0;
        std::vector<int> rows(n),cols(m);
        for(auto &idx : indices) 
            ++rows[idx[0]],++cols[idx[1]];
        
        for(auto &row : rows){
            for(auto &col : cols)
                ret += ((row+col) & 1);
        }
        
        return ret;
    }
};

分析

🍚暴力版思路:

新建一个m行n列的二维矩阵,然后遍历indeces,把对应的每一行和每一列上的元素进行自加,最后再遍历二维数组,查找数值为奇数的即可,或者为了降低内存消耗量,可以把整型数组换成boolean数组,每次rec[i][j]=!rec[i][j]取反即可,最后判断rec[i][j]为true的点的个数即可,如果数据小的话,直接暴力应该是没问题的,如果数据量较大的话,可能就会出现超时的问题或者OOM的问题。

🍖进阶版思路:

按照进阶版的要求,内存占用为m+n,所以我们定义了一个m行的一维数组和n列的一维数组,用于记录每次变化的时候对应的行数和列数变化量,为什么要用这两个数组一会用到,遍历indeces数组的时候,我们需要记录影响的行数和列数,遍历完之后,分别查看影响的行数和列数,是否因为收到影响为奇数的,因为如果受到影响是偶数的,不会对原数组有影响,分别站在行的方面和列的方面记录受影响的个数,然后用这个公式=(总列数-受影响的列数)*受影响的行数+(总行数-受影响的行数)*受影响的列数,为什么要减去,因为站在行的角度,如果列的变化为奇数次,会抵消基数词变化的行数。

题解过程分析

  1. int[] rows= new int[m]; //定义一个记录影响的行数的数组
  2. int[] cols = new int[n]; //定义一个记录影响的列数的数组
  3. for(int i=0;i<len;i++){ rows[indices[i][0]]++; cols[indices[i][1]]++; } 遍历indeces的数组,分别记录影响的行数和列数。
  4. for(int i=0;i<m;i++) if((rows[i]&1)==1) row++; 记录受影响为奇数次的行数。
  5. for(int j=0;j<n;j++) if((cols[j]&1)==1) col++; 记录受影响为奇数次的列数。
  6. return (n-col)*row+(m-row)*col; 调用上边分析的公式。

图解过程

  1. 初始影响的行数和列数

image.png

  1. 筛选出受影响为奇数和偶数的行和列

image.png

  1. 站在行的角度

image.png

  1. 站在列的角度

image.png

所以公式就是根据上述推导出来的。

复杂度分析

  • 时间复杂度: O(n + m + indices.length) 
  • 空间复杂度: O(n + m)

总结

总体来说暴力就能解决的问题,仔细探索还能找到更精简的方法,就是是用公式去解决。 提交结果分析:时间上没问题了,其实我能改进的就是上边的数组。

  1. 改进点1: 可以使用无符号整型,因为所有的数组都是大于0的
  2. 改进点2: 还可以使用Map的键值对的格式,因为影响的行和列不是所有的都会受影响,所以用整型数组很多无用的空间被浪费。
  3. 改进点3: 把整形数组改为boolean数组,减少存储的容量,只要记录受影响的行数和列数即可,我这里就不再给出改进的代码,可以自己动手实现一下。

image.png

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿