Random Flip Matrix
MedPrompt
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 matrixmandn.int[] flip()Returns a random index[i, j]of the matrix wherematrix[i][j] == 0and flips it to1.void reset()Resets all the values of the matrix to be0.
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 <= 104- There will be at least one free cell for each call to
flip. - At most
1000calls will be made toflipandreset.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the problem description by maintaining a full m x n matrix in memory. Each cell in the matrix stores a boolean value indicating whether it has been flipped or not. When flip() is called, the algorithm repeatedly generates random (row, col) coordinates until it finds a cell that has not yet been flipped. This method is known as Rejection Sampling.
Algorithm
- Constructor
Solution(m, n):- Initialize an
m x nmatrix (e.g.,boolean[][] flipped) with all valuesfalse. - Store the dimensions
mandn. - Initialize a random number generator.
- Initialize an
flip()Method:- Start an infinite loop (or a
do-whileloop). - Generate a random row
rwithin[0, m-1]. - Generate a random column
cwithin[0, n-1]. - Check the state of the cell
(r, c)in theflippedmatrix. - If
flipped[r][c]isfalse, it means the cell is available. a. Mark it as flipped:flipped[r][c] = true. b. Return the coordinates[r, c]. c. Exit the loop. - If
flipped[r][c]istrue, the cell has already been picked. The loop continues to find another random cell.
- Start an infinite loop (or a
reset()Method:- Create a new
m x nmatrix and set all its values tofalse, effectively resetting the state.
- Create a new
Walkthrough
The core idea is to keep a complete representation of the grid. The Solution class holds an m x n boolean array, say flipped, initialized to all false.
For the flip() operation, we generate random row and column indices. We then check flipped[row][col]. If it's false, we've found an available cell. We set flipped[row][col] to true and return [row, col]. If it's true, we reject this sample and repeat the process until an unflipped cell is found. Since the problem guarantees at least one cell is available, this loop will eventually terminate.
The reset() operation is straightforward: it re-initializes the flipped matrix to its original all-false state.
import java.util.Random; class Solution { private boolean[][] flipped; private int m; private int n; private Random rand; public Solution(int m, int n) { this.m = m; this.n = n; this.rand = new Random(); this.flipped = new boolean[m][n]; } public int[] flip() { while (true) { int r = rand.nextInt(m); int c = rand.nextInt(n); if (!flipped[r][c]) { flipped[r][c] = true; return new int[]{r, c}; } } } public void reset() { this.flipped = new boolean[m][n]; }}Complexity
Time
* **Constructor**: `O(m * n)` to initialize the matrix. * **`flip()`**: `O(1)` in the best case (first pick is valid), but the expected time is `O(N / (N - k))` where `N = m * n` and `k` is the number of flipped cells. This becomes very slow as `k` approaches `N`. * **`reset()`**: `O(m * n)` to re-initialize the matrix.
Space
O(m * n) to store the entire matrix.
Trade-offs
Pros
Simplicity: The logic is very easy to understand and implement.
Cons
High Space Complexity: The
O(m * n)space requirement is prohibitive for the given constraints (m, n <= 10^4), potentially causing Memory Limit Exceeded errors.Inefficient
flip(): The time complexity offlip()degrades as more cells are flipped. When the matrix is nearly full, finding the last few available cells can take a very long time due to repeated random picks landing on already flipped cells.Slow
reset(): Resetting the entire matrix takesO(m * n)time, which is inefficient.
This optimal approach avoids the high memory cost of a full matrix by recognizing that the number of flip calls is small. It treats the m x n grid as a virtual 1D array of size total = m * n. The core idea is to maintain a pool of available indices, which initially contains all indices from 0 to total - 1.
When flip() is called, we pick a random index from the available pool. To avoid picking it again, we virtually swap this picked index with the last index in the pool and then shrink the pool's size by one. A HashMap is used to efficiently track these swaps, storing only the indices that have been involved in a swap. This avoids allocating memory for all m * n cells.
Algorithm
- Conceptual Model: Imagine a 1D array of all cell indices from
0tom*n - 1. We need to randomly pick an index from this array without replacement. - Data Structures:
top: An integer representing the count of available cells. Initiallym * n.map: AHashMap<Integer, Integer>to store mappings for swapped indices.
- Constructor
Solution(m, n):- Store
mandn. - Call
reset()to initializetopand themap.
- Store
flip()Method:- Generate a random index
rand_idxin the range[0, top - 1].toprepresents the size of the pool of available indices. - Decrement
topto shrink the pool for the next call. - Determine the actual cell index to return. If
rand_idxis a key in ourmap, it means its original value was swapped. We use the mapped value:result_idx = map.get(rand_idx). Otherwise,rand_idxhas not been touched, soresult_idx = rand_idx. - To virtually "remove"
rand_idxfrom the pool, we swap it with the last element of the pool, which is at indextop. We find the value at indextop(which could also have been swapped) usingtop_val = map.getOrDefault(top, top). - Update the map to reflect the swap:
map.put(rand_idx, top_val). This ensures that ifrand_idxis picked again, we will usetop_valinstead. - Convert the 1D
result_idxback to 2D coordinates[result_idx / n, result_idx % n]and return them.
- Generate a random index
reset()Method:- Reset
toptom * n. - Clear the
mapby creating a new emptyHashMap.
- Reset
Walkthrough
We map each 2D coordinate (i, j) to a unique 1D index k = i * n + j. We maintain a variable, top, which represents the number of available (un-flipped) cells, initialized to m * n.
In flip(), we generate a random number rand_idx from [0, top - 1]. This rand_idx represents the k-th available element, not necessarily the cell with index k. We use a HashMap to resolve this. The map stores indices that have been picked and maps them to the values they were swapped with.
- Pick a random index
rand_idxfrom the current valid range[0, top - 1]. - The actual cell index we return,
result_idx, ismap.getOrDefault(rand_idx, rand_idx). This gives us the true value at that position in our virtual array. - We then shrink the valid range by decrementing
top. To ensurerand_idxis not picked again, we virtually place the value from the end of the range (top) into therand_idxposition. We do this by updating the map:map.put(rand_idx, map.getOrDefault(top, top)). This means ifrand_idxis ever generated again, we will instead use the value that was at the end of the pool.
This ensures each flip() is an O(1) operation on average, and the space used is proportional only to the number of flips performed.
import java.util.HashMap;import java.util.Map;import java.util.Random; class Solution { private Map<Integer, Integer> map; private int m, n, top; private Random rand; public Solution(int m, int n) { this.m = m; this.n = n; this.rand = new Random(); reset(); } public int[] flip() { // Generate a random index from the available range [0, top-1] int rand_idx = rand.nextInt(top); // The number of available cells decreases by one for the next call top--; // Get the actual cell index to return. // If rand_idx is in the map, it means its original value was swapped. // We use the mapped value. Otherwise, it hasn't been touched, so we use rand_idx itself. int result_idx = map.getOrDefault(rand_idx, rand_idx); // Now, we virtually "swap" the element at rand_idx with the element at the end of the available range (top). // Get the value that is currently at the `top` position. int top_val = map.getOrDefault(top, top); // Place the value from the end (`top_val`) into the `rand_idx` position for future picks. map.put(rand_idx, top_val); // Convert the 1D result index back to 2D coordinates return new int[]{result_idx / n, result_idx % n}; } public void reset() { this.map = new HashMap<>(); this.top = m * n; }}Complexity
Time
* **Constructor**: `O(1)`. * **`flip()`**: `O(1)` on average, due to the constant-time performance of hash map operations. * **`reset()`**: `O(1)` as it only reinitializes a few variables and creates a new map.
Space
O(K), where K is the number of calls to `flip()`. Since K is at most 1000, this is very efficient.
Trade-offs
Pros
Optimal Space Complexity: Space is
O(K)whereKis the number of flips, which is independent of the matrix size.Optimal Time Complexity:
flip()runs inO(1)average time.Fast Initialization and Reset: The constructor and
reset()methods are alsoO(1)(ignoring garbage collection of the old map).
Cons
Conceptual Complexity: The logic of virtual swapping using a hash map is less intuitive than a direct simulation.
Solutions
Solution
class Solution {private int m;private int n;private int total;private Random rand = new Random();private Map<Integer, Integer> mp = new HashMap<>();public Solution(int m, int n) { this.m = m; this.n = n; this.total = m * n; }public int[] flip() { int x = rand.nextInt(total--); int idx = mp.getOrDefault(x, x); mp.put(x, mp.getOrDefault(total, total)); return new int[]{idx / n, idx % n}; }public void reset() { total = m * n; mp.clear(); }} /** * Your Solution object will be instantiated and called as such: * Solution obj = new Solution(m, n); * int[] param_1 = obj.flip(); * obj.reset(); */class Solution: # Your Solution object will be instantiated and called as such: # obj = Solution(m, n) # param_1 = obj.flip() # obj.reset() def __init__(self, m: int, n: int): self . m = m self . n = n self . total = m * n self . mp = {} def flip(self) -> List[int]: self . total -= 1 x = random . randint(0, self . total) idx = self . mp . get(x, x) self . mp[x] = self . mp . get(self . total, self . total) return [idx // self . n, idx % self . n] def reset(self) -> None: self . total = self . m * self . n self . mp . clear()Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.