2711.对角线上不同值的数量差:O(mn)时间O(1)空间 - 位运算优化 - C++/Go双百版本 - 三种方法(一步步优化)

【LetMeFly】2711.对角线上不同值的数量差:O(mn)时间O(1)空间 - 位运算优化 - C++/Go双百版本 - 三种方法(一步步优化)

力扣题目链接:https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals/

给你一个下标从 0 开始、大小为 m x n 的二维矩阵 grid ,请你求解大小同样为 m x n 的答案矩阵 answer

矩阵 answer 中每个单元格 (r, c) 的值可以按下述方式进行计算:

  • topLeft[r][c] 为矩阵 grid 中单元格 (r, c) 左上角对角线上 不同值 的数量。
  • bottomRight[r][c] 为矩阵 grid 中单元格 (r, c) 右下角对角线上 不同值 的数量。

然后 answer[r][c] = |topLeft[r][c] - bottomRight[r][c]|

返回矩阵 answer

矩阵对角线 是从最顶行或最左列的某个单元格开始,向右下方向走到矩阵末尾的对角线。

如果单元格 (r1, c1) 和单元格 (r, c) 属于同一条对角线且 r1 < r ,则单元格 (r1, c1) 属于单元格 (r, c) 的左上对角线。类似地,可以定义右下对角线。

 

示例 1:

输入:grid = [[1,2,3],[3,1,5],[3,2,1]]
输出:[[1,1,0],[1,0,1],[0,1,1]]
解释:第 1 个图表示最初的矩阵 grid 。 
第 2 个图表示对单元格 (0,0) 计算,其中蓝色单元格是位于右下对角线的单元格。
第 3 个图表示对单元格 (1,2) 计算,其中红色单元格是位于左上对角线的单元格。
第 4 个图表示对单元格 (1,1) 计算,其中蓝色单元格是位于右下对角线的单元格,红色单元格是位于左上对角线的单元格。
- 单元格 (0,0) 的右下对角线包含 [1,1] ,而左上对角线包含 [] 。对应答案是 |1 - 0| = 1 。
- 单元格 (1,2) 的右下对角线包含 [] ,而左上对角线包含 [2] 。对应答案是 |0 - 1| = 1 。
- 单元格 (1,1) 的右下对角线包含 [1] ,而左上对角线包含 [1] 。对应答案是 |1 - 1| = 0 。
其他单元格的对应答案也可以按照这样的流程进行计算。

示例 2:

输入:grid = [[1]]
输出:[[0]]
解释:- 单元格 (0,0) 的右下对角线包含 [] ,左上对角线包含 [] 。对应答案是 |0 - 0| = 0 。

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n, grid[i][j] <= 50

挺不错的一道题。

解题方法一:暴力模拟

使用$O(mn)$的时间复杂度枚举每个起点,对于每个起点:

使用两个哈希表(集合),分别统计这个起点左上角和右下角有多少个不同的元素。

怎么统计呢?从$k=1$开始,到$i + d$或$j + d$超出合法边界为止,直接将$grid[i + d][j + d]$插入到哈希表中。

知道了每个起点左上角和右下角分别有多少个不同的元素,就知道答案了。

  • 时间复杂度$O(mn\times \min(m, n))$
  • 空间复杂度$O(\min(m, n))$

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* @Author: LetMeFly
* @Date: 2025-03-25 16:52:02
* @LastEditors: LetMeFly.xyz
* @LastEditTime: 2025-03-25 17:42:27
*/
class Solution {
public:
vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {
vector<vector<int>> ans(grid.size(), vector<int>(grid[0].size()));
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[i].size(); j++) {
unordered_set<int> l, r;
for (int k = 1; i - k >= 0 && j - k >= 0; k++) {
l.insert(grid[i - k][j - k]);
}
for (int k = 1; i + k < grid.size() && j + k < grid[0].size(); k++) {
r.insert(grid[i + k][j + k]);
}
ans[i][j] = abs(int(l.size() - r.size()));
}
}
return ans;
}
};

解题方法二:前缀和优化

方法一中我们需要从每个起点开始往两个方向遍历,但其实很多数被统计了很多次。

怎么优化呢?前缀和呗。从第一行或第一列的共$m + n - 1$个元素开始向右下角遍历,遍历过程中使用哈希表统计当前一共遍历到了多少个元素,并直接将结果写入$ans[i][j]$中。

也就是说,当前$ans[i][j]$代表$grid[i][j]$左上角的那条线一共有多少种不同的元素。

当从左上角遍历到右下角后,清空哈希表,再次向左上角折回,并在折回过程中统计右下角一共有多少种元素。

左上角元素的种类数减去右下角元素的种类数即为当前位置对应的答案。

  • 时间复杂度$O(mn)$
  • 空间复杂度$O(\min(m, n))$

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
* @Author: LetMeFly
* @Date: 2025-03-25 19:44:55
* @LastEditors: LetMeFly.xyz
* @LastEditTime: 2025-03-25 21:04:00
*/
class Solution {
public:
vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
vector<vector<int>> ans(n, vector<int>(m));
for (int k = 0; k < m + n - 1; k++) {
int i = k < m ? 0 : k - m + 1, j = k < m ? k : 0;
unordered_set<int> se;
int d = 0;
for (; i + d < n && j + d < m; d++) {
ans[i + d][j + d] = se.size();
se.insert(grid[i + d][j + d]);
}
se.clear();
for (d--; d >= 0; d--) {
ans[i + d][j + d] = abs(int(ans[i + d][j + d] - se.size()));
se.insert(grid[i + d][j + d]);
}
}
return ans;
}
};

