Sliding Puzzle - Graph / Implicit Graph

Isn’t the serialization and deserialization O(1) time complexity? We have a fixed size of 2x3, not a variable size of n.

Edit: since we have a fixed size, wouldn’t we also have a fixed number of combinations? So O(1) space and time complexity? I agree that if the number of tiles was variable, time complexity would be O(n * n!). If that were the case, wouldn’t space complexity also be O(n * n!)?

def is_valid_hash(hashed_mat):
    return hashed_mat.startswith("123450")

def hash_mat(mat):
    def gen_mat():
        for y in range(2):
            for x in range(3):
                yield str(mat[y][x])
    return ''.join(gen_mat())

def get_neighbors(mat):
    empty_y, empty_x = next((y, x) for y in range(2) for x in range(3) if mat[y][x] == 0)
    for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
        new_y, new_x = empty_y + dy, empty_x + dx
        if 0 <= new_y < 2 and 0 <= new_x < 3:
            rst_mat = deepcopy(mat)
            rst_mat[empty_y][empty_x], rst_mat[new_y][new_x] = rst_mat[new_y][new_x], rst_mat[empty_y][empty_x]
            yield rst_mat

def num_steps(init_pos: List[List[int]]) -> int:
    _mat = hash_mat(init_pos)
    if is_valid_hash(_mat): 
        return 0
    visited = set([_mat])
    queue = deque([deepcopy(init_pos)])
    steps = 0
    while queue:
        n = len(queue)
        for _ in range(n):
            mat = queue.popleft()
            for neighbor_mat in get_neighbors(mat):
                _mat = hash_mat(neighbor_mat)
                if _mat in visited:
                    continue
                if is_valid_hash(_mat):
                    return steps + 1
                visited.add(_mat)
                queue.append(neighbor_mat)
        steps += 1
    return -1

My clean solution.

from typing import List
from collections import deque 

class State:
    def __init__(self, pos, row=None, col=None):
        self.pos = pos
        self.row = row
        self.col = col
        
        if not row and not col:
            self.row, self.col = self.get_row_col(pos)
            
    def get_row_col(self, pos):
        for i in range(len(pos)):
            for j in range(len(pos[i])):
                if pos[i][j] == 0:
                    return (i, j)
    
    def __eq__(self, other):
        return self.pos == other.pos
    
    def __hash__(self):
        return hash(str(self.pos))
    
    def get_next_states(self):
        row_delta = [-1, 0, 1, 0]
        col_delta = [0, 1, 0, -1]

        result = []
        
        for i in range(len(row_delta)):

            row = self.row + row_delta[i]
            col = self.col + col_delta[i]

            if 0 <= row < len(self.pos) and 0 <= col < len(self.pos[0]):
                temp = [row_list[:] for row_list in self.pos] # deep copy 
                
                val = temp[row][col]
                temp[self.row][self.col] = val
                temp[row][col] = 0 
                
                state = State(temp, row, col)

                result.append(state)

        return result
    
    
def num_steps(init_pos: List[List[int]]) -> int:
    def bfs(): 
        init = State(init_pos)
        end_state = State([[1,2,3],[4,5,0]])
        q = deque([init])
        seen = set([init])
        steps = 0 
        while q:
            n = len(q)
            for _ in range(n):
                state = q.popleft()
                if state == end_state:
                    return steps
                next_states = state.get_next_states()
                for next_state in next_states:
                    if next_state in seen:
                        continue
                    seen.add(next_state)
                    q.append(next_state)
            steps += 1    
        return -1
    return bfs()