class Solution { private fun wordDifference( lhs: String, rhs: String, ): Int = lhs.zip(rhs).count { (l, r) -> l != r } private enum class VertexState { Unvisited, Enqueued, Done, } private data class Vertex( val label: T, val neighbours: MutableSet> = mutableSetOf(), ) { var distance: Int = Int.MAX_VALUE val parents: MutableSet> = mutableSetOf() var state: VertexState = VertexState.Unvisited private set fun add(neighbour: Vertex) { neighbours.add(neighbour) } fun addParent(parent: Vertex) { parents.add(parent) } fun transition() { state = when (state) { VertexState.Unvisited -> VertexState.Enqueued VertexState.Enqueued -> VertexState.Done VertexState.Done -> error("Cannot transition further!") } } override fun hashCode(): Int = label.hashCode() override fun toString(): String = "$label(${neighbours.map { it.label.toString() }})" override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Vertex<*> if (label != other.label) return false return true } } private class Graph { private val vertices: MutableMap> = mutableMapOf() fun addVertex( src: T, dst: T, ) { val u = vertices.getOrPut(src) { Vertex(src) } val v = vertices.getOrPut(dst) { Vertex(dst) } u.add(v) } fun addSymmetricVertex( src: T, dst: T, ) { addVertex(src, dst) addVertex(dst, src) } private fun processVertex( queue: ArrayDeque>, u: Vertex, ) { u.neighbours.forEach { v -> if (v.state == VertexState.Unvisited) { queue.addLast(v) queue.last().distance = u.distance + 1 queue.last().transition() } else if (u.distance + 1 != v.distance) { return@forEach } v.addParent(u) } u.transition() } fun bfs( src: T, dst: T, ) { if (vertices[src] == null) { return } val queue = ArrayDeque>() queue.addLast(vertices[src]!!) queue.last().distance = 0 queue.last().transition() while (queue.isNotEmpty()) { val u = queue.removeFirst() if (u.label == dst) { break } processVertex(queue, u) } } fun parents(vertex: T): List = vertices[vertex]?.parents?.map(Vertex::label) ?: emptyList() override fun toString(): String = vertices.toString() } private fun constructGraph( src: String, vertices: List, ): Graph { val g = Graph() vertices .filter { wordDifference(src, it) == 1 } .forEach { g.addVertex(src, it) } vertices.indices .flatMap { listOf(it).zip(it + 1 until vertices.size) } .map { (i, j) -> vertices[i] to vertices[j] } .filter { (lhs, rhs) -> wordDifference(lhs, rhs) == 1 } .forEach { (lhs, rhs) -> g.addSymmetricVertex(lhs, rhs) } return g } private fun constructPath( graph: Graph, currentPath: MutableList, paths: MutableList>, ): MutableList> { if (graph.parents(currentPath.last()).isEmpty()) { paths.add(currentPath.reversed().toMutableList()) return paths } for (parent in graph.parents(currentPath.last()).filter { !currentPath.contains(it) }) { currentPath.add(parent) constructPath(graph, currentPath, paths) currentPath.remove(parent) } return paths } fun findLadders( beginWord: String, endWord: String, wordList: List, ): List> { val graph = constructGraph(beginWord, wordList) graph.bfs(beginWord, endWord) return constructPath(graph, mutableListOf(endWord), mutableListOf()).filter { it.count() >= 2 } } } fun main() { val s = Solution() check( s.findLadders("hit", "cog", listOf("hot", "dot", "dog", "lot", "log", "cog")) == listOf( listOf("hit", "hot", "dot", "dog", "cog"), listOf("hit", "hot", "lot", "log", "cog"), ), ) check(s.findLadders("hit", "cog", listOf("hot", "dot", "dog", "lot", "log")) == emptyList>()) check(s.findLadders("hot", "dog", listOf("hot", "dog")) == emptyList>()) check(s.findLadders("hot", "dog", listOf("hot", "dog", "dot")) == listOf(listOf("hot", "dot", "dog"))) }