两个回文子字符串长度的最大乘积

标签: 字符串 哈希函数 滚动哈希

难度: Hard

给你一个下标从 0 开始的字符串 s ,你需要找到两个 不重叠的回文 子字符串,它们的长度都必须为 奇数 ,使得它们长度的乘积最大。

更正式地,你想要选择四个整数 i ,j ,k ,l ,使得 0 <= i <= j < k <= l < s.length ,且子字符串 s[i...j] 和 s[k...l] 都是回文串且长度为奇数。s[i...j] 表示下标从 i 到 j 且 包含 两端下标的子字符串。

请你返回两个不重叠回文子字符串长度的 最大 乘积。

回文字符串 指的是一个从前往后读和从后往前读一模一样的字符串。子字符串 指的是一个字符串中一段连续字符。

示例 1:

输入:s = "ababbb"
输出:9
解释:子字符串 "aba" 和 "bbb" 为奇数长度的回文串。乘积为 3 * 3 = 9 。

示例 2:

输入:s = "zaaaxbbby"
输出:9
解释:子字符串 "aaa" 和 "bbb" 为奇数长度的回文串。乘积为 3 * 3 = 9 。

提示:

  • 2 <= s.length <= 105
  • s 只包含小写英文字母。

Submission

运行时间: 668 ms

内存: 26.2 MB

class Solution:
    def maxProduct(self, s: str) -> int:
        n = len(s)
        # print(n)
        r = [0] * n
        l = [0] * n
        l[0] = 1
        def manchester(s):
            n = len(s)
            p = [0] * n
            c = r = m = 0
            for i in range(n):
                if r > i:
                    p[i] = min(r - i, p[2*c - i])
                else:
                    p[i] = 0
                while i + 1 + p[i] < n and i - 1 - p[i] >= 0 and s[i + 1 + p[i]] == s[i - 1 - p[i]]:
                    p[i] += 1
                if i + p[i] > r:
                    c, r = i, i + p[i]
                if p[i] > m:
                    m = p[i]
            return p

        res = manchester(s)
        # print(res)
        j = 1
        for i in range(1, n): 
            while j <= res[i] + i:
                l[j] = 2 * (j - i) + 1
                j += 1
            # l[res[i] + i] = max(l[res[i] + i], 2 * res[i] + 1)
        for i in range(1, n):
            l[i] = max(l[i - 1], l[i])
        
        j = n - 2
        r[n - 1] = 1
        for i in range(n - 2, -1, -1):
            while j >= i - res[i]:
                r[j] = 2 * (i - j) + 1
                j -= 1
            # r[i - res[i]] = max(r[i - res[i]], 2 * res[i] + 1)
        for i in range(n - 2, -1, -1):
            r[i] = max(r[i + 1], r[i])
        
        # print(l)
        # print(r)
        ans = 0
        for i in range(n - 1):
            ans = max(ans, l[i] * r[i + 1])
        return ans
            
        

Explain

这道题目使用了马拉车算法(Manacher's algorithm),首先求解每个字符为中心的最大回文半径。利用这个半径数组,我们可以计算出每个位置最大的奇数长度回文串的长度。接着,使用两个辅助数组 l 和 r,l[i] 表示从字符串起点到位置 i 最长的奇数长度回文串,r[i] 表示从位置 i 到字符串结尾最长的奇数长度回文串。最后,我们遍历字符串,每个位置尝试将其分成两部分,左边的部分使用 l 数组,右边的部分使用 r 数组,并尝试找出两个部分的长度乘积的最大值。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def maxProduct(self, s: str) -> int:
        n = len(s)
        r = [0] * n  # 存储从每个位置开始的最大奇数长度回文串
        l = [0] * n  # 存储到每个位置为止的最大奇数长度回文串
        l[0] = 1  # 初始化第一个字符自身是长度为1的回文串
        def manchester(s):
            n = len(s)
            p = [0] * n  # 存储每个位置的最大回文半径
            c = r = m = 0  # 中心,当前右边界,和最大回文半径
            for i in range(n):
                if r > i:
                    p[i] = min(r - i, p[2*c - i])
                while i + 1 + p[i] < n and i - 1 - p[i] >= 0 and s[i + 1 + p[i]] == s[i - 1 - p[i]]:
                    p[i] += 1
                if i + p[i] > r:
                    c, r = i, i + p[i]
            return p

        res = manchester(s)  # 应用马拉车算法
        j = 1
        for i in range(1, n): 
            while j <= res[i] + i:
                l[j] = 2 * (j - i) + 1
                j += 1
        for i in range(1, n):
            l[i] = max(l[i - 1], l[i])

        j = n - 2
        r[n - 1] = 1
        for i in range(n - 2, -1, -1):
            while j >= i - res[i]:
                r[j] = 2 * (i - j) + 1
                j -= 1
        for i in range(n - 2, -1, -1):
            r[i] = max(r[i + 1], r[i])

        ans = 0
        for i in range(n - 1):
            ans = max(ans, l[i] * r[i + 1])  # 查找最大的乘积
        return ans

Explore

马拉车算法通过中心扩展的方式检测回文边界。算法中有两个关键变量:`c`(当前回文中心)和`r`(当前回文的右边界)。对于每个字符位置`i`,若`i`在`r`内,则利用对称性质初始化`p[i]`。随后,算法尝试向两侧扩展,比较位置`i+p[i]+1`和`i-p[i]-1`的字符是否相等,若相等则增加`p[i]`。这个过程确保了对于每个位置,算法都精确地检测到了以该位置为中心的最长回文的边界。

当`r > i`时,表示当前位置`i`位于一个已知的回文子串内。使用`p[2*c - i]`(即`i`关于中心`c`的对称点的回文半径)来尝试初始化`p[i]`。由于回文的对称性,`p[i]`至少可以取到`p[2*c - i]`的值,但是不能超过`r - i`(即`i`到已知回文右边界的距离),因此取两者的最小值作为初始值。这样做可以有效利用已知信息加速算法的执行,避免不必要的重复计算。

公式`2 * (j - i) + 1`用于计算以位置`i`为中心的奇数长度回文串的长度。这里,`j - i`是回文半径(从中心到边界的距离)。由于回文串以中心对称,实际长度需要将左半部分(`j - i`)和右半部分(`j - i`)长度相加,再加上中心位置的1个字符,因此总长度为`2 * (j - i) + 1`。

两个独立的循环确保l数组和r数组能够完整地计算出所有位置的最大奇数长度回文串。若在一个循环中同时更新,由于l数组和r数组的更新依赖于不同方向的累积最大值(l数组依赖之前的值,r数组依赖之后的值),可能会导致部分数据未能正确更新。分开两个循环可以简化逻辑,确保每个数组在更新前都已经得到了需要的全部信息。