{x}
blog image

Random Flip Matrix

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 <= 104
  • There will be at least one free cell for each call to flip.
  • At most 1000 calls will be made to flip and reset.

Solution Explanation for Random Flip Matrix

This problem requires designing a data structure that efficiently handles random selection of unflipped cells (cells with value 0) from an m x n binary matrix, flipping the selected cell to 1, and resetting the matrix. The key is to minimize calls to the random number generator and optimize time and space complexity.

The optimal approach uses a combination of reservoir sampling and a hash map.

Algorithm:

  1. Initialization (__init__ or Solution constructor):

    • Store the dimensions m and n of the matrix.
    • Initialize total to m * n, representing the total number of available cells (initially all 0s).
    • Create a hash map (mp or map) to store the mapping between randomly generated indices and their actual indices in the matrix. This map is crucial for efficient flipping and resetting.
  2. Flipping (flip):

    • Decrement total because one cell will be flipped.
    • Generate a random integer x between 0 (inclusive) and total (inclusive). This represents the index to be flipped after considering the already flipped cells.
    • Check if x is already mapped to a different index in mp. If it is, retrieve the actual index (idx) from the map. Otherwise, idx is simply x.
    • Update mp to map x to the index of the last available cell (originally total but now total -1). This is the essence of reservoir sampling; it maintains the equal probability of selection for all remaining cells.
    • Calculate and return the row and column indices [idx // n, idx % n] from idx.
  3. Resetting (reset):

    • Reset total to m * n.
    • Clear the hash map (mp).

Time and Space Complexity:

  • __init__ / Solution: O(1) time and space. Initialization takes constant time and space.
  • flip: O(1) amortized time and O(k) space, where k is the number of flipped cells. While hash map lookups can be O(1) on average, in the worst-case scenario (many collisions), it could degrade to O(k). However, the amortized time complexity remains O(1) due to efficient hash table operations. Space is proportional to the number of flipped cells stored in the hash map.
  • reset: O(k) time and O(1) space. Resetting clears the hash map, which takes time proportional to the number of entries (flipped cells). The space complexity remains constant as the map is cleared.

Code Examples:

Python:

import random
 
class Solution:
    def __init__(self, m: int, n: int):
        self.m = m
        self.n = n
        self.total = m * n
        self.mp = {}  # Hash map to track flipped cells
 
    def flip(self) -> list[int]:
        self.total -= 1
        x = random.randint(0, self.total) # Generates a random index within the remaining available cells
        idx = self.mp.get(x, x)  # Get actual index or use random index if not already mapped
        self.mp[x] = self.mp.get(self.total, self.total) # Map the random index to the last unflipped cell index
        return [idx // self.n, idx % self.n] #Convert the linear index back to row and column
 
    def reset(self) -> None:
        self.total = self.m * self.n
        self.mp.clear()

Java:

import java.util.*;
 
class Solution {
    private int m, n, total;
    private Random rand = new Random();
    private Map<Integer, Integer> mp = new HashMap<>(); //Hashmap to track flipped cells
 
    public Solution(int m, int n) {
        this.m = m;
        this.n = n;
        this.total = m * n;
    }
 
    public int[] flip() {
        total--;
        int x = rand.nextInt(total +1); // Generates a random index within the remaining available cells
        int idx = mp.getOrDefault(x, x); // Get actual index or use random index if not already mapped
        mp.put(x, mp.getOrDefault(total, total)); // Map the random index to the last unflipped cell index
 
        return new int[] {idx / n, idx % n}; //Convert the linear index back to row and column
    }
 
    public void reset() {
        total = m * n;
        mp.clear();
    }
}

The Java and Python code demonstrate the same core algorithm. The use of a hash map significantly improves the efficiency of the random flipping process, making it suitable for large matrices. The reservoir sampling technique ensures that all unflipped cells have an equal probability of being selected.