LeetCode 1643. 第 K 条最小指令

康托展开

一开始无脑枚举全排列, 果断超时, 还是得看看如果降低计算量。

题目destination = [2,3], 相当于2个V, 3个H, 输出全排列去重后的对应位置字典序列内容。

忽略去重

则问题为全排列, 所有可能为:

\[(\sum destination)! = (2+3)! = 5! \]

k恰好为康托展开结果+1, 直接逆向还原排列。

为了偷懒, 下标从最大值递减, k相比原值-1。
HHVHV对应的康托展开为:

\[k = \sum_{i=4}^{1} (n_i*i!) \]

\(n_i\)为后续小于当前元素的数量, 由于题目只有2种元素, 将原始表达式稍加改造

定义\(v_i\) \(h_i\)为从当前位置往右, 剩余的VH对应数量, 其总量分别定义为V H

则在无去重情况下的表达式为:

\[\begin{aligned} k &= \sum (h_i*i!) \\ &= \sum (h_i*(v_i+h_i-1)!) \\ \end{aligned} \]

其中i ∈ (0, V+H), 且 \(n_i\) = V

H不存在小于它的项, 因此只关注V

考虑去重

此时表达式与题意只相差相同元素的去重, 根据排列组合知识, \(v_i\)V提供\(v_i!\)种排列, \(h_i\)H提供\(h_i!\)种排列。

将上述原始的康托展开去掉重复项就能得到最终表达式:

(为了观感, \(x_i\)替换为\(x\))

\[\begin{aligned} k &= \sum \frac{h*(v+h-1)!}{v!*h!} \\ &= \sum \frac{h* \Pi_{j=h+1}^{v+h-1} j}{v!} \\ &= \sum \frac{\Pi_{j=h}^{v+h-1} j}{v!} \end{aligned} \]

还原时从前往后, 计算将V放置在当前位置后提供的k值是否溢出, 生成最终输出。过程与逆康托展开一致。

中途连乘计算可能越界, 没想出足够优雅的处理方法, 转Double摆烂了。

Kotlin

private val cache = Array(30) { Array<Double?>(30) { null } }
private fun pai(st: Int, ed: Int): Double = cache[st][ed] ?: run {
    val v = if (st == ed) st.toDouble() else pai(st, ed - 1) * ed.toDouble()
    cache[st][ed] = v
    return@run v
}

class Solution {

    fun kthSmallestPath(destination: IntArray, k: Int): String {
        var currentK = 0
        return String(CharArray(destination.sum()) {
            val (v, h) = destination
            if (v <= 0) return@CharArray 'H'
            if (h <= 0) return@CharArray 'V'

            val n = (pai(h, v + h - 1) / pai(1, v)).toInt()
            if (n + currentK < k) {
                currentK += n
                destination[0] -= 1
                return@CharArray 'V'
            }

            destination[1] -= 1
            return@CharArray 'H'
        })
    }
}
posted @ 2023-04-25 22:15  Simon_X  阅读(15)  评论(0编辑  收藏  举报