LeetCode/problems/word-ladder-ii.kt

158 lines
4.9 KiB
Kotlin
Raw Normal View History

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<T>(
val label: T, val neighbours: MutableSet<Vertex<T>> = mutableSetOf()
) {
var distance: Int = Int.MAX_VALUE
val parents: MutableSet<Vertex<T>> = mutableSetOf()
var state: VertexState = VertexState.Unvisited
private set
fun add(neighbour: Vertex<T>) {
neighbours.add(neighbour)
}
fun addParent(parent: Vertex<T>) {
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<T> {
private val vertices: MutableMap<T, Vertex<T>> = 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<Vertex<T>>, u: Vertex<T>) {
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<Vertex<T>>()
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<T> = vertices[vertex]?.parents?.map(Vertex<T>::label) ?: emptyList()
override fun toString(): String = vertices.toString()
}
private fun constructGraph(src: String, vertices: List<String>): Graph<String> {
val g = Graph<String>()
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<String>, currentPath: MutableList<String>, paths: MutableList<MutableList<String>>
): MutableList<MutableList<String>> {
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<String>
): List<List<String>> {
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<List<String>>())
check(s.findLadders("hot", "dog", listOf("hot", "dog")) == emptyList<List<String>>())
check(s.findLadders("hot", "dog", listOf("hot", "dog", "dot")) == listOf(listOf("hot", "dot", "dog")))
}