Problem. Fifteen numbered tiles and one blank in a frame; a move slides a tile into the blank. Which positions can be solved, and how to solve them optimally? Sam Loyd’s famous “14–15” prize (the solved board with 14 and 15 swapped) is impossible.

FactValue
solvable iff ( odd) inversions even; ( even) inversions + blank row has the parity of slidingSolvable in Parity, Coloring and Mod Observations
reachable positions of an board ()
8-puzzle () positions, hardest need 31 moves (2 positions)
15-puzzle () positions, hardest need 80 moves (Brüngger et al. 1999; Korf’s complete BFS 2008)
optimal solvingIDA* with an admissible heuristic: Manhattan distance + linear conflicts (fine for ), pattern databases (for )
finding some solutionlayer-by-layer method (place the top row, then the left column, recurse): moves per tile, in total on
finding a shortest solution of the puzzleNP-hard (Ratner and Warmuth, 1990)
// R x C sliding puzzle, row-major, 0 = blank, goal 1..RC-1 then the blank. Optimal move count by IDA*.
// Heuristic: Manhattan distance + 2 per tile that must leave its goal row (or column) to resolve a linear conflict.
struct Sliding {
    int R, C; vector<int> b;
    int h() const {
        int s = 0;
        for (int i = 0; i < R * C; i++) if (b[i]) { int g = b[i] - 1; s += abs(i / C - g / C) + abs(i % C - g % C); }
        auto mustLeave = [](const vector<int>& v) {          // |v| - LIS(v): tiles that cannot keep their order
            vector<int> d; for (int x : v) { auto it = lower_bound(all(d), x); if (it == d.end()) d.push_back(x); else *it = x; }
            return sz(v) - sz(d);
        };
        for (int r = 0; r < R; r++) {
            vector<int> v; for (int c = 0; c < C; c++) { int t = b[r * C + c]; if (t && (t - 1) / C == r) v.push_back((t - 1) % C); }
            s += 2 * mustLeave(v);
        }
        for (int c = 0; c < C; c++) {
            vector<int> v; for (int r = 0; r < R; r++) { int t = b[r * C + c]; if (t && (t - 1) % C == c) v.push_back((t - 1) / C); }
            s += 2 * mustLeave(v);
        }
        return s;
    }
    bool dfs(int z, int prev, int g, int bound, int& next) {
        int hv = h(), f = g + hv;
        if (f > bound) { next = min(next, f); return false; }
        if (hv == 0) return true;                             // h = 0 only at the goal
        for (int y : {z - C, z + C, z - 1, z + 1}) {
            if (y < 0 || y >= R * C || (abs(y - z) == 1 && y / C != z / C) || y == prev) continue;
            swap(b[z], b[y]); bool ok = dfs(y, z, g + 1, bound, next); swap(b[z], b[y]);
            if (ok) return true;
        }
        return false;
    }
    int solve() {                                             // call only on solvable boards
        int z = int(find(all(b), 0) - b.begin()), bound = h();
        while (true) { int next = INT_MAX; if (dfs(z, -1, 0, bound, next)) return bound; bound = next; }
    }
};