列表

详情


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

给你一个下标从 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 。

 

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
class Solution { public: long long maxProduct(string s) { } };

python3 解法, 执行用时: 1328 ms, 内存消耗: 34.7 MB, 提交时间: 2023-07-12 10:20:29

class Manacher:
    '''
    马拉车算法O(n)
    计算字符串的所有回文信息
    https://oi-wiki.org/string/manacher/
    '''
    def __init__(self,s):
        self.s = s
        self.n = len(s)
    
    def get_odd(self):
        '''
        获取每个中心点的奇回文半径
        '''
        # Python Version
        d1 = [0] * self.n
        l, r = 0, -1
        for i in range(0, self.n):
            k = 1 if i > r else min(d1[l + r - i], r - i + 1)
            while 0 <= i - k and i + k < self.n and self.s[i - k] == self.s[i + k]:
                k += 1
            d1[i] = k
            k -= 1
            if i + k > r:
                l = i - k
                r = i + k
        return d1

    def get_even(self):
        '''
        获取每个中心点的偶回文半径
        '''
        # Python Version
        d2 = [0] * self.n
        l, r = 0, -1
        for i in range(0, self.n):
            k = 0 if i > r else min(d2[l + r - i + 1], r - i + 1)
            while 0 <= i - k - 1 and i + k < self.n and self.s[i - k - 1] == self.s[i + k]:
                k += 1
            d2[i] = k
            k -= 1
            if i + k > r:
                l = i - k - 1
                r = i + k
        return d2

class Solution:
    def maxProduct(self, s: str) -> int:
        n = len(s)
        ma1 = Manacher(s)
        d1 = ma1.get_odd()
        pre = [0]*n #pre[i]: s[:i]中的最长奇回文半径
        suf = [0]*n #odd[i]: s[i:]中的最长奇回文半径
        premax = 1
        sufmax = 1
        dq1 = deque()
        dq2 = deque()
        for i in range(n-1):
            j = n-i-1
            dq1.append((i,d1[i]))
            while dq1[0][0]+dq1[0][1]-1<i:
                dq1.popleft()
            premax = max(premax,i - dq1[0][0] + 1)
            dq2.append((j,d1[j]))
            while dq2[0][0]-dq2[0][1]+1>j:
                dq2.popleft()
            sufmax = max(sufmax,dq2[0][0] - j + 1)
            pre[i+1] = premax
            suf[j] = sufmax
        ans = 0
        #枚举分界点
        for i in range(1,n):
            m1 = pre[i]*2-1
            m2 = suf[i]*2-1
            ans = max(ans,m1*m2)
        return ans

cpp 解法, 执行用时: 100 ms, 内存消耗: 21.8 MB, 提交时间: 2023-07-12 10:18:24

class Solution {
public:
    long long maxProduct(string s) {
        int n = s.size();
        vector<int> len(n);
        
        // 马拉车算法 求出以 i 为中心的最大扩展长度
        for(int i = 0, j = -1, mx = -1; i < n; ++i) {
            if(i > mx) 
                len[i] = 0;
            else 
                len[i] = min(len[2*j - i], mx - i);
            
            // 中心扩展
            while(i - len[i] - 1 >= 0 && i + len[i] + 1 < n && s[i - len[i] - 1] == s[i + len[i] + 1]) 
                ++len[i];
            
            if(i + len[i] > mx)
                mx = i + len[i], j = i;
        }
        
        vector<int> left(n) /* s[0...i] 的最长奇数回文子串 */, right(n) /* s[i...n-1] 的最长奇数回文子串 */;
        
        // 求出 s[0...i] 的最长奇数回文子串
        left[0] = 1;
        for(int i = 1, p = 0; i < n; ++i) {
            while(p + len[p] < i) ++p;
            left[i] = max(left[i - 1], 2 * (i - p) + 1);
        }
        
        // 求出 s[i...n-1] 的最长奇数回文子串
        right[n-1] = 1;
        for(int i = n-2, p = n-1; i >= 0; --i) {
            while(p - len[p] > i) --p;
            right[i] = max(right[i + 1], 2*(p - i) + 1);
        }
        
        // 最终结果
        long long ret = 0;
        for(int i = 0; i + 1 < n; ++i)
            ret = max(ret, 1ll * left[i] * right[i + 1]);
        return ret;
    }
};

golang 解法, 执行用时: 24 ms, 内存消耗: 8.8 MB, 提交时间: 2023-07-12 10:16:51

