diff --git a/problems/word-ladder-ii.kt b/problems/word-ladder-ii.kt new file mode 100644 index 0000000..ba22903 --- /dev/null +++ b/problems/word-ladder-ii.kt @@ -0,0 +1,157 @@ +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"))) +}