康托展开
一开始无脑枚举全排列, 果断超时, 还是得看看如果降低计算量。
题目destination = [2,3], 相当于2个V
, 3个H
, 输出全排列去重后的对应位置字典序列内容。
忽略去重
则问题为全排列, 所有可能为:
\[(\sum destination)! = (2+3)! = 5! \]k
恰好为康托展开结果+1, 直接逆向还原排列。
为了偷懒, 下标从最大值递减, k相比原值-1。
则HHVHV
对应的康托展开为:
\(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'
})
}
}
标签:1643,ed,sum,destination,st,指令,aligned,LeetCode,康托
From: https://www.cnblogs.com/Simon-X/p/17354118.html