// 由于本题只要奇回文串,故不需要将s改造
func maxProduct(s string) int64 {
	n := len(s)
	halfLen := make([]int, n)
	for i, mid, r := 0, 0, 0; i < n; i++ {
		hl := 1
		if i < r {
			hl = min(halfLen[mid*2-i], r-i)
		}
		for ; i >= hl && i+hl < n && s[i-hl] == s[i+hl]; hl++ {}
		if i+hl > r {
			mid, r = i, i+hl
		}
		halfLen[i] = hl
	}

	startPL := make([]int, n)
	endPL := make([]int, n)
	for i, hl := range halfLen {
		left, right := i-hl+1, i+hl-1
		startPL[left] = max(startPL[left], hl*2-1)
		endPL[right] = max(endPL[right], hl*2-1)
	}
	for i := 1; i < n; i++ {
		startPL[i] = max(startPL[i], startPL[i-1]-2)
	}
	for i := n - 2; i >= 0; i-- {
		startPL[i] = max(startPL[i], startPL[i+1])
	}
	for i := n - 2; i >= 0; i-- {
		endPL[i] = max(endPL[i], endPL[i+1]-2)
	}
	for i := 1; i < n; i++ {
		endPL[i] = max(endPL[i], endPL[i-1])
	}

	ans := 0
	for i := 1; i < n; i++ {
		ans = max(ans, endPL[i-1]*startPL[i])
	}
	return int64(ans)
}

func min(a, b int) int { if a > b { return b }; return a }
func max(a, b int) int { if a < b { return b }; return a }

golang 解法, 执行用时: 24 ms, 内存消耗: 9 MB, 提交时间: 2023-07-12 10:15:55

// 马拉车算法
func maxProduct(s string) int64 {
	n := len(s)

	// 将 s 改造为 t,这样就不需要分 len(s) 的奇偶来讨论了,因为新串 t 的每个回文子串都是奇回文串(都有回文中心)
	// s 和 t 的下标转换关系:
	// (si+1)*2 = ti
	// ti/2-1 = si
	t := append(make([]byte, 0, n*2+3), '^')
	for _, c := range s {
		t = append(t, '#', byte(c))
	}
	t = append(t, '#', '$')

	// 定义一个奇回文串的半长度=(长度+1)/2,即保留回文中心,去掉一侧后的剩余字符串的长度
	// halfLen[i] 表示在 t 上的以 t[i] 为回文中心的最长回文子串的半长度
	// 即 [i-halfLen[i]+1,i+halfLen[i]-1] 是 t 上的一个回文子串
	halfLen := make([]int, len(t)-2)
	halfLen[1] = 1
	// r 表示当前右边界下标最大的回文子串的右边界下标+1
	// mid 为该回文子串的中心位置,二者的关系为 r=mid+halfLen[mid]
	for i, mid, r := 2, 1, 0; i < len(halfLen); i++ { // 循环的起止位置对应着原串的首尾字符
		hl := 1
		if i < r {
			// 记 i 关于 mid 的对称位置 i'=mid*2-i
			// 若以 i' 为中心的最长回文子串范围超出了以 mid 为中心的回文串的范围(即 i+halfLen[i'] >= r)
			// 则 halfLen[i] 应先初始化为已知的半长度 r-i,然后再继续暴力匹配
			// 否则 halfLen[i] 与 halfLen[i'] 相等
			hl = min(halfLen[mid*2-i], r-i)
		}
		// 暴力扩展
		// 算法的复杂度取决于这部分执行的次数
		// 由于扩展之后 r 必然会更新(右移),且扩展的的次数就是 r 右移的次数
		// 因此算法的复杂度和 t 的长度成正比
		for ; t[i-hl] == t[i+hl]; hl++ {}
		if i+hl > r {
			mid, r = i, i+hl
		}
		halfLen[i] = hl
	}

	// 计算两个数组,其中
	// startPL[i] 表示以 s[i] 为首字母的最长奇回文子串的长度
	// endPL[i]   表示以 s[i] 为尾字母的最长奇回文子串的长度
	startPL := make([]int, n)
	endPL := make([]int, n)
	for i := 2; i < len(halfLen); i += 2 { // 由于求的是奇数长度,写成 i+=2,改成 i++ 就是不限奇偶
		// t 中回文子串的长度为 hl*2-1
		// 由于其中 # 的数量总是比字符的数量多 1
		// 因此其在 s 中对应的回文子串的长度为 hl-1
		// 由于 t 中回文子串的首尾字母一定是 #,再根据上面说的下标转换关系,可以得到其在 s 中对应的回文子串的区间:
		// [(i-hl)/2, (i+hl)/2-2]
		hl := halfLen[i]
		left, right := (i-hl)/2, (i+hl)/2-2
		startPL[left] = max(startPL[left], hl-1)
		endPL[right] = max(endPL[right], hl-1)
	}
	for i := 1; i < n; i++ {
		startPL[i] = max(startPL[i], startPL[i-1]-2) // startPL[i] 还可能是一个更长的回文串缩短后的结果
	}
	for i := n - 2; i >= 0; i-- {
		startPL[i] = max(startPL[i], startPL[i+1]) // 变成后缀最大值
	}
	for i := n - 2; i >= 0; i-- {
		endPL[i] = max(endPL[i], endPL[i+1]-2) // endPL[i] 还可能是一个更长的回文串缩短后的结果
	}
	for i := 1; i < n; i++ {
		endPL[i] = max(endPL[i], endPL[i-1]) // 变成前缀最大值
	}

	ans := 0
	for i := 1; i < n; i++ {
		ans = max(ans, endPL[i-1]*startPL[i])
	}
	return int64(ans)
}

func min(a, b int) int { if a > b { return b }; return a }
func max(a, b int) int { if a < b { return b }; return a }

上一题