Quick Overview

A grid simulation coding question: plants are healthy or poisoned, and each day every healthy plant with at least k poisoned plants among its eight neighbors becomes poisoned. It asks how many days pass until no new plant is poisoned, testing simultaneous-update semantics, edge cells, and a solution that runs in time linear in the grid size.

Days Until Poison Stops Spreading in a Plant Grid Under a K-of-8-Neighbors Rule

Company: Apple

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

A field of plants is laid out as an `m x n` grid. A cell holding `0` is a healthy plant and a cell holding `1` is a poisoned plant. Poison spreads once a day: on each day, every healthy plant that has at least `k` poisoned plants among its up to eight neighbors (left, right, up, down and the four diagonals) becomes poisoned. A poisoned plant stays poisoned. Return the number of days it takes for the field to become stable, that is, the number of days on which at least one plant becomes poisoned. ### Function Signature ```python def days_until_stable(grid: list[list[int]], k: int) -> int: ``` ### Rules - All plants update at the same time. Whether a healthy plant becomes poisoned on day `d` depends only on the grid as it was at the start of day `d`, so a plant poisoned on day `d` first counts toward its neighbors on day `d + 1`. - The threshold is inclusive: a healthy plant with exactly `k` poisoned neighbors becomes poisoned. - Cells outside the grid are not neighbors, so a plant on an edge or in a corner has fewer than eight neighbors. - Once a day passes on which no plant becomes poisoned, the grid never changes again. The days with new poisonings are therefore days `1, 2, ..., D`, and the answer is `D`. Return `0` if no plant ever becomes poisoned. ### Constraints - `1 <= m, n <= 500`, where `m = len(grid)` and `n = len(grid[0])`; every row has length `n`. - Every `grid[i][j]` is `0` or `1`. - `1 <= k <= 8` - The number of days can grow in proportion to `m * n`. Aim for `O(m * n)` total time rather than time proportional to the number of cells multiplied by the number of days. ### Examples **Example 1** ```text Input: grid = [[1, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], k = 2 Output: 4 ``` The grid at the start and after each day: ```text start after day 1 after day 2 after day 3 after day 4 1 1 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 ``` On day 1, the plants at row 1, columns 0 and 1, each have two poisoned neighbors. On day 2, the plant at row 2, column 2 has only one poisoned neighbor (row 1, column 1), because its other neighbors are poisoned during that same day; it is poisoned on day 3. The plant at row 2, column 3 is poisoned last, on day 4, and day 5 poisons nothing. **Example 2** ```text Input: grid = [[1, 1, 0], [1, 0, 0], [0, 0, 0]], k = 3 Output: 1 ``` On day 1 the center plant has three poisoned neighbors and becomes poisoned. After that, every remaining healthy plant has at most two poisoned neighbors (the top-right plant, for example, touches only the top-middle and center plants), so nothing else changes and five plants stay healthy. **Example 3** ```text Input: grid = [[0, 0], [0, 0]], k = 1 Output: 0 ``` No plant is poisoned, so the poison never spreads.

Overview: A grid simulation coding question: plants are healthy or poisoned, and each day every healthy plant with at least k poisoned plants among its eight neighbors becomes poisoned. It asks how many days pass until no new plant is poisoned, testing simultaneous-update semantics, edge cells, and a solution that runs in time linear in the grid size.

Read the full Apple Software Engineer interview experience this question came from