解题方法三:位运算优化

很容易注意到,$grid[i][j]$的范围是$1$到$50$,因此我们可以直接使用64位整数的每一位表示每个数值是否出现过

这样,加上将临时结果存放在答案数组中,我们就做到了$O(1)$额外空间得出答案了。

  • 时间复杂度$O(mn)$
  • 空间复杂度$O(1)$,力扣返回值不记入算法空间复杂度

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* @Author: LetMeFly
* @Date: 2025-03-25 21:06:53
* @LastEditors: LetMeFly.xyz
* @LastEditTime: 2025-03-25 21:17:48
* @Description: AC,100.00%,100.00%
*/
typedef long long ll;


class Solution {
public:
vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
vector<vector<int>> ans(n, vector<int>(m));
for (int k = 0; k < m + n - 1; k++) {
int i = k < m ? 0 : k - m + 1, j = k < m ? k : 0;
ll se = 0;
int d = 0;
for (; i + d < n && j + d < m; d++) {
ans[i + d][j + d] = __builtin_popcountll(se);
se |= 1LL << grid[i + d][j + d];
}
se = 0;
for (d--; d >= 0; d--) {
ans[i + d][j + d] = abs(ans[i + d][j + d] - __builtin_popcountll(se));
se |= 1LL << grid[i + d][j + d];
}
}
return ans;
}
};
  • 执行用时分布3ms,击败100.00%
  • 消耗内存分布28.95MB,击败100.00%

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'''
Author: LetMeFly
Date: 2025-03-25 21:18:16
LastEditors: LetMeFly.xyz
LastEditTime: 2025-03-25 21:23:09
'''
from typing import List

class Solution:
def differenceOfDistinctValues(self, grid: List[List[int]]) -> List[List[int]]:
n, m = len(grid), len(grid[0])
ans = [[0] * m for _ in range(n)]
for k in range(m + n - 1):
i = 0 if k < m else k - m + 1
j = k if k < m else 0
se = d = 0
while i + d < n and j + d < m:
ans[i + d][j + d] = se.bit_count() # python10才有
se |= 1 << grid[i + d][j + d]
d += 1
se = 0
d -= 1
while d >= 0:
ans[i + d][j + d] = abs(ans[i + d][j + d] - se.bit_count())
se |= 1 << grid[i + d][j + d]
d -= 1
return ans

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
* @Author: LetMeFly
* @Date: 2025-03-25 21:23:51
* @LastEditors: LetMeFly.xyz
* @LastEditTime: 2025-03-25 21:28:30
*/
class Solution {
public int[][] differenceOfDistinctValues(int[][] grid) {
int n = grid.length, m = grid[0].length;
int[][] ans = new int[n][m];
for (int k = 0; k < m + n - 1; k++) {
int i = k < m ? 0 : k - m + 1;
int j = k < m ? k : 0;
long se = 0;
int d = 0;
for (; i + d < n && j + d < m; d++) {
ans[i + d][j + d] = Long.bitCount(se);
se |= 1L << grid[i + d][j + d];
}
se = 0;
for (d--; d >= 0; d--) {
ans[i + d][j + d] = Math.abs(ans[i + d][j + d] - Long.bitCount(se));
se |= 1L << grid[i + d][j + d];
}
}
return ans;
}
}

Go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* @Author: LetMeFly
* @Date: 2025-03-25 21:29:42
* @LastEditors: LetMeFly.xyz
* @LastEditTime: 2025-03-25 21:37:43
*/
package main

import "math/bits"

func abs2711(a int) int {
if a < 0 {
return -a
}
return a
}

func differenceOfDistinctValues(grid [][]int) [][]int {
n, m := len(grid), len(grid[0])
ans := make([][]int, n)
for i := range ans {
ans[i] = make([]int, m)
}
for k := 0; k < m + n - 1; k++ {
var i, j int
if k < m {
i, j = 0, k
} else {
i, j = k - m + 1, 0
}
se := uint64(0)
d := 0
for ; i + d < n && j + d < m; d++ {
ans[i + d][j + d] = bits.OnesCount64(se)
se |= uint64(1) << grid[i + d][j + d]
}
se = 0
for d--; d >= 0; d-- {
ans[i + d][j + d] = abs2711(ans[i + d][j + d] - bits.OnesCount64(se))
se |= uint64(1) << grid[i + d][j + d]
}
}
return ans
}
  • 执行用时分布0ms,击败100.00%
  • 消耗内存分布8.31MB,击败100.00%

End

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源


2711.对角线上不同值的数量差:O(mn)时间O(1)空间 - 位运算优化 - C++/Go双百版本 - 三种方法(一步步优化)
https://blog.letmefly.xyz/2025/03/25/LeetCode 2711.对角线上不同值的数量差/
作者
发布于
2025年3月25日
许可协议