diff --git a/problems/3568-minimum-moves-to-clean-the-classroom/analysis.md b/problems/3568-minimum-moves-to-clean-the-classroom/analysis.md new file mode 100644 index 0000000..e8e007b --- /dev/null +++ b/problems/3568-minimum-moves-to-clean-the-classroom/analysis.md @@ -0,0 +1,135 @@ +# 3568. Minimum Moves to Clean the Classroom + +[LeetCode Link](https://leetcode.com/problems/minimum-moves-to-clean-the-classroom/) + +Difficulty: Medium +Topics: Array, Hash Table, Bit Manipulation, Breadth-First Search, Matrix +Acceptance Rate: 41.0% + +## Hints + +### Hint 1 + +Every move costs exactly one unit, and you want the *fewest* moves. On a grid +with unit-cost edges that screams BFS. But before you write the usual +`visited[r][c]` grid BFS, ask yourself: is "which cell am I on" really enough to +describe the situation you are in? Two students standing on the same cell are +not necessarily equally well off. + +### Hint 2 + +Look hard at the constraint "at most 10 `'L'` cells". Ten of anything is a loud +hint that a subset should be encoded as a bitmask (`2^10 = 1024` possibilities). +So part of your state is *which litter you have already collected*. And notice +that you may legitimately need to walk over a cell you have already visited — +to double back for a second piece of litter, or to detour to an `'R'` — so +per-cell visited marking would be wrong anyway. + +### Hint 3 + +The state is the triple `(row, col, collectedMask)` plus the remaining energy. +The trick is what to do with energy. Do **not** make it a fourth dimension you +mark as visited independently; instead, for each `(cell, mask)` remember only +the *largest* remaining energy you have ever arrived there with. If you show up +at the same `(cell, mask)` again with less-or-equal energy, that arrival is +strictly dominated — anything the weaker version could do next, the stronger one +could already do. Combine that pruning with a level-by-level BFS and the first +time the mask becomes full, the current level count is the answer. + +## Approach + +**Step 1 — preprocess the grid.** Scan once to find the starting cell `S` and to +assign each `'L'` cell a bit index `0..k-1` (with `k <= 10`). Keep a flat array +`litterID[r*n+c]` holding that index, or `-1` for non-litter cells. If `k == 0` +there is nothing to clean, so return `0` immediately. + +**Step 2 — define the state.** A state is `(r, c, mask, energy)`: + +- `(r, c)` — where the student stands, +- `mask` — bitset of litter already collected (`mask == (1< best[cell][mask]`, and update the entry when you do. This is what +keeps the search finite: without it, a loop through an `'R'` cell would let you +wander forever. It is also what makes revisits *possible* when they matter — a +cell you already stood on gets re-explored the moment you come back with a +fuller tank, which is exactly the situation where a detour to a reset area pays +off. + +**Worked example — `["LS","RL"]`, `energy = 4`.** Litter `(0,0)` is bit 0 and +`(1,1)` is bit 1; start is `(0,1)` with energy 4. + +- Level 0: `((0,1), mask=00, e=4)`. +- Level 1: move left to `(0,0)`, an `'L'` → `mask=01`, `e=3`. (Moving down to + `(1,1)` gives `mask=10`, `e=3`; BFS keeps both branches alive.) +- Level 2: from `(0,0)` move down to `(1,0)`, an `'R'` → energy resets to 4, + `mask=01`. Note the tank is *fuller* than it was two moves ago. +- Level 3: from `(1,0)` move right to `(1,1)`, an `'L'` → `mask=11`, which is + full. Return `3`. + +If you had marked `(1,0)` visited without tracking energy, the branch that +reaches it later with a full tank would have been thrown away and you could +report `-1` on grids that are actually solvable. That is the single most common +way this problem is failed. + +## Complexity Analysis + +Let `m x n` be the grid size, `k <= 10` the litter count, and `E` the energy +capacity. + +Time Complexity: O(m * n * 2^k * E) — there are `m * n * 2^k` distinct +`(cell, mask)` pairs, and each can be enqueued at most `O(E)` times because the +recorded best energy must strictly increase on every re-entry. Each dequeue does +constant work over 4 neighbours. With the given limits (`400 * 1024 * 50`) this +is comfortably fast, and in practice the frontier stays far smaller than the +bound. + +Space Complexity: O(m * n * 2^k) for the `best` table, plus the BFS frontier, +which is bounded by the same quantity. + +## Edge Cases + +- **No litter in the grid.** The full mask is `0`, which is already satisfied at + move zero. Return `0` before entering the BFS, otherwise the loop only checks + the goal *after* a move and you would return `-1` or an inflated count. +- **Energy runs out on a non-`'R'` cell.** The student is stuck, not dead — the + state simply has no outgoing edges. Guard with `if energy == 0 { skip }` + rather than pruning the state when it is created; it might still be the state + that lands on the final piece of litter. +- **Landing on `'R'` sets energy to the maximum, not `energy + something`.** + Arriving with 3 of 4 left and stepping onto `'R'` gives 4, never 5. +- **Revisiting cells is mandatory, not optional.** Grids like `["LSL"]` require + walking back over the start. A plain `visited[r][c]` grid returns `-1` here. +- **Litter reachable but not *all* of it.** Collecting a subset is worthless; + only the full mask counts. `["L.S","RXL"]` with `energy = 3` reaches either + piece but never both — return `-1`. +- **Litter fully walled off by `'X'`,** e.g. `["SXL"]`. BFS exhausts the frontier + and falls through to `-1`. +- **1x1 grid containing only `'S'`.** Handled by the no-litter early return. +- **Tight-but-sufficient energy.** `["S.L"]` with `energy = 2` succeeds in 2 + moves while `energy = 1` fails; make sure you decrement *before* checking + whether the destination completes the job, not after. + +This one is a fair Medium that punishes a reflexive grid BFS. If your first +instinct was `visited[r][c]`, that is the normal path through this problem — the +lesson worth keeping is that the visited key must contain everything that +distinguishes two situations, and here that means the litter mask plus a +dominance rule on energy. diff --git a/problems/3568-minimum-moves-to-clean-the-classroom/problem.md b/problems/3568-minimum-moves-to-clean-the-classroom/problem.md new file mode 100644 index 0000000..3e71ef0 --- /dev/null +++ b/problems/3568-minimum-moves-to-clean-the-classroom/problem.md @@ -0,0 +1,94 @@ +--- +number: "3568" +frontend_id: "3568" +title: "Minimum Moves to Clean the Classroom" +slug: "minimum-moves-to-clean-the-classroom" +difficulty: "Medium" +topics: + - "Array" + - "Hash Table" + - "Bit Manipulation" + - "Breadth-First Search" + - "Matrix" +acceptance_rate: 4099.6 +is_premium: false +created_at: "2026-09-01T05:25:12.500772+00:00" +fetched_at: "2026-09-01T05:25:12.500772+00:00" +link: "https://leetcode.com/problems/minimum-moves-to-clean-the-classroom/" +date: "2026-09-01" +--- + +# 3568. Minimum Moves to Clean the Classroom + +You are given an `m x n` grid `classroom` where a student volunteer is tasked with cleaning up litter scattered around the room. Each cell in the grid is one of the following: + + * `'S'`: Starting position of the student + * `'L'`: Litter that must be collected (once collected, the cell becomes empty) + * `'R'`: Reset area that restores the student's energy to full capacity, regardless of their current energy level (can be used multiple times) + * `'X'`: Obstacle the student cannot pass through + * `'.'`: Empty space + + + +You are also given an integer `energy`, representing the student's maximum energy capacity. The student starts with this energy from the starting position `'S'`. + +Each move to an adjacent cell (up, down, left, or right) costs 1 unit of energy. If the energy reaches 0, the student can only continue if they are on a reset area `'R'`, which resets the energy to its **maximum** capacity `energy`. + +Return the **minimum** number of moves required to collect all litter items, or `-1` if it's impossible. + + + +**Example 1:** + +**Input:** classroom = ["S.", "XL"], energy = 2 + +**Output:** 2 + +**Explanation:** + + * The student starts at cell `(0, 0)` with 2 units of energy. + * Since cell `(1, 0)` contains an obstacle 'X', the student cannot move directly downward. + * A valid sequence of moves to collect all litter is as follows: + * Move 1: From `(0, 0)` -> `(0, 1)` with 1 unit of energy and 1 unit remaining. + * Move 2: From `(0, 1)` -> `(1, 1)` to collect the litter `'L'`. + * The student collects all the litter using 2 moves. Thus, the output is 2. + + + +**Example 2:** + +**Input:** classroom = ["LS", "RL"], energy = 4 + +**Output:** 3 + +**Explanation:** + + * The student starts at cell `(0, 1)` with 4 units of energy. + * A valid sequence of moves to collect all litter is as follows: + * Move 1: From `(0, 1)` -> `(0, 0)` to collect the first litter `'L'` with 1 unit of energy used and 3 units remaining. + * Move 2: From `(0, 0)` -> `(1, 0)` to `'R'` to reset and restore energy back to 4. + * Move 3: From `(1, 0)` -> `(1, 1)` to collect the second litter `'L'`. + * The student collects all the litter using 3 moves. Thus, the output is 3. + + + +**Example 3:** + +**Input:** classroom = ["L.S", "RXL"], energy = 3 + +**Output:** -1 + +**Explanation:** + +No valid path collects all `'L'`. + + + +**Constraints:** + + * `1 <= m == classroom.length <= 20` + * `1 <= n == classroom[i].length <= 20` + * `classroom[i][j]` is one of `'S'`, `'L'`, `'R'`, `'X'`, or `'.'` + * `1 <= energy <= 50` + * There is exactly **one** `'S'` in the grid. + * There are **at most** 10 `'L'` cells in the grid. diff --git a/problems/3568-minimum-moves-to-clean-the-classroom/solution.go b/problems/3568-minimum-moves-to-clean-the-classroom/solution.go new file mode 100644 index 0000000..1ecec57 --- /dev/null +++ b/problems/3568-minimum-moves-to-clean-the-classroom/solution.go @@ -0,0 +1,109 @@ +// 3568. Minimum Moves to Clean the Classroom +// +// BFS over the state space (row, col, collectedMask, remainingEnergy). +// A plain grid BFS is not enough: the answer depends on which litter has +// already been picked up and on how much energy is left, so both are folded +// into the state. Since at most 10 'L' cells exist, the set of collected +// litter fits in a 10-bit mask. +// +// Every move costs exactly 1, so BFS explores states in order of move count +// and the first state whose mask is full is optimal. To keep the state space +// small we do not treat energy as a separate BFS dimension; instead we record, +// for each (cell, mask), the greatest remaining energy we have ever reached it +// with. Arriving again with less-or-equal energy is strictly dominated: any +// continuation from the weaker state is also available from the stronger one. + +package main + +type classroomState struct { + r, c, mask, energy int +} + +func minMoves(classroom []string, energy int) int { + m := len(classroom) + if m == 0 { + return -1 + } + n := len(classroom[0]) + + // litterID maps a flattened cell index to its bit position, or -1. + litterID := make([]int, m*n) + for i := range litterID { + litterID[i] = -1 + } + total := 0 + sr, sc := -1, -1 + for r := 0; r < m; r++ { + for c := 0; c < n; c++ { + switch classroom[r][c] { + case 'S': + sr, sc = r, c + case 'L': + litterID[r*n+c] = total + total++ + } + } + } + if sr < 0 { + return -1 + } + + full := (1 << total) - 1 + if full == 0 { + return 0 // nothing to clean + } + + // best[cell][mask] is the greatest remaining energy with which that pair + // has already been reached; -1 means it has never been reached. + best := make([][]int, m*n) + for i := range best { + best[i] = make([]int, full+1) + for j := range best[i] { + best[i][j] = -1 + } + } + best[sr*n+sc][0] = energy + + dirs := [4][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} + queue := []classroomState{{r: sr, c: sc, mask: 0, energy: energy}} + + for moves := 0; len(queue) > 0; moves++ { + var next []classroomState + for _, st := range queue { + if st.energy == 0 { + continue // out of fuel and not standing on a reset area + } + for _, d := range dirs { + nr, nc := st.r+d[0], st.c+d[1] + if nr < 0 || nr >= m || nc < 0 || nc >= n { + continue + } + ch := classroom[nr][nc] + if ch == 'X' { + continue + } + + nEnergy := st.energy - 1 + nMask := st.mask + if ch == 'R' { + nEnergy = energy // reset areas refill regardless of level + } else if id := litterID[nr*n+nc]; id >= 0 { + nMask |= 1 << id + } + if nMask == full { + return moves + 1 + } + + idx := nr*n + nc + if best[idx][nMask] >= nEnergy { + continue + } + best[idx][nMask] = nEnergy + next = append(next, classroomState{r: nr, c: nc, mask: nMask, energy: nEnergy}) + } + } + queue = next + } + + return -1 +} diff --git a/problems/3568-minimum-moves-to-clean-the-classroom/solution_test.go b/problems/3568-minimum-moves-to-clean-the-classroom/solution_test.go new file mode 100644 index 0000000..ad20fe0 --- /dev/null +++ b/problems/3568-minimum-moves-to-clean-the-classroom/solution_test.go @@ -0,0 +1,94 @@ +package main + +import "testing" + +func TestSolution(t *testing.T) { + tests := []struct { + name string + classroom []string + energy int + expected int + }{ + { + name: "example 1: detour around an obstacle", + classroom: []string{"S.", "XL"}, + energy: 2, + expected: 2, + }, + { + name: "example 2: reset area refills energy mid-route", + classroom: []string{"LS", "RL"}, + energy: 4, + expected: 3, + }, + { + name: "example 3: no route collects every litter", + classroom: []string{"L.S", "RXL"}, + energy: 3, + expected: -1, + }, + { + name: "edge case: no litter at all needs zero moves", + classroom: []string{"S..", "..."}, + energy: 1, + expected: 0, + }, + { + name: "edge case: single cell grid with only the student", + classroom: []string{"S"}, + energy: 50, + expected: 0, + }, + { + name: "edge case: litter walled off by an obstacle", + classroom: []string{"SXL"}, + energy: 50, + expected: -1, + }, + { + name: "edge case: energy exactly covers the straight walk", + classroom: []string{"S.L"}, + energy: 2, + expected: 2, + }, + { + name: "edge case: one unit short of reaching the litter", + classroom: []string{"S.L"}, + energy: 1, + expected: -1, + }, + { + name: "edge case: reset area makes a long corridor feasible", + classroom: []string{"S.R.L"}, + energy: 2, + expected: 4, + }, + { + name: "edge case: backtracking to collect litter on both sides", + classroom: []string{"LSL"}, + energy: 3, + expected: 3, + }, + { + name: "edge case: backtracking impossible without enough energy", + classroom: []string{"LSL"}, + energy: 2, + expected: -1, + }, + { + name: "edge case: student starts adjacent to the only litter", + classroom: []string{"SL"}, + energy: 1, + expected: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := minMoves(tt.classroom, tt.energy) + if got != tt.expected { + t.Errorf("minMoves(%v, %d) = %v, want %v", tt.classroom, tt.energy, got, tt.expected) + } + }) + } +}