186 lines
5.2 KiB
Kotlin
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")))
|
|
}
|