Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1632. Rank Transform of a Matrix - Leetcode Solution

Code Implementation

from collections import defaultdict

class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)

class Solution:
    def matrixRankTransform(self, matrix):
        m, n = len(matrix), len(matrix[0])
        answer = [[0]*n for _ in range(m)]
        value_to_positions = defaultdict(list)
        for i in range(m):
            for j in range(n):
                value_to_positions[matrix[i][j]].append((i, j))
        rank = [0] * (m + n)
        for value in sorted(value_to_positions):
            dsu = DSU(m + n)
            for i, j in value_to_positions[value]:
                dsu.union(i, j + m)
            group_max_rank = {}
            for i, j in value_to_positions[value]:
                root = dsu.find(i)
                group_max_rank[root] = max(group_max_rank.get(root, 0), rank[i], rank[j + m])
            for i, j in value_to_positions[value]:
                root = dsu.find(i)
                answer[i][j] = group_max_rank[root] + 1
            for i, j in value_to_positions[value]:
                rank[i] = answer[i][j]
                rank[j + m] = answer[i][j]
        return answer
      
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

class DSU {
public:
    vector<int> parent;
    DSU(int n) : parent(n) {
        for (int i = 0; i < n; ++i) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        parent[find(x)] = find(y);
    }
};

class Solution {
public:
    vector<vector<int>> matrixRankTransform(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> answer(m, vector<int>(n, 0));
        map<int, vector<pair<int,int>>> value_to_positions;
        for (int i = 0; i < m; ++i)
            for (int j = 0; j < n; ++j)
                value_to_positions[matrix[i][j]].emplace_back(i, j);
        vector<int> rank(m + n, 0);
        for (auto& [value, positions] : value_to_positions) {
            DSU dsu(m + n);
            for (auto& [i, j] : positions)
                dsu.unite(i, j + m);
            map<int, int> group_max_rank;
            for (auto& [i, j] : positions) {
                int root = dsu.find(i);
                group_max_rank[root] = max(group_max_rank[root], max(rank[i], rank[j + m]));
            }
            for (auto& [i, j] : positions) {
                int root = dsu.find(i);
                answer[i][j] = group_max_rank[root] + 1;
            }
            for (auto& [i, j] : positions) {
                rank[i] = answer[i][j];
                rank[j + m] = answer[i][j];
            }
        }
        return answer;
    }
};
      
import java.util.*;

class DSU {
    int[] parent;
    DSU(int n) {
        parent = new int[n];
        for (int i = 0; i < n; ++i) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void union(int x, int y) {
        parent[find(x)] = find(y);
    }
}

class Solution {
    public int[][] matrixRankTransform(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        int[][] answer = new int[m][n];
        Map<Integer, List<int[]>> valueToPositions = new TreeMap<>();
        for (int i = 0; i < m; ++i)
            for (int j = 0; j < n; ++j)
                valueToPositions.computeIfAbsent(matrix[i][j], k -> new ArrayList<>()).add(new int[]{i, j});
        int[] rank = new int[m + n];
        for (int value : valueToPositions.keySet()) {
            DSU dsu = new DSU(m + n);
            for (int[] pos : valueToPositions.get(value))
                dsu.union(pos[0], pos[1] + m);
            Map<Integer, Integer> groupMaxRank = new HashMap<>();
            for (int[] pos : valueToPositions.get(value)) {
                int root = dsu.find(pos[0]);
                groupMaxRank.put(root, Math.max(groupMaxRank.getOrDefault(root, 0), Math.max(rank[pos[0]], rank[pos[1] + m])));
            }
            for (int[] pos : valueToPositions.get(value)) {
                int root = dsu.find(pos[0]);
                answer[pos[0]][pos[1]] = groupMaxRank.get(root) + 1;
            }
            for (int[] pos : valueToPositions.get(value)) {
                rank[pos[0]] = answer[pos[0]][pos[1]];
                rank[pos[1] + m] = answer[pos[0]][pos[1]];
            }
        }
        return answer;
    }
}
      
class DSU {
    constructor(n) {
        this.parent = Array.from({length: n}, (_, i) => i);
    }
    find(x) {
        if (this.parent[x] !== x) this.parent[x] = this.find(this.parent[x]);
        return this.parent[x];
    }
    union(x, y) {
        this.parent[this.find(x)] = this.find(y);
    }
}

var matrixRankTransform = function(matrix) {
    const m = matrix.length, n = matrix[0].length;
    const answer = Array.from({length: m}, () => Array(n).fill(0));
    const valueToPositions = new Map();
    for (let i = 0; i < m; ++i)
        for (let j = 0; j < n; ++j) {
            if (!valueToPositions.has(matrix[i][j])) valueToPositions.set(matrix[i][j], []);
            valueToPositions.get(matrix[i][j]).push([i, j]);
        }
    let rank = Array(m + n).fill(0);
    const sortedValues = Array.from(valueToPositions.keys()).sort((a, b) => a - b);
    for (const value of sortedValues) {
        const dsu = new DSU(m + n);
        for (const [i, j] of valueToPositions.get(value))
            dsu.union(i, j + m);
        const groupMaxRank = {};
        for (const [i, j] of valueToPositions.get(value)) {
            const root = dsu.find(i);
            groupMaxRank[root] = Math.max(groupMaxRank[root] || 0, rank[i], rank[j + m]);
        }
        for (const [i, j] of valueToPositions.get(value)) {
            const root = dsu.find(i);
            answer[i][j] = groupMaxRank[root] + 1;
        }
        for (const [i, j] of valueToPositions.get(value)) {
            rank[i] = answer[i][j];
            rank[j + m] = answer[i][j];
        }
    }
    return answer;
};
      

Problem Description

Given an integer matrix matrix of size m x n, your task is to transform the matrix so that each element is replaced by its rank. The rank of an element is defined as follows:

