题目描述 leetcode 困难题
给你一个 m x n 的矩阵 matrix ,请你返回一个新的矩阵 answer ,其中 answer[row][col] 是 matrix[row][col] 的秩。
每个元素的 秩 是一个整数,表示这个元素相对于其他元素的大小关系,它按照如下规则计算:
秩是从 1 开始的一个整数。 如果两个元素 p 和 q 在 同一行 或者 同一列 ,那么: 如果 p < q ,那么 rank(p) < rank(q) 如果 p == q ,那么 rank(p) == rank(q) 如果 p > q ,那么 rank(p) > rank(q) 秩 需要越 小 越好。 题目保证按照上面规则 answer 数组是唯一的。
示例1:
1 2 3 4 5 6 7 输入:matrix = 输出: 解释: matrix 的秩为 1 ,因为它是所在行和列的最小整数。 matrix 的秩为 2 ,因为 matrix > matrix 且 matrix 的秩为 1 。 matrix 的秩为 2 ,因为 matrix > matrix 且 matrix 的秩为 1 。 matrix 的秩为 3 ,因为 matrix > matrix, matrix > matrix 且 matrix 和 matrix 的秩都为 2 。
示例2:
提示1:
1 2 3 4 m == matrix .length n == matrix [i].length 1 <= m, n <= 500 -10 ^9 <= matrix [row ][col ] <= 10 ^9
两轮枚举 首先由于题意要求 秩需要越小越好。
,所以我们可以先按照值大小将元素分组后排序,然后贪心的从小到大遍历这些值进行秩的维护。
设 $colMax[i]、rowMax[i]$ 分别为第 $i$ 行、列当前最大值的下标,当前遍历到的值为 $cur$ , $cur$ 对应的点下标为 $cur_1,cur_2…cur_n$。
我们先遍历一轮 $cur$ 对应的点的集合,将这些点的秩更新为对应行/列最大秩加一,并更新 $colMax、rowMax$ 。
第一轮遍历结束后,若这些相同值对应的任意两点,均不在同行/同列,那么这些点的秩就正确了。
但如果这些点位于同行/同列时,就可能会产生错误,举例来说:设某个 $cur$ 对应的第一个点下标为 $[1][2]$ ,该行该列当前最大秩为 $4$ ,将其赋给 $[1][2]$ ,此时满足题意,但假如该值对应的下一个点为 $[1][4]$ ,而因为第四列最大秩已经到了 $7$,所以 $[1][4]$ 的秩为 $7$,那么先前 $[1][2]$ 点的秩就与 $[1][4]$ 点发生了冲突,两点位于同一行,值相同,秩却不同,实际上 $[1][2]$点的秩也应该为 $7$ 。
所以我们还要进行第二轮遍历,修正由于该情况带来的错误。容易发现这些点的最终秩,都依赖/等于对应行列的 $cur_i$ 点的最大秩。所以类似拓扑排序,我们可以从没有任何依赖(入边)的点开始处理,即最大的点,接着是该点的行列上的 $cur_i$ ,因为此时这些点的秩也变成了最大秩,也没有了依赖(入边),如此反复,直到这些点的行列上不存在 $cur_i$ ,再接着处理秩为第二大的点,直到所有点遍历结束即可。
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class Solution { public int [][] matrixRankTransform(int [][] matrix) { int n = matrix.length, m = matrix[0 ].length; Map<Integer, List<int []>> valueIndexMap = new TreeMap <>(); for (int i = 0 ; i < n; i++){ for (int j = 0 ; j < m; j++){ valueIndexMap.computeIfAbsent(matrix[i][j], key -> new ArrayList <>()) .add(new int []{i, j}); } } int [] colMax = new int [n]; int [] rowMax = new int [m]; int [][] ans = new int [n][m]; for (List<int []> indexList : valueIndexMap.values()){ Consumer<int []> fun = index -> { int curCol = matrix[index[0 ]][colMax[index[0 ]]] == matrix[index[0 ]][index[1 ]] && ans[index[0 ]][colMax[index[0 ]]] != 0 ? ans[index[0 ]][colMax[index[0 ]]] : ans[index[0 ]][colMax[index[0 ]]] + 1 ; int curRow = matrix[rowMax[index[1 ]]][index[1 ]] == matrix[index[0 ]][index[1 ]] && ans[rowMax[index[1 ]]][index[1 ]] != 0 ? ans[rowMax[index[1 ]]][index[1 ]] : ans[rowMax[index[1 ]]][index[1 ]] + 1 ; ans[index[0 ]][index[1 ]] = Math.max(curCol, curRow); colMax[index[0 ]] = index[1 ]; rowMax[index[1 ]] = index[0 ]; }; indexList.forEach(fun); Map<Integer, List<int []>> groupByCol = indexList.stream().collect(Collectors.groupingBy(index -> index[0 ])); Map<Integer, List<int []>> groupByRow = indexList.stream().collect(Collectors.groupingBy(index -> index[1 ])); int [] visitCol = new int [n]; int [] visitRow = new int [m]; Queue<int []> queue = new ArrayDeque <>(); List<int []> sortByAns = indexList.stream() .sorted((a, b) -> ans[b[0 ]][b[1 ]] - ans[a[0 ]][a[1 ]]).toList(); int queueCnt = 0 ; while (queueCnt < indexList.size()){ if (queue.isEmpty()){ while (queueCnt < indexList.size() && visitCol[sortByAns.get(queueCnt)[0 ]] == 1 && visitRow[sortByAns.get(queueCnt)[1 ]] == 1 ){ queueCnt++; } if (queueCnt >= indexList.size()){ break ; } int [] a = sortByAns.get(queueCnt++); queue.offer(a); } int [] item = queue.poll(); if (visitCol[item[0 ]] != 1 ){ groupByCol.get(item[0 ]).forEach(index -> { fun.accept(index); if (visitRow[index[1 ]] == 0 ){ queue.offer(index); } }); visitCol[item[0 ]] = 1 ; } if (visitRow[item[1 ]] != 1 ){ groupByRow.get(item[1 ]).forEach(index -> { fun.accept(index); if (visitCol[index[0 ]] == 0 ){ queue.offer(index); } }); visitRow[item[1 ]] = 1 ; } } } return ans; } }
并查集 上面解法的代码过于复杂,一种更自然的做法是使用并查集来维护元素间的依赖关系。
就如上面所说
容易发现这些点的最终秩,都依赖/等于对应行列的 $cur_i$ 点的最大秩
所以我们可以将这些相同值的同行/同列的点,进行连通,此时就能很容易的维护出一个连通块的秩,为该连通块所有点的秩。
稍微要注意的是,初始化一个并查集的时间复杂度为 $O(n + m)$ ,如果所有点的值均不相同,时间复杂度约为 $O(n \times m \times (n + m))$ ,所以我们可以只初始化一个并查集,而在对于某个值循环结束时,清空对应点的连通即可。
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class Solution { public int [][] matrixRankTransform(int [][] matrix) { int n = matrix.length, m = matrix[0 ].length; Map<Integer, List<int []>> valueIndexMap = new TreeMap <>(); for (int i = 0 ; i < n; i++){ for (int j = 0 ; j < m; j++){ valueIndexMap.computeIfAbsent(matrix[i][j], key -> new ArrayList <>()) .add(new int []{i, j}); } } int [] colMaxRank = new int [n]; int [] rowMaxRank = new int [m]; int [][] ans = new int [n][m]; UnionFind union = new UnionFind (n + m); for (List<int []> indexList : valueIndexMap.values()){ for (int [] index : indexList){ union.union(index[0 ], index[1 ] + n); } int [] rank = new int [n + m]; for (int [] index : indexList){ rank[union.find(index[0 ])] = Math.max(rank[union.find(index[0 ])], Math.max(colMaxRank[index[0 ]], rowMaxRank[index[1 ]])); } for (int [] index : indexList){ ans[index[0 ]][index[1 ]] = rank[union.find(index[0 ])] + 1 ; colMaxRank[index[0 ]] = ans[index[0 ]][index[1 ]]; rowMaxRank[index[1 ]] = ans[index[0 ]][index[1 ]]; } indexList.forEach(index -> { union.clear(index[0 ]); union.clear(index[1 ] + n); }); } return ans; } }class UnionFind { public int [] fa; public int [] size; private int n; public UnionFind (int n) { this .n = n; fa = IntStream.range(0 , n).toArray(); size = IntStream.generate(() -> 1 ).limit(n).toArray(); } public void union (int a, int b) { a = find(a); b = find(b); if (a == b){ return ; } if (size[a] < size[b]){ a = a ^ b; b = a ^ b; a = a ^ b; } fa[b] = a; size[a] += size[b]; } public int find (int a) { if (a != fa[a]){ fa[a] = find(fa[a]); } return fa[a]; } public void clear (int a) { fa[a] = a; size[a] = 1 ; } }