2661. First Completely Painted Row or Column

MediumArrayHash TableMatrix
Leetcode Link

Problem Description

Given a 0-indexed integer array arr and an m x n integer matrix mat, the task is to process arr and paint the cells in mat based on the sequence provided in arr. Both arr and mat contain all integers in the range [1, m * n]. The process involves iterating over arr and painting the cell in mat that corresponds to each integer arr[i]. The goal is to determine the smallest index i at which either a whole row or a whole column in the matrix mat is completely painted. In other words, find the earliest point at which there's either a full row or a full column with all cells painted in the matrix.

Intuition

The solution revolves around tracking the painting progress in mat as we process each element in arr. The core thought is that we don't need to modify the matrix mat itself but can instead track the number of painted cells in each row and column with auxiliary data structures. This leads us to the idea of using two arrays row and col to maintain the count of painted cells for each respective row and column.

Since we need to find the positions in the matrix mat that correspond to the integers in arr, we can set up a hash table idx to map each number in the matrix to its coordinate (i, j). This allows us to quickly access the correct row and column to increment our counts.

With this setup, we iterate through arr, using the hashed mapping to find where arr[k] should be painted in mat. Each time we increment the counts in row and col, we check if either of them has reached the size of their respective dimension (n for rows, m for columns). If they have, it means that a complete row or column has been painted, and we can return the current index k as the result. This approach leads to an efficient solution since it avoids the need to modify the matrix directly and leverages fast lookups and updates in the auxiliary arrays.

Solution Approach

The implementation consists of several key steps that use algorithms and data structures to efficiently solve the problem.

Firstly, we create a hash table named idx to store the position of each element in the matrix mat. For each element in mat, we make an entry in idx where the key is the element mat[i][j] and the value is a tuple (i, j) representing its row and column indices.

idx = {}
for i in range(m):
    for j in range(n):
        idx[mat[i][j]] = (i, j)

This hash table allows us to have constant-time lookups for the position of any element later when we traverse arr.

Next, we define two arrays, row and col, with lengths corresponding to the number of rows m and the number of columns n in the matrix mat. These arrays are used to count how many cells have been painted in each row and column, respectively.

row = [0] * m
col = [0] * n

The main part of the solution involves iterating through the array arr. For each element arr[k], we get its corresponding position (i, j) in the matrix mat using the hash table idx.

for k in range(len(arr)):
    i, j = idx[arr[k]]

We increment the counts in row[i] and col[j] since arr[k] marks the cell at (i, j) as painted.

row[i] += 1
col[j] += 1

Then, we check if we have completed painting a row or a column. In other words, if either row[i] equals the number of columns n or col[j] equals the number of rows m, we have found our solution. At that point, we can return the current index k as this is the first index where a row or column is completely painted.

if row[i] == n or col[j] == m:
    return k

This approach efficiently determines the solution by using a hash table for fast lookups and simple arrays for counting, thus avoiding the need to make any modifications to the matrix mat itself.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's illustrate the solution with a small example. Assume we are given the following matrix mat and array arr:

mat = [
    [1, 2],
    [3, 4]
]
arr = [3, 1, 4, 2]

The matrix mat is of size 2 x 2, so m = 2 and n = 2. Our goal is to paint the cells in mat in the order specified by arr and find the smallest index i at which either a whole row or a whole column is completely painted.

Step 1: Create a hash table idx mapping matrix values to coordinates:

idx = {1: (0, 0), 2: (0, 1), 3: (1, 0), 4: (1, 1)}

Step 2: Initialize counts for rows and columns:

row = [0, 0]
col = [0, 0]

Step 3: Start iterating through arr:

  • For arr[0] = 3, the position in mat is idx[3] = (1, 0). Increment row[1] and col[0]. Now row = [0, 1], col = [1, 0].
  • For arr[1] = 1, the position in mat is idx[1] = (0, 0). Increment row[0] and col[0]. Now row = [1, 1], col = [2, 0].

Since col[0] is now equal to the number of rows m, we've painted an entire column. The smallest index at this point is i = 1.

So, the earliest index in arr at which a whole row or column is painted is 1.

Solution Implementation

