LeetCode/kt/word-ladder-ii.kt
Matej Focko aaaebf1d52
style(kt): reformat the files
Signed-off-by: Matej Focko <me@mfocko.xyz>
2024-05-17 18:23:38 +02:00

186 lines
5.2 KiB
Kotlin

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")))
}