Skip to main content
Back to problems
#3568
Medium Algorithms

Minimum moves to clean the classroom

Array Hash Table Bit Manipulation Breadth-First Search Matrix
26.5% acceptance
Feb 25, 2026
125
15
m x n grid classroom with 'S' (start), 'L' (litter), 'R' (reset), 'X' (obstacle), '.' (empty). Student starts with full energy. Each move costs 1. On 'R', energy resets to max. Return minimum moves to collect all litter, or -1 if impossible.

Solution

Rust
Time O(n * m)
Space O(n * m)
LeetCode
solution.rs
impl Solution {
  pub fn min_moves(classroom: Vec<String>, energy: i32) -> i32 {
    let m = classroom.len();
    let n = classroom[0].len();
    let energy = energy as usize;
    let grid: Vec<Vec<u8>> = classroom.iter().map(|s| s.as_bytes().to_vec()).collect();

    // Find start, litter positions
    let mut start = (0usize, 0usize);
    let mut litter: Vec<(usize, usize)> = vec![];
    for r in 0..m {
      for c in 0..n {
        match grid[r][c] {
          b'S' => start = (r, c),
          b'L' => litter.push((r, c)),
          _ => {}
        }
      }
    }
    let nl = litter.len();
    if nl == 0 { return 0; }

    // BFS between all key positions: precompute dist[src][dst] = min moves, and best_energy[src][dst]
    // For BFS from a source, track max energy on arrival (to maximize remaining energy = minimize cost)
    // State: (row, col, energy_remaining, litter_mask)
    // BFS/Dijkstra with state (r, c, energy, mask)
    // energy up to 50, positions up to 400, mask up to 2^10=1024 -> 50*400*1024 = 20M states (feasible)

    // Encode positions
    let pos_id = |r: usize, c: usize| r * n + c;
    let nr_pos = m * n;

    // State: (pos, energy, mask) -> min moves
    // Use BFS since each move costs 1 (uniform cost)
    use std::collections::VecDeque;

    let start_mask = 0u32;
    let start_pos = pos_id(start.0, start.1);
    let full_mask = (1u32 << nl) - 1;

    // dist[pos][energy][mask] = min moves or infinity
    // Too large: 400 * 51 * 1024 = 20M entries
    let mut dist = vec![vec![vec![i32::MAX; 1 << nl]; energy + 1]; nr_pos];
    dist[start_pos][energy][start_mask as usize] = 0;

    let mut queue = VecDeque::new();
    queue.push_back((start_pos, energy, start_mask));

    let dirs: [(i32, i32); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];

    while let Some((pos, e, mask)) = queue.pop_front() {
      let moves = dist[pos][e][mask as usize];
      if mask == full_mask {
        return moves;
      }
      if e == 0 { continue; }
      let r = pos / n;
      let c = pos % n;
      for &(dr, dc) in &dirs {
        let nr = r as i32 + dr;
        let nc = c as i32 + dc;
        if nr < 0 || nr >= m as i32 || nc < 0 || nc >= n as i32 { continue; }
        let nr = nr as usize;
        let nc = nc as usize;
        if grid[nr][nc] == b'X' { continue; }
        let new_pos = pos_id(nr, nc);
        let new_e = if grid[nr][nc] == b'R' { energy } else { e - 1 };
        let mut new_mask = mask;
        if grid[nr][nc] == b'L' {
          for (li, &lp) in litter.iter().enumerate() {
            if lp == (nr, nc) {
              new_mask |= 1 << li;
            }
          }
        }
        let new_moves = moves + 1;
        if dist[new_pos][new_e][new_mask as usize] == i32::MAX {
          dist[new_pos][new_e][new_mask as usize] = new_moves;
          queue.push_back((new_pos, new_e, new_mask));
        }
      }
    }

    // Check if full_mask achieved
    for p in 0..nr_pos {
      for e in 0..=energy {
        if dist[p][e][full_mask as usize] != i32::MAX {
          return dist[p][e][full_mask as usize];
        }
      }
    }
    -1
  }
}