class Solution {
    private fun precomputeFrom(
        words: List<String>,
        s: String,
        dp: Map<Int, List<String>>,
        start: Int,
    ): List<String> {
        val valid = mutableListOf<String>()

        (start..<s.length)
            .map { end -> end to s.substring(start..end) }
            .filter { (_, word) -> words.contains(word) }
            .forEach { (end, word) ->
                when {
                    end == s.length - 1 -> valid.add(word)
                    else -> {
                        dp[end + 1]?.forEach { suffix ->
                            valid.add("$word $suffix")
                        }
                    }
                }
            }

        return valid
    }

    fun wordBreak(
        s: String,
        wordDict: List<String>,
    ): List<String> {
        val dp = mutableMapOf<Int, List<String>>()

        s.indices.reversed().forEach { start ->
            dp[start] = precomputeFrom(wordDict, s, dp, start)
        }

        return dp.getOrDefault(0, emptyList())
    }
}

fun main() {
    val s = Solution()

    check(
        s.wordBreak("catsanddog", listOf("cat", "cats", "and", "sand", "dog")).sorted() ==
            listOf(
                "cats and dog", "cat sand dog",
            ).sorted(),
    )
    check(
        s.wordBreak("pineapplepenapple", listOf("apple", "pen", "applepen", "pine", "pineapple")).sorted() ==
            listOf(
                "pine apple pen apple", "pineapple pen apple", "pine applepen apple",
            ).sorted(),
    )
    check(
        s.wordBreak("catsandog", listOf("cats", "dog", "sand", "and", "cat")) == emptyList<String>(),
    )
}