- 题目链接 leetcode-cn.com/problems/k-…
- 题目描述
- 如果可以通过将 A 中的两个小写字母精确地交换位置 K 次得到与 B 相等的字符串,我们称字符串 A 和 B 的相似度为 K(K 为非负整数)。
给定两个字母异位词 A 和 B ,返回 A 和 B 的相似度 K 的最小值。 (来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/k-similar-strings 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。)
- 限制条件
- 1 <= A.length == B.length <= 20
- A 和 B 只包含集合 {'a', 'b', 'c', 'd', 'e', 'f'} 中的小写字母。
- 思路
- 一般看到输入的规模比较短,比如10, 20,可以向字符串掩码,动态规划方向思考;
- 假如在A,B中相同位置,有相同的字符,那么该位置可以忽略。因为如果要对该位置进行交换,必须也交换来一个相同的字符(要和B[i]一致)。而我们要找到更小的K,所以不交换会得到一个更好的结果。
- 我们思考另一个简单的问题,符合什么条件的两个字符串,可以通过交换(不论次数),从A得到B呢?答案是,只要这两个字符串的各个字母的数量是一致的。比如字符串abc 和 bca,进过两次交换,就可以从A变成B。abd和bca,无论怎么交换都无法从A变成B;
- 对于上面的例子abc,bca。为什么是交换了两次呢?如果把两个字符串各个位置对应起来,a->b, b -> c, c -> a, 使用 -> 是有意的。我们发现这正好组成了一个环,而环的长度是3,交换的次数是3 - 1。 进而,我们可以得到一个结论,对于一个长度为n的环,交换的次数是n-1;
- 回到题目,对于字符串A,B(长度为n),最多交换n-1次,就可以从A变成B。我们要得到最小的交换次数K, 就需要增加更多的环,因为每增加一个,就可以减少一次交换;
- 我们把所有的组合可能映射到(1 到 1 << n) 的状态,然后检查该组合是否可以组成一个环。把这样的状态保存下来,并按照状态内1的个数排序(这样的目的是为了在处理后面的状态时,它所依赖的状态已经被正确处理了)。假设对于状态 x, dp[x] 表示该状态的最大环数;dp[y] = max(dp[x] + dp[z], 如果 x | z = y, 并且 x & z = 0);最终的答案是 dp[1 << n - 1]。
- 时间复杂度,至少是 n * (2**n), (dp[x]的计算的复杂度我算不出来,^-^)
- 代码
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.Sorting
object Solution {
def kSimilarity(A: String, B: String): Int = {
val as = A.toCharArray
val bs = B.toCharArray
var n = 0
var i = 0
while (i < as.length) {
if (as(i) != bs(i)) {
as(n) = as(i)
bs(n) = bs(i)
n += 1
}
i += 1
}
if (n == 0) {
0
} else {
val N = 1 << n
val cnt = Array.ofDim[Int](6)
val groupBuf = ArrayBuffer.empty[(Int, Int)]
var mask = 1
while (mask < N) {
(0 until 6).foreach(j => cnt(j) = 0)
var l = 0
i = 0
while (i < n) {
if ((mask & (1 << i)) > 0) {
l += 1
cnt(as(i) - 'a') += 1
cnt(bs(i) - 'a') -= 1
}
i += 1
}
val can = cnt.forall(_ == 0)
if (can) {
groupBuf += mask -> l
}
mask += 1
}
val groups = groupBuf.toArray
Sorting.quickSort(groups)(Ordering.fromLessThan((a, b) => a._2 < b._2))
val ii = mutable.Map.empty[Int, Int].withDefaultValue(-1)
(0 until groups.length).foreach(i => ii += groups(i)._1 -> i)
val dp = Array.fill(groups.length)(1)
i = 0
while (i < groups.length) {
val cur = groups(i)
val mask = cur._1
var j = 0
while (j < i) {
val tmp = groups(j)
val first = tmp._1
if ((mask & first) == first) {
val second = mask ^ first
val k = ii(second)
if (k >= 0) {
dp(i) = dp(i) max (dp(j) + dp(k))
}
}
j += 1
}
i += 1
}
n - dp(groups.length - 1)
}
}
}