由子序列构造的最长回文串的长度

标签: 字符串 动态规划

难度: Hard

给你两个字符串 word1word2 ,请你按下述方法构造一个字符串:

  • word1 中选出某个 非空 子序列 subsequence1
  • word2 中选出某个 非空 子序列 subsequence2
  • 连接两个子序列 subsequence1 + subsequence2 ,得到字符串。

返回可按上述方法构造的最长 回文串长度 。如果无法构造回文串,返回 0

字符串 s 的一个 子序列 是通过从 s 中删除一些(也可能不删除)字符而不更改其余字符的顺序生成的字符串。

回文串 是正着读和反着读结果一致的字符串。

 

示例 1:

输入:word1 = "cacb", word2 = "cbba"
输出:5
解释:从 word1 中选出 "ab" ,从 word2 中选出 "cba" ,得到回文串 "abcba" 。

示例 2:

输入:word1 = "ab", word2 = "ab"
输出:3
解释:从 word1 中选出 "ab" ,从 word2 中选出 "a" ,得到回文串 "aba" 。

示例 3:

输入:word1 = "aa", word2 = "bb"
输出:0
解释:无法按题面所述方法构造回文串,所以返回 0 。

 

提示:

  • 1 <= word1.length, word2.length <= 1000
  • word1word2 由小写英文字母组成

Submission

运行时间: 868 ms

内存: 16.7 MB

class Solution:
    def longestPalindrome(self, word1: str, word2: str) -> int:
        w = word1 + word2
        n = len(w)
        res = 0
        dp = [0] * n
        for i in range(n - 1, -1, -1):
            newDp = [0] * n
            newDp[i] = 1
            for j in range(i + 1, n):
                if w[i] == w[j]:
                    newDp[j] = dp[j - 1] + 2
                    if i < len(word1) and j >= len(word1):
                        res = max(res,newDp[j])
                else:
                    newDp[j] = max(dp[j],newDp[j - 1])
            dp = newDp

        return res

Explain

这个题解使用动态规划来寻找连接后的字符串中最长的回文子序列的长度。首先,将两个字符串word1和word2连接成一个新字符串w。然后利用一个二维动态规划的技巧,但只使用一维数组dp来减少空间复杂度。遍历这个合并后的字符串,利用两层循环,外层从后向前,内层从外层的当前索引向后,来更新dp数组。若字符w[i]和w[j]相同,则dp[j]被更新为dp[j-1]加2;否则,dp[j]为dp[j]和dp[j-1]的最大值。特别地,如果i属于word1的部分,而j属于word2的部分,且形成了新的回文字符串,则更新最大长度res。最后,返回res。

时间复杂度: O(n^2)

空间复杂度: O(n)

class Solution:
    def longestPalindrome(self, word1: str, word2: str) -> int:
        w = word1 + word2  # 连接两个字符串
        n = len(w)  # 计算新字符串的长度
        res = 0  # 初始化最长回文长度为0
        dp = [0] * n  # 初始化dp数组
        for i in range(n - 1, -1, -1):  # 从后向前遍历
            newDp = [0] * n  # 创建新的dp数组
            newDp[i] = 1  # 单字符总是回文
            for j in range(i + 1, n):  # 从i+1到n遍历
                if w[i] == w[j]:  # 如果字符相等
                    newDp[j] = dp[j - 1] + 2  # 更新dp[j]
                    if i < len(word1) and j >= len(word1):  # 检查是否跨越了word1和word2
                        res = max(res,newDp[j])  # 更新最大回文长度
                else:
                    newDp[j] = max(dp[j],newDp[j - 1])  # 选择最大的dp值
            dp = newDp  # 更新dp数组

        return res  # 返回最长回文长度

Explore

算法通过维护一个动态更新的dp数组来确保每次比较都取最长可能的回文长度。每次对于字符w[i]和w[j]相等的情况,dp[j]会被更新为dp[j-1]加2,这种方法是基于回文串的对称性质,并且通过不断比较和取较大值来确保任何时刻dp[j]存储的都是到目前为止最长的回文子序列长度。因此,通过这种方式,我们能够确保找到的是最长的回文子序列,而不是任意一个回文子序列。

当w[i] != w[j]时,说明当前的i和j位置的字符不能形成回文对,因此需要考虑不包含w[i]或w[j]的子序列。dp[j]代表了包含w[j]但不包含w[i]的最长回文序列长度,而newDp[j-1]代表了包含w[i]但不包含w[j]的最长回文序列长度。通过取这两者的最大值,算法能够确保至少一个字符(w[i]或w[j])被排除在外时,仍然能够追踪到目前为止的最长回文序列。这样的更新策略确保了在每一步都不丢失可能的最长回文长度。

在初始化newDp数组时,设置newDp[i] = 1是因为任何单个字符本身就是一个长度为1的回文串。这种初始化是基于回文串定义的基本性质 —— 单个字符总是对称的,因此总是一个回文。设置newDp[i] = 1提供了动态规划过程中的基础情况,即每个字符被视为最短的回文子序列。

题解中的这个条件是为了确保计算的回文长度是由两个不同的输入字符串word1和word2的字符组成的。这一条件确保了回文串跨越了这两个字符串的边界,使得这个回文串确实是连接后字符串的子序列,并且利用了两个字符串的元素。如果没有这个条件,算法可能会错误地计算只来自word1或只来自word2的回文长度,而忽略了题目要求的通过连接两个字符串构造回文的目的。