Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Given an m x n matrix, return a new matrix answer where answer[row][col] is the rank of matrix[row][col].
The rank is an integer that represents how large an element is compared to other elements. It is calculated using the following rules:
1.p and q are in the same row or column, then:
p < q then rank(p) < rank(q)p == q then rank(p) == rank(q)p > q then rank(p) > rank(q)The test cases are generated so that answer is unique under the given rules.
Example 1:
Input: matrix = [[1,2],[3,4]] Output: [[1,2],[2,3]] Explanation: The rank of matrix[0][0] is 1 because it is the smallest integer in its row and column. The rank of matrix[0][1] is 2 because matrix[0][1] > matrix[0][0] and matrix[0][0] is rank 1. The rank of matrix[1][0] is 2 because matrix[1][0] > matrix[0][0] and matrix[0][0] is rank 1. The rank of matrix[1][1] is 3 because matrix[1][1] > matrix[0][1], matrix[1][1] > matrix[1][0], and both matrix[0][1] and matrix[1][0] are rank 2.
Example 2:
Input: matrix = [[7,7],[7,7]] Output: [[1,1],[1,1]]
Example 3:
Input: matrix = [[20,-21,14],[-19,4,19],[22,-47,24],[-19,4,19]] Output: [[4,2,3],[1,3,4],[5,1,6],[1,3,4]]
Constraints:
m == matrix.lengthn == matrix[i].length1 <= m, n <= 500-109 <= matrix[row][col] <= 109Problem summary: Given an m x n matrix, return a new matrix answer where answer[row][col] is the rank of matrix[row][col]. The rank is an integer that represents how large an element is compared to other elements. It is calculated using the following rules: The rank is an integer starting from 1. If two elements p and q are in the same row or column, then: If p < q then rank(p) < rank(q) If p == q then rank(p) == rank(q) If p > q then rank(p) > rank(q) The rank should be as small as possible. The test cases are generated so that answer is unique under the given rules.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Union-Find · Topological Sort
[[1,2],[3,4]]
[[7,7],[7,7]]
[[20,-21,14],[-19,4,19],[22,-47,24],[-19,4,19]]
rank-transform-of-an-array)gcd-sort-of-an-array)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1632: Rank Transform of a Matrix
class UnionFind {
private int[] p;
private int[] size;
public UnionFind(int n) {
p = new int[n];
size = new int[n];
for (int i = 0; i < n; ++i) {
p[i] = i;
size[i] = 1;
}
}
public int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
public void union(int a, int b) {
int pa = find(a), pb = find(b);
if (pa != pb) {
if (size[pa] > size[pb]) {
p[pb] = pa;
size[pa] += size[pb];
} else {
p[pa] = pb;
size[pb] += size[pa];
}
}
}
public void reset(int x) {
p[x] = x;
size[x] = 1;
}
}
class Solution {
public int[][] matrixRankTransform(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
TreeMap<Integer, List<int[]>> d = new TreeMap<>();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
d.computeIfAbsent(matrix[i][j], k -> new ArrayList<>()).add(new int[] {i, j});
}
}
int[] rowMax = new int[m];
int[] colMax = new int[n];
int[][] ans = new int[m][n];
UnionFind uf = new UnionFind(m + n);
int[] rank = new int[m + n];
for (var ps : d.values()) {
for (var p : ps) {
uf.union(p[0], p[1] + m);
}
for (var p : ps) {
int i = p[0], j = p[1];
rank[uf.find(i)] = Math.max(rank[uf.find(i)], Math.max(rowMax[i], colMax[j]));
}
for (var p : ps) {
int i = p[0], j = p[1];
ans[i][j] = 1 + rank[uf.find(i)];
rowMax[i] = ans[i][j];
colMax[j] = ans[i][j];
}
for (var p : ps) {
uf.reset(p[0]);
uf.reset(p[1] + m);
}
}
return ans;
}
}
// Accepted solution for LeetCode #1632: Rank Transform of a Matrix
type unionFind struct {
p, size []int
}
func newUnionFind(n int) *unionFind {
p := make([]int, n)
size := make([]int, n)
for i := range p {
p[i] = i
size[i] = 1
}
return &unionFind{p, size}
}
func (uf *unionFind) find(x int) int {
if uf.p[x] != x {
uf.p[x] = uf.find(uf.p[x])
}
return uf.p[x]
}
func (uf *unionFind) union(a, b int) {
pa, pb := uf.find(a), uf.find(b)
if pa != pb {
if uf.size[pa] > uf.size[pb] {
uf.p[pb] = pa
uf.size[pa] += uf.size[pb]
} else {
uf.p[pa] = pb
uf.size[pb] += uf.size[pa]
}
}
}
func (uf *unionFind) reset(x int) {
uf.p[x] = x
uf.size[x] = 1
}
func matrixRankTransform(matrix [][]int) [][]int {
m, n := len(matrix), len(matrix[0])
type pair struct{ i, j int }
d := map[int][]pair{}
for i, row := range matrix {
for j, v := range row {
d[v] = append(d[v], pair{i, j})
}
}
rowMax := make([]int, m)
colMax := make([]int, n)
ans := make([][]int, m)
for i := range ans {
ans[i] = make([]int, n)
}
vs := []int{}
for v := range d {
vs = append(vs, v)
}
sort.Ints(vs)
uf := newUnionFind(m + n)
rank := make([]int, m+n)
for _, v := range vs {
ps := d[v]
for _, p := range ps {
uf.union(p.i, p.j+m)
}
for _, p := range ps {
i, j := p.i, p.j
rank[uf.find(i)] = max(rank[uf.find(i)], max(rowMax[i], colMax[j]))
}
for _, p := range ps {
i, j := p.i, p.j
ans[i][j] = 1 + rank[uf.find(i)]
rowMax[i], colMax[j] = ans[i][j], ans[i][j]
}
for _, p := range ps {
uf.reset(p.i)
uf.reset(p.j + m)
}
}
return ans
}
# Accepted solution for LeetCode #1632: Rank Transform of a Matrix
class UnionFind:
def __init__(self, n):
self.p = list(range(n))
self.size = [1] * n
def find(self, x):
if self.p[x] != x:
self.p[x] = self.find(self.p[x])
return self.p[x]
def union(self, a, b):
pa, pb = self.find(a), self.find(b)
if pa != pb:
if self.size[pa] > self.size[pb]:
self.p[pb] = pa
self.size[pa] += self.size[pb]
else:
self.p[pa] = pb
self.size[pb] += self.size[pa]
def reset(self, x):
self.p[x] = x
self.size[x] = 1
class Solution:
def matrixRankTransform(self, matrix: List[List[int]]) -> List[List[int]]:
m, n = len(matrix), len(matrix[0])
d = defaultdict(list)
for i, row in enumerate(matrix):
for j, v in enumerate(row):
d[v].append((i, j))
row_max = [0] * m
col_max = [0] * n
ans = [[0] * n for _ in range(m)]
uf = UnionFind(m + n)
for v in sorted(d):
rank = defaultdict(int)
for i, j in d[v]:
uf.union(i, j + m)
for i, j in d[v]:
rank[uf.find(i)] = max(rank[uf.find(i)], row_max[i], col_max[j])
for i, j in d[v]:
ans[i][j] = row_max[i] = col_max[j] = 1 + rank[uf.find(i)]
for i, j in d[v]:
uf.reset(i)
uf.reset(j + m)
return ans
// Accepted solution for LeetCode #1632: Rank Transform of a Matrix
/**
* [1632] Rank Transform of a Matrix
*
* Given an m x n matrix, return a new matrix answer where answer[row][col] is the rank of matrix[row][col].
* The rank is an integer that represents how large an element is compared to other elements. It is calculated using the following rules:
*
* The rank is an integer starting from 1.
* If two elements p and q are in the same row or column, then:
*
* If p < q then rank(p) < rank(q)
* If p == q then rank(p) == rank(q)
* If p > q then rank(p) > rank(q)
*
*
* The rank should be as small as possible.
*
* The test cases are generated so that answer is unique under the given rules.
*
* Example 1:
* <img alt="" src="https://assets.leetcode.com/uploads/2020/10/18/rank1.jpg" style="width: 442px; height: 162px;" />
* Input: matrix = [[1,2],[3,4]]
* Output: [[1,2],[2,3]]
* Explanation:
* The rank of matrix[0][0] is 1 because it is the smallest integer in its row and column.
* The rank of matrix[0][1] is 2 because matrix[0][1] > matrix[0][0] and matrix[0][0] is rank 1.
* The rank of matrix[1][0] is 2 because matrix[1][0] > matrix[0][0] and matrix[0][0] is rank 1.
* The rank of matrix[1][1] is 3 because matrix[1][1] > matrix[0][1], matrix[1][1] > matrix[1][0], and both matrix[0][1] and matrix[1][0] are rank 2.
*
* Example 2:
* <img alt="" src="https://assets.leetcode.com/uploads/2020/10/18/rank2.jpg" style="width: 442px; height: 162px;" />
* Input: matrix = [[7,7],[7,7]]
* Output: [[1,1],[1,1]]
*
* Example 3:
* <img alt="" src="https://assets.leetcode.com/uploads/2020/10/18/rank3.jpg" style="width: 601px; height: 322px;" />
* Input: matrix = [[20,-21,14],[-19,4,19],[22,-47,24],[-19,4,19]]
* Output: [[4,2,3],[1,3,4],[5,1,6],[1,3,4]]
*
*
* Constraints:
*
* m == matrix.length
* n == matrix[i].length
* 1 <= m, n <= 500
* -10^9 <= matrix[row][col] <= 10^9
*
*/
pub struct Solution {}
// problem: https://leetcode.com/problems/rank-transform-of-a-matrix/
// discuss: https://leetcode.com/problems/rank-transform-of-a-matrix/discuss/?currentPage=1&orderBy=most_votes&query=
// submission codes start here
// Credit: https://leetcode.com/problems/rank-transform-of-a-matrix/solutions/3183268/unionfind-solution/
struct UnionFind {
parent: std::collections::HashMap<i32, i32>,
}
impl UnionFind {
fn new() -> Self {
UnionFind {
parent: std::collections::HashMap::new(),
}
}
fn find(&mut self, u: i32) -> i32 {
let pu = *self.parent.entry(u).or_insert(u);
if pu == u {
return u;
}
let v = self.find(pu);
self.parent.insert(u, v);
v
}
fn union(&mut self, u: i32, v: i32) {
self.parent.entry(u).or_insert(u);
self.parent.entry(v).or_insert(v);
let pu = self.find(u);
let pv = self.find(v);
if pu != pv {
self.parent.insert(pu, pv);
}
}
fn get_groups(&mut self) -> std::collections::HashMap<i32, Vec<i32>> {
let mut groups = std::collections::HashMap::new();
let parent = self.parent.clone();
for (u, _) in parent {
let pu = self.find(u);
groups.entry(pu).or_insert(vec![]).push(u);
}
groups
}
}
impl Solution {
pub fn matrix_rank_transform(matrix: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
let mut matrix = matrix;
let (m, n) = (matrix.len(), matrix[0].len());
let mut group_by_value = std::collections::BTreeMap::new();
for (r, item_r) in matrix.iter().enumerate().take(m) {
for (c, &item_c) in item_r.iter().enumerate().take(n) {
group_by_value
.entry(item_c)
.or_insert(vec![])
.push((r as i32, c as i32));
}
}
let mut rank = vec![0; m + n];
for (_, cells) in group_by_value {
let mut uf = UnionFind::new();
for &(r, c) in cells.iter() {
uf.union(r, c + m as i32);
}
for (_, group) in uf.get_groups() {
let mut max_rank = 0;
for &i in group.iter() {
max_rank = max_rank.max(rank[i as usize]);
}
for &i in group.iter() {
rank[i as usize] = max_rank + 1;
}
}
for &(r, c) in cells.iter() {
matrix[r as usize][c as usize] = rank[r as usize];
}
}
matrix
}
}
// submission codes end
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_1632_example_1() {
let matrix = vec![vec![1, 2], vec![3, 4]];
let result = vec![vec![1, 2], vec![2, 3]];
assert_eq!(Solution::matrix_rank_transform(matrix), result);
}
#[test]
fn test_1632_example_2() {
let matrix = vec![vec![7, 7], vec![7, 7]];
let result = vec![vec![1, 1], vec![1, 1]];
assert_eq!(Solution::matrix_rank_transform(matrix), result);
}
#[test]
fn test_1632_example_3() {
let matrix = vec![
vec![20, -21, 14],
vec![-19, 4, 19],
vec![22, -47, 24],
vec![-19, 4, 19],
];
let result = vec![vec![4, 2, 3], vec![1, 3, 4], vec![5, 1, 6], vec![1, 3, 4]];
assert_eq!(Solution::matrix_rank_transform(matrix), result);
}
}
// Accepted solution for LeetCode #1632: Rank Transform of a Matrix
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #1632: Rank Transform of a Matrix
// class UnionFind {
// private int[] p;
// private int[] size;
//
// public UnionFind(int n) {
// p = new int[n];
// size = new int[n];
// for (int i = 0; i < n; ++i) {
// p[i] = i;
// size[i] = 1;
// }
// }
//
// public int find(int x) {
// if (p[x] != x) {
// p[x] = find(p[x]);
// }
// return p[x];
// }
//
// public void union(int a, int b) {
// int pa = find(a), pb = find(b);
// if (pa != pb) {
// if (size[pa] > size[pb]) {
// p[pb] = pa;
// size[pa] += size[pb];
// } else {
// p[pa] = pb;
// size[pb] += size[pa];
// }
// }
// }
//
// public void reset(int x) {
// p[x] = x;
// size[x] = 1;
// }
// }
//
// class Solution {
// public int[][] matrixRankTransform(int[][] matrix) {
// int m = matrix.length, n = matrix[0].length;
// TreeMap<Integer, List<int[]>> d = new TreeMap<>();
// for (int i = 0; i < m; ++i) {
// for (int j = 0; j < n; ++j) {
// d.computeIfAbsent(matrix[i][j], k -> new ArrayList<>()).add(new int[] {i, j});
// }
// }
// int[] rowMax = new int[m];
// int[] colMax = new int[n];
// int[][] ans = new int[m][n];
// UnionFind uf = new UnionFind(m + n);
// int[] rank = new int[m + n];
// for (var ps : d.values()) {
// for (var p : ps) {
// uf.union(p[0], p[1] + m);
// }
// for (var p : ps) {
// int i = p[0], j = p[1];
// rank[uf.find(i)] = Math.max(rank[uf.find(i)], Math.max(rowMax[i], colMax[j]));
// }
// for (var p : ps) {
// int i = p[0], j = p[1];
// ans[i][j] = 1 + rank[uf.find(i)];
// rowMax[i] = ans[i][j];
// colMax[j] = ans[i][j];
// }
// for (var p : ps) {
// uf.reset(p[0]);
// uf.reset(p[1] + m);
// }
// }
// return ans;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Track components with a list or adjacency matrix. Each union operation may need to update all n elements’ component labels, giving O(n) per union. For n union operations total: O(n²). Find is O(1) with direct lookup, but union dominates.
With path compression and union by rank, each find/union operation takes O(α(n)) amortized time, where α is the inverse Ackermann function — effectively constant. Space is O(n) for the parent and rank arrays. For m operations on n elements: O(m × α(n)) total.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.