A field of plants is laid out as an `m x n` grid. A cell holding `0` is a healthy plant and a cell holding `1` is a poisoned plant. Poison spreads once per day: on each day, every healthy plant that has at least `k` poisoned plants among its up to eight neighbors (left, right, up, down and the four diagonals) becomes poisoned. A poisoned plant stays poisoned. Return the number of days it takes for the field to become stable, that is, the number of days on which at least one plant becomes poisoned. ### Rules - All plants update at the same time. Whether a healthy plant becomes poisoned on day `d` depends only on the grid as it was at the start of day `d`, so a plant poisoned on day `d` first counts toward its neighbors on day `d + 1`. - The threshold is inclusive: a healthy plant with exactly `k` poisoned neighbors becomes poisoned. - Cells outside the grid are not neighbors (there is no wraparound), so a plant on an edge or in a corner has fewer than eight neighbors. - Once a day passes on which no plant becomes poisoned, the grid never changes again. The days with new poisonings are therefore days `1, 2, ..., D`, and the answer is `D`. Return `0` if no plant ever becomes poisoned. The answer is at most `m * n <= 250,000`, so it always fits in a 32-bit signed integer. ### Example 1 ```text Input: grid = [[1, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], k = 2 Output: 4 ``` The grid at the start and after each day: ```text start after day 1 after day 2 after day 3 after day 4 1 1 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 ``` On day 1 the plants at row 1, columns 0 and 1, each have two poisoned neighbors. On day 2 the plant at row 2, column 2 has only one poisoned neighbor (row 1, column 1), because its other neighbors are poisoned during that same day, so it is poisoned on day 3. The plant at row 2, column 3 is poisoned last, on day 4, and day 5 poisons nothing. ### Example 2 ```text Input: grid = [[1, 1, 0], [1, 0, 0], [0, 0, 0]], k = 3 Output: 1 ``` On day 1 the center plant has exactly three poisoned neighbors and becomes poisoned. After that every remaining healthy plant has at most two poisoned neighbors (the top-right plant, for example, touches only the top-middle and center plants), so nothing else changes and five plants stay healthy. ### Constraints - `1 <= m, n <= 500`, where `m = len(grid)` and `n = len(grid[0])`; every row has length `n`. - Every `grid[i][j]` is `0` or `1`. - `1 <= k <= 8` - The number of days can grow in proportion to `m * n`. Aim for `O(m * n)` total time rather than time proportional to the number of cells multiplied by the number of days.

Constraints

  • `1 <= m, n <= 500`, where `m = len(grid)` and `n = len(grid[0])`; every row has length `n`.
  • Every `grid[i][j]` is `0` or `1`.
  • `1 <= k <= 8`
  • The number of days can grow in proportion to `m * n`. Aim for `O(m * n)` total time rather than time proportional to the number of cells multiplied by the number of days.

Examples

Input: ([[1, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], 2)

Expected Output: 4

Explanation: Example 1: spread from the top-left pair takes four days; a plant poisoned on day 2 only counts toward its neighbors from day 3 (simultaneous update).

Input: ([[1, 1, 0], [1, 0, 0], [0, 0, 0]], 3)

Expected Output: 1

Explanation: Example 2: the center has exactly k = 3 poisoned neighbors and fires on day 1; every other healthy plant stalls at two or fewer, so five plants stay healthy (partial stabilization).

Hints

  1. Whether a healthy plant is poisoned on day d depends only on how many of its neighbors were already poisoned before day d, and that number never decreases.
  2. Recomputing the whole grid every day can cost time proportional to the number of cells multiplied by the number of days. Which plants can possibly change on a given day?
  3. Plants on an edge or in a corner have fewer than eight neighbors; cells outside the grid never count.

Loading coding console...

Show the approach

Approach

Algorithm: keep, for every healthy plant, the number of its in-grid neighbors that are already poisoned, and spread the poison as a level-by-level frontier. The initially poisoned plants form the day-0 frontier. Processing a frontier means: for every plant in it, add 1 to the count of each in-grid healthy neighbor; a neighbor whose count becomes exactly k joins the next frontier. Only after the whole frontier has been processed are the next frontier's plants marked poisoned, and if that frontier is nonempty one more day is counted. The loop stops when a frontier produces no new plant.

Invariant: after the frontier of day d (d = 0 for the initial plants) has been processed, every healthy plant's count equals the number of its neighbors that are poisoned at the start of day d + 1.

Correctness: counts only grow, one step at a time, so a healthy plant's count equals k at exactly one moment. That moment is during the processing of the first day d after which it has at least k poisoned neighbors, so it joins frontier d + 1, which is exactly the day the simultaneous rule poisons it (a plant whose initial count is already at least k reaches k while the day-0 frontier is processed and is poisoned on day 1). Because plants in the next frontier are marked poisoned only after the current frontier finishes, a plant poisoned on day d never influences anyone before day d + 1. The days with new poisonings are consecutive, so counting the nonempty frontiers after day 0 gives D, and the first empty frontier (the first day with no change) is not counted.

Edge cases: a grid with no poisoned plants or no healthy plants returns 0; a 1x1 grid has no neighbors; edge and corner plants consult only in-grid neighbors, so there is no wraparound; when k exceeds a plant's neighbor count it can never be poisoned. Each plant enters a frontier at most once and inspects at most eight neighbors, so the total work is O(m * n) even when the number of days grows with m * n. The answer is at most m * n <= 250,000 and fits in a 32-bit int in every language.

Time complexity:
O(m * n)
Space complexity:
O(m * n)