Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

957. Prison Cells After N Days - Leetcode Solution

Problem Description

You are given an array cells of length 8, where each element is either 0 (cell is vacant) or 1 (cell is occupied). Each day, the state of the cells changes according to the following rules:

  • If a cell has two adjacent neighbors that are both occupied or both vacant, then it becomes occupied (1). Otherwise, it becomes vacant (0).
  • The first and last cell in the row always become vacant (0) on the next day, because they have only one neighbor.

Given an integer N, representing the number of days, return the state of the cells after N days. The initial state is provided by the input array cells.

Constraints:

  • cells.length == 8
  • cells[i] is 0 or 1
  • 1 <= N <= 10^9

Thought Process

At first glance, it seems we need to simulate each day, updating the array according to the rules, for up to N days. However, since N can be as large as a billion, simulating each day one by one would be too slow.

Notice that the number of possible states is limited. With 8 cells and each cell being 0 or 1, there are at most 256 possible states. However, the first and last cells always become 0, so only the 6 middle cells can vary, leading to 64 possible states. This suggests that the process must eventually repeat (cycle).

Therefore, rather than simulating all N days, we can simulate until we see a repeated state and then use the cycle length to skip ahead, reducing the number of simulations needed.

Solution Approach

  • Step 1: Simulate State Transitions
    • Write a function to compute the next day's state from the current state, following the rules given.
  • Step 2: Detect Cycles
    • Use a hash map (dictionary) to record each unique state and the day it first appeared.
    • As you simulate each day, check if the new state has been seen before. If so, a cycle has been found.
  • Step 3: Fast Forward Using the Cycle
    • Once a cycle is detected, compute the cycle length.
    • Use modulo arithmetic to skip over full cycles, reducing N to a much smaller number of steps.
  • Step 4: Simulate Remaining Days
    • After fast-forwarding, simulate the remaining days (which will be less than a full cycle) to get the final state.
  • Design Choices:
    • We use a hash map for cycle detection because it allows O(1) lookups for previously seen states.
    • We represent the state as a tuple or integer for easy comparison and storage.

Example Walkthrough

Input: cells = [0,1,0,1,1,0,0,1], N = 7

  1. Day 0: [0,1,0,1,1,0,0,1]
  2. Day 1: [0,0,1,0,0,0,1,0]
    • Each cell is updated according to its neighbors.
  3. Day 2: [0,1,1,0,0,1,0,0]
  4. Day 3: [0,0,0,0,1,1,0,0]
  5. Day 4: [0,1,0,1,0,0,1,0]
  6. Day 5: [0,0,1,0,1,1,0,0]
  7. Day 6: [0,1,0,0,0,0,1,0]
  8. Day 7: [0,0,1,1,1,0,0,0]

After 7 days, the final state is [0,0,1,1,1,0,0,0].

If N were much larger, we would detect when the state repeats, calculate the cycle length, and skip ahead using modulo.

Time and Space Complexity

  • Brute-force Approach:
    • Time: O(N), since we would simulate each day up to N days.
    • Space: O(1), only storing the state array.
  • Optimized Cycle Detection Approach:
    • Time: O(C), where C is the length of the cycle (at most 64), plus O(R) for the remaining steps after fast-forwarding.
    • Space: O(C), for storing previously seen states in the hash map.
    • This is much faster than O(N) when N is large.

Summary

The key insight for this problem is recognizing that the number of possible states is limited, so the process must eventually repeat, forming a cycle. By detecting cycles, we can fast-forward through many redundant steps, making it feasible to handle very large values of N. This approach efficiently combines simulation with cycle detection, leveraging hash maps for quick lookups and modulo arithmetic for skipping ahead.

Code Implementation

class Solution:
    def prisonAfterNDays(self, cells, N):
        seen = dict()
        is_cycle = False
        for i in range(N):
            next_cells = [0]
            for j in range(1, 7):
                next_cells.append(1 if cells[j-1] == cells[j+1] else 0)
            next_cells.append(0)
            state_tuple = tuple(next_cells)
            if state_tuple in seen:
                # Cycle detected
                cycle_length = i - seen[state_tuple]
                remaining = (N - i - 1) % cycle_length
                return self.prisonAfterNDays(next_cells, remaining)
            else:
                seen[state_tuple] = i
            cells = next_cells
        return cells
      
class Solution {
public:
    vector<int> prisonAfterNDays(vector<int>& cells, int N) {
        unordered_map<string, int> seen;
        bool isCycle = false;
        for (int i = 0; i < N; ++i) {
            vector<int> next(8, 0);
            for (int j = 1; j < 7; ++j) {
                next[j] = (cells[j - 1] == cells[j + 1]) ? 1 : 0;
            }
            string key(next.begin(), next.end());
            if (seen.count(key)) {
                int cycle = i - seen[key];
                int rem = (N - i - 1) % cycle;
                return prisonAfterNDays(next, rem);
            } else {
                seen[key] = i;
            }
            cells = next;
        }
        return cells;
    }
};
      
class Solution {
    public int[] prisonAfterNDays(int[] cells, int N) {
        Map<String, Integer> seen = new HashMap<>();
        for (int i = 0; i < N; ++i) {
            int[] next = new int[8];
            for (int j = 1; j < 7; ++j) {
                next[j] = cells[j - 1] == cells[j + 1] ? 1 : 0;
            }
            String key = Arrays.toString(next);
            if (seen.containsKey(key)) {
                int cycle = i - seen.get(key);
                int rem = (N - i - 1) % cycle;
                return prisonAfterNDays(next, rem);
            } else {
                seen.put(key, i);
            }
            cells = next;
        }
        return cells;
    }
}
      
var prisonAfterNDays = function(cells, N) {
    let seen = new Map();
    for (let i = 0; i < N; ++i) {
        let next = Array(8).fill(0);
        for (let j = 1; j < 7; ++j) {
            next[j] = cells[j - 1] === cells[j + 1] ? 1 : 0;
        }
        let key = next.join('');
        if (seen.has(key)) {
            let cycle = i - seen.get(key);
            let rem = (N - i - 1) % cycle;
            return prisonAfterNDays(next, rem);
        } else {
            seen.set(key, i);
        }
        cells = next;
    }
    return cells;
};