mirror of
https://gitlab.com/mfocko/LeetCode.git
synced 2024-11-14 18:07:32 +01:00
158 lines
4.9 KiB
Kotlin
158 lines
4.9 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")))
|
||
|
}
|