1class Solution:
2    def first_complete_index(self, sequence: List[int], matrix: List[List[int]]) -> int:
3        # Get the dimensions of the matrix.
4        rows_count, cols_count = len(matrix), len(matrix[0])
5      
6        # Create a dictionary to map each number to its position in the matrix.
7        number_position_index = {}
8        for row_index in range(rows_count):
9            for col_index in range(cols_count):
10                number = matrix[row_index][col_index]
11                number_position_index[number] = (row_index, col_index)
12      
13        # Initialize counters for each row and column.
14        row_counters = [0] * rows_count
15        col_counters = [0] * cols_count
16      
17        # Iterate through the sequence and update row and column counters.
18        for index, number in enumerate(sequence):
19            if number in number_position_index:
20                row_index, col_index = number_position_index[number]
21                row_counters[row_index] += 1
22                col_counters[col_index] += 1
23              
24                # Check if a row or a column is complete.
25                if row_counters[row_index] == cols_count or col_counters[col_index] == rows_count:
26                    return index
27      
28        # If no row or column has been completed, return -1 as a default case.
29        return -1
30
31# The List type needs to be imported from the typing module.
32from typing import List
33
34# This would then allow you to use the Solution class and its method.
35# Example usage:
36# solution_instance = Solution()
37# result = solution_instance.first_complete_index(sequence, matrix)
38
1class Solution {
2
3    /**
4     * Finds the first index in the input array where a complete row or column is found in the input matrix.
5     * @param arr Single-dimensional array of integers.
6     * @param mat Two-dimensional matrix of integers.
7     * @return The earliest index at which the input array causes a row or a column in the matrix to be filled.
8     */
9    public int firstCompleteIndex(int[] arr, int[][] mat) {
10        // Dimensions of the matrix
11        int rowCount = mat.length;
12        int colCount = mat[0].length;
13
14        // Mapping from the value to its coordinates in the matrix
15        Map<Integer, int[]> valueToIndexMap = new HashMap<>();
16        for (int row = 0; row < rowCount; ++row) {
17            for (int col = 0; col < colCount; ++col) {
18                valueToIndexMap.put(mat[row][col], new int[]{row, col});
19            }
20        }
21
22        // Arrays to keep track of the number of values found per row and column
23        int[] rowCompletion = new int[rowCount];
24        int[] colCompletion = new int[colCount];
25
26        // Iterate through the array `arr`
27        for (int k = 0; ; ++k) {
28            // Get the coordinates of the current array value in the matrix
29            int[] coordinates = valueToIndexMap.get(arr[k]);
30            int rowIndex = coordinates[0];
31            int colIndex = coordinates[1];
32
33            // Increment the counters for the row and column
34            rowCompletion[rowIndex]++;
35            colCompletion[colIndex]++;
36
37            // Check if the current row or column is completed
38            if (rowCompletion[rowIndex] == colCount || colCompletion[colIndex] == rowCount) {
39                // Return the current index if a row or column is complete
40                return k;
41            }
42        }
43    }
44}
45
1#include <vector>
2#include <unordered_map>
3
4class Solution {
5public:
6    // Returns the first index at which all numbers in a row or column are filled
7    // according to the order given in 'arr'.
8    int firstCompleteIndex(vector<int>& order, vector<vector<int>>& matrix) {
9        int rows = matrix.size(), cols = matrix[0].size();
10        unordered_map<int, pair<int, int>> numberToPosition;
11
12        // Populate a hash map with the number as the key and its position (i, j)
13        // in the matrix as the value.
14        for (int i = 0; i < rows; ++i) {
15            for (int j = 0; j < cols; ++j) {
16                numberToPosition[matrix[i][j]] = {i, j};
17            }
18        }
19
20        // Create a vector to keep track of the count of filled numbers in each row and column.
21        vector<int> rowCount(rows, 0), colCount(cols, 0);
22
23        // Iterate through the order vector to simulate filling the matrix.
24        for (int k = 0; k < order.size(); ++k) {
25            // Get the position of the current number in the order array
26            // from the hash map.
27            auto [i, j] = numberToPosition[order[k]];
28
29            // Increment the filled numbers count for the respective row and column.
30            ++rowCount[i];
31            ++colCount[j];
32
33            // If a row or a column is completely filled, return the current index.
34            if (rowCount[i] == cols || colCount[j] == rows) {
35                return k;
36            }
37        }
38
39        // The code prior guarantees a result, so this return statement might never be reached.
40        // However, it is here as a fail-safe.
41        return -1;
42    }
43};
44
1// Function to find the first index at which all numbers in either the 
2// same row or the same column of the matrix have appeared in the array.
3function firstCompleteIndex(arr: number[], mat: number[][]): number {
4    // Get the dimensions of the matrix
5    const rowCount = mat.length;
6    const colCount = mat[0].length;
7
8    // Map to store the position for each value in the matrix
9    const positionMap: Map<number, number[]> = new Map();
10    // Fill the map with positions of each value
11    for (let row = 0; row < rowCount; ++row) {
12        for (let col = 0; col < colCount; ++col) {
13            positionMap.set(mat[row][col], [row, col]);
14        }
15    }
16
17    // Arrays to keep track of the count of numbers found in each row and column
18    const rowCompletionCount: number[] = new Array(rowCount).fill(0);
19    const colCompletionCount: number[] = new Array(colCount).fill(0);
20
21    // Iterate through the array elements to find the complete row/col index
22    for (let index = 0; index < arr.length; ++index) {
23        // Get the position of the current element from the map
24        const [row, col] = positionMap.get(arr[index])!;
25        // Increment completion count for the row and column
26        ++rowCompletionCount[row];
27        ++colCompletionCount[col];
28
29        // Check if the current row or column is completed
30        if (rowCompletionCount[row] === colCount || colCompletionCount[col] === rowCount) {
31            // Return the current index if a complete row or column is found
32            return index;
33        }
34    }
35  
36    // In case no complete row or column is found,
37    // the function will keep running indefinitely due to the lack of a stopping condition in the for loop.
38}
39

Time and Space Complexity

The time complexity of the given code can be broken down into two main parts. Firstly, populating the idx dictionary requires iterating through each element of the m x n matrix, which takes O(m * n) time. Secondly, iterating over the arr array with k elements and updating the row and col counts takes O(k) time. However, each element's index is accessed in constant time due to the dictionary. Therefore, the overall time complexity is the sum of both parts, O(m * n + k).

The space complexity involves the storage used by the idx dictionary, which holds one entry for each element in the m x n matrix, hence O(m * n) space. Additionally, the row and col arrays utilize O(m) and O(n) space, respectively. Consequently, the total space complexity is O(m * n) since O(m + n) is subsumed under O(m * n) when m and n are of the same order.

Learn more about how to find time and space complexity quickly using problem constraints.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!