最长公共子串

Posted on By ᵇᵒ

LCS

LeetCode 1143

Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

  • For example, “ace” is a subsequence of “abcde”.

A common subsequence of two strings is a subsequence that is common to both strings.

动态规划解法1

fun longestCommonSubsequence(text1: String, text2: String): Int {
    val length1 = text1.length
    val length2 = text2.length
    val chars1 = text1.toCharArray()
    val chars2 = text2.toCharArray()
    //  dp[i][j] 表示 text1 的前 i 个元素与 text2 的前 j 个元素的最长公共子序列的长度
    val dp = Array(length1 + 1) { IntArray(length2 + 1) }
    chars1.forEachIndexed { i, c1 ->
        chars2.forEachIndexed { j, c2 ->
            dp[i + 1][j + 1] = if (c1 == c2) dp[i][j] + 1 else dp[i][j + 1].coerceAtLeast(dp[i + 1][j])
        }
    }
    return dp[length1][length2]
}

位运算解法2

@OptIn(ExperimentalUnsignedTypes::class)
fun longestCommonSubsequence(text1: String, text2: String): Int {
    var A = text1
    var B = text2
    // 不交换也是 ok 的,交换可以减少循环、提高运行效率
    if (A.length < B.length) A = B.also { B = A }
    val m = A.length
    val n = B.length
    if (m == 0 || n == 0) return 0

    val w = (m + 63) shr 6
    val S = Array(256) { ULongArray(w) }
    var set = 1UL
    var j = 0
    for (i in 0 until m) {
        S[A[i].code][j] = S[A[i].code][j] or set
        set = set shl 1
        if (set == 0UL) {
            set = 1UL
            j++
        }
    }

    val L = ULongArray(w)
    for (i in 0 until n) {
        var b1 = 1UL
        var b2 = 0UL
        for (j in 0 until w) {
            val X = L[j] or S[B[i].code][j]
            val c = L[j] shr 63
            val V = X - ((L[j] shl 1) or (b1 + b2))
            b1 = c
            b2 = if (V > X) 1UL else 0UL
            L[j] = X and V.inv() // 另一种方式 L[j] = V.xor(X).and(X)
        }
    }
    return L.fold(0) { acc, i -> acc + i.countOneBits() }
}

LCS of three strings

Given two strings text1 text2 and text3, return the length of their longest common subsequence. If there is no common subsequence, return 0.

fun longestCommonSubsequence(text1: String, text2: String, text3: String): Int {
    val length1 = text1.length
    val length2 = text2.length
    val length3 = text3.length
    val chars1 = text1.toCharArray()
    val chars2 = text2.toCharArray()
    val chars3 = text2.toCharArray()
    //  dp[i][j][k] 表示 text1 的前 i 个元素、 text2 的前 j 个元素与 text3 的前 k 个元素的最长公共子序列的长度
    val dp = Array(length1 + 1) { Array(length2 + 1) { IntArray(length3 + 1) } }
    chars1.forEachIndexed { i, c1 ->
        chars2.forEachIndexed { j, c2 ->
            chars3.forEachIndexed { k, c3 ->
                dp[i + 1][j + 1][k + 1] = if (c1 == c2 && c2 == c3) {
                    dp[i][j][k] + 1
                } else {
                    maxOf(dp[i][j + 1][k + 1], dp[i + 1][j][k + 1], dp[i + 1][j + 1][k])
                }
            }
        }
    }
    return dp[length1][length2][length3]
}

Footnote