  • The rank is an integer starting from 1.
  • If two elements are in the same row or column and one is strictly smaller than the other, then the rank of the smaller element must be strictly less than the rank of the larger element.
  • The rank should be as small as possible while satisfying the above constraints.
  • Elements with the same value that are in the same row or column are considered to be connected and must be assigned the same rank.

The output is a new matrix of the same size, where each position contains the computed rank for the corresponding element of the input.

Thought Process

The problem at first glance may seem similar to sorting or ranking elements, but the twist is that the rank must respect both the row and column dependencies, and identical values connected in a row or column must have the same rank.

A brute-force approach would be to repeatedly scan the matrix, updating ranks whenever a constraint is violated, but this would be very inefficient. Instead, we need to process the elements in value order, ensuring that smaller values are ranked before larger ones, and that all constraints are maintained.

The main challenge is managing the dependencies between rows and columns, especially for duplicate values. Using a data structure that can efficiently group connected elements with the same value (like Union-Find/Disjoint Set Union) is key for handling this efficiently.

Solution Approach

To solve the problem efficiently, we use the following steps:

  1. Group by Value: First, group all positions in the matrix by their value. We'll process values in increasing order so that ranks for smaller values are set before larger ones.
  2. Union-Find for Connections: For each value, use a Disjoint Set Union (DSU) to group all positions with the same value that are in the same row or column. This ensures that all such positions get the same rank.
  3. Compute Rank: For each group of connected positions, determine the maximum rank among all their rows and columns (from previous steps), and set the rank for the current group as one more than this maximum.
  4. Update Ranks: Update the row and column ranks for all positions in the current group so that future, larger values in the same row/column get the correct minimum possible rank.
  5. Repeat: Move to the next larger value and repeat the process.

By using a DSU, we efficiently manage connections between duplicate values, and by processing in value order, we guarantee that all constraints are met.

Example Walkthrough

Consider the matrix:

    1 2
    3 4
  
  • We process 1 first. It is at (0,0). No smaller value, so rank is 1.
  • Next, 2 at (0,1). The largest rank in its row/column so far is 1 (from 1), so its rank is 2.
  • Then, 3 at (1,0). The largest rank in its row/column so far is 1 (from 1), so its rank is 2.
  • Finally, 4 at (1,1). Its row and column both have rank 2 (from 2 and 3), so its rank is 3.

The resulting matrix is:

    1 2
    2 3
  

If there are duplicate values in the same row or column, they are grouped and assigned the same rank.

Time and Space Complexity

  • Brute-force approach: Would need to repeatedly scan and update the matrix, resulting in O((mn)^2) or worse, which is not practical for large matrices.
  • Optimized approach:
    • Grouping and sorting by value: O(mn log(mn)), since we sort all matrix elements.
    • Union-Find operations: Nearly O(1) per operation (amortized), and we do at most O(mn) of them.
    • Overall time: O(mn log(mn)), dominated by the sorting step.
    • Space: O(mn) for storing groups and ranks.

Summary

The Rank Transform of a Matrix problem is solved efficiently by processing elements in value order, using Union-Find to group duplicates, and carefully tracking the maximum rank in each row and column. This approach ensures all constraints are met and achieves optimal time complexity. The elegance comes from combining sorting, grouping, and DSU to handle all dependencies succinctly.