There is an m x n binary grid matrix with all the values set 0 initially. Design an algorithm to randomly pick an index (i, j) where matrix[i][j] == 0 and flips it to 1. All the indices (i, j) where matrix[i][j] == 0 should be equally likely to be returned.
Optimize your algorithm to minimize the number of calls made to the built-in random function of your language and optimize the time and space complexity.
Implement the Solution class:
Solution(int m, int n) Initializes the object with the size of the binary matrix m and n.int[] flip() Returns a random index [i, j] of the matrix where matrix[i][j] == 0 and flips it to 1.void reset() Resets all the values of the matrix to be 0.Example 1:
Input ["Solution", "flip", "flip", "flip", "reset", "flip"] [[3, 1], [], [], [], [], []] Output [null, [1, 0], [2, 0], [0, 0], null, [2, 0]] Explanation Solution solution = new Solution(3, 1); solution.flip(); // return [1, 0], [0,0], [1,0], and [2,0] should be equally likely to be returned. solution.flip(); // return [2, 0], Since [1,0] was returned, [2,0] and [0,0] solution.flip(); // return [0, 0], Based on the previously returned indices, only [0,0] can be returned. solution.reset(); // All the values are reset to 0 and can be returned. solution.flip(); // return [2, 0], [0,0], [1,0], and [2,0] should be equally likely to be returned.
Constraints:
1 <= m, n <= 104flip.1000 calls will be made to flip and reset.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to this problem is like randomly picking a spot in the matrix, checking if it's already been flipped, and repeating until we find an unflipped spot. We essentially simulate many random coin flips until we get the desired outcome.
Here's how the algorithm would work step-by-step:
import random
class RandomFlipMatrix:
def __init__(self, rows, columns):
self.rows = rows
self.columns = columns
self.matrix = [[0] * columns for _ in range(rows)]
self.total_cells = rows * columns
def flip(self):
# Iterate until we successfully flip an un-flipped cell
while True:
random_row = random.randint(0, self.rows - 1)
random_column = random.randint(0, self.columns - 1)
# Only flip if the cell is currently 0 (unflipped)
if self.matrix[random_row][random_column] == 0:
self.matrix[random_row][random_column] = 1
# Return the coordinates of the flipped cell
return [random_row, random_column]
def reset(self):
# Reset all cells to 0 (unflipped)
self.matrix = [[0] * self.columns for _ in range(self.rows)]The efficient solution avoids directly representing the entire matrix. It maintains a mapping to simulate flipping elements without having to touch every single cell, making the process much faster especially when the matrix is huge and sparsely flipped.
Here's how the algorithm would work step-by-step:
import random
class Solution:
def __init__(self, number_of_rows: int, number_of_columns: int):
self.number_of_rows = number_of_rows
self.number_of_columns = number_of_columns
self.total_cells = number_of_rows * number_of_columns
self.flipped_cells = {}
def flip(self) -> list[int]:
# Generate a random index within the range of available cells.
random_index = random.randrange(self.total_cells)
# Check if the random index has been flipped before.
if random_index in self.flipped_cells:
result = self.flipped_cells[random_index]
else:
result = random_index
# Get the value of the last available cell.
if self.total_cells - 1 in self.flipped_cells:
last_cell_value = self.flipped_cells[self.total_cells - 1]
else:
last_cell_value = self.total_cells - 1
# Update the flipped cells mapping. This is the core 'flip' operation.
self.flipped_cells[random_index] = last_cell_value
self.total_cells -= 1
row_index = result // self.number_of_columns
column_index = result % self.number_of_columns
return [row_index, column_index]
def reset(self) -> None:
self.total_cells = self.number_of_rows * self.number_of_columns
self.flipped_cells = {}| Case | How to Handle |
|---|---|
| m or n is zero | Return immediately if m or n is zero as no flips are possible. |
| m * n is 1 | Initialize the data structure and flip the single cell, ensuring getRandom returns only that cell. |
| m * n exceeds the maximum integer value | Use long or appropriate data type to represent the total number of cells to avoid overflow when calculating random indices. |
| Repeated calls to flip eventually exhaust all possible flips | Ensure the reservoir/hashmap shrinks and getRandom correctly handles the diminishing range of available indices, halting when no more flips are possible. |
| Extremely large matrix dimensions (large m * n), causing memory issues | Use a lazy swapping approach (hash map) to store only flipped indices instead of a full matrix representation to conserve memory. |
| getRandom is called more times than m * n | Ensure flip operations do not access invalid memory and that has been reset will return empty after all positions have been flipped. |
| The coordinate representation leads to out-of-bounds access. | Ensure modulo operations map to valid row and column indices within the matrix dimensions. |
| Random number generator returns the same sequence repeatedly. | Use a good quality random number generator with sufficient entropy to provide uniform distribution of flips. |