Structured for clarity and two optimizations - caching the position of the zero cell so we don’t need to traverse to find it (except the initial state), and no need for a map, just a set using a simple string serialization of the state.
Complexity is O(n + n!)
whereas the provided solution is actually O(3n * n!)
due to zero-finding traversal and deserialization, which my solution doesn’t require.
from typing import List
from collections import deque
import copy
def num_steps(init_pos: List[List[int]]) -> int:
dirs = [(-1, 0), (1, 0), (0, 1), (0, -1)]
target = [[1,2,3],[4,5,0]]
if init_pos == target:
return 0
def get_neighbors(r, c):
for dr, dc in dirs:
r1, c1 = r+dr, c+dc
if 0 <= r1 < len(init_pos) and 0 <= c1 < len(init_pos[0]):
yield r1, c1
def forward(state_, o_r, o_c, t_r, t_c):
next_state = copy.deepcopy(state_)
next_state[o_r][o_c] = state[t_r][t_c]
next_state[t_r][t_c] = 0
return next_state
# find the starting open slot
start_open = (0, 0)
for i in range(len(init_pos)):
for j in range(len(init_pos[0])):
if init_pos[i][j] == 0:
start_open = (i, j)
queue = deque([(init_pos, start_open, 0)])
states : set[str] = set()
while queue:
state, zero_pos, distance = queue.popleft()
z_r, z_c = zero_pos
for r1, c1 in get_neighbors(z_r, z_c):
next_state = forward(state,
z_r, z_c,
r1, c1)
if next_state == target:
return distance + 1
state_repr = str(next_state)
if state_repr in states:
continue
states.add(state_repr)
# we already know what the position of the new zero is,
# since it was the tile we moved to get to the new state.
# putting in the queue alongside the state to avoid recalculating it
queue.append((next_state, (r1, c1), distance + 1))
return -1