Medium
题目描述
给你一个字符串 s,请你找到 s 中两个 不相交回文子序列,使得它们长度的 乘积最大。两个子序列在原字符串中如果没有任何相同的下标位置被同时选中,那么它们就是 不相交 的。
请你返回两个回文子序列长度可以达到的 最大乘积 。
子序列 指的是从原字符串中删除若干个字符(可以一个也不删除)后,剩余字符不改变相对位置而组成的新字符串。如果一个字符串从前往后读和从后往前读一样,那么这个字符串是 回文字符串 。
示例 1:
输入:s = "leetcodecom"
输出:9
解释:最优方案是选择 "ete" 作为第一个子序列,"cdc" 作为第二个子序列。
它们长度的乘积为:3 * 3 = 9 。
示例 2:
输入:s = "bb"
输出:1
解释:最优方案是选择 "b"(第一个字符)作为第一个子序列,"b"(第二个字符)作为第二个子序列。
它们长度的乘积为:1 * 1 = 1 。
示例 3:
输入:s = "accbcaxxcxx"
输出:25
解释:最优方案是选择 "accca" 作为第一个子序列,"xxcxx" 作为第二个子序列。
它们长度的乘积为:5 * 5 = 25 。
提示:
2 <= s.length <= 12s只包含小写英文字母。
解题思路
这道题需要找到两个不相交的回文子序列,使得它们长度的乘积最大。由于字符串长度最大只有12,我们可以使用位掩码(bitmask)来枚举所有可能的子序列组合。
解题思路:
- 位掩码枚举:使用位掩码来表示子序列的选择情况,每个位表示是否选择对应位置的字符。
- 生成所有子序列:对于长度为n的字符串,总共有2^n种选择方式。
- 检查回文性:对于每个子序列,检查其是否为回文串。
- 寻找不相交对:两个子序列不相交意味着它们对应的位掩码按位与结果为0。
- 优化策略:可以预先计算所有回文子序列及其长度,然后枚举所有不相交的组合。
算法步骤:
- 首先枚举所有可能的位掩码(1到2^n-1)
- 对每个位掩码,构造对应的子序列并检查是否为回文
- 将所有回文子序列的位掩码和长度存储
- 枚举所有回文子序列对,检查是否不相交,更新最大乘积
时间复杂度主要受限于枚举所有子序列组合,由于n≤12,这种暴力方法是可行的。
代码实现
class Solution {
public:
int maxProduct(string s) {
int n = s.length();
vector<pair<int, int>> palindromes; // (mask, length)
// 枚举所有可能的子序列
for (int mask = 1; mask < (1 << n); mask++) {
string subseq = "";
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) {
subseq += s[i];
}
}
// 检查是否为回文
if (isPalindrome(subseq)) {
palindromes.push_back({mask, (int)subseq.length()});
}
}
int maxProduct = 0;
// 枚举所有回文子序列对
for (int i = 0; i < palindromes.size(); i++) {
for (int j = i + 1; j < palindromes.size(); j++) {
// 检查是否不相交
if ((palindromes[i].first & palindromes[j].first) == 0) {
maxProduct = max(maxProduct, palindromes[i].second * palindromes[j].second);
}
}
}
return maxProduct;
}
private:
bool isPalindrome(const string& s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
}
};
class Solution:
def maxProduct(self, s: str) -> int:
n = len(s)
palindromes = [] # (mask, length)
# 枚举所有可能的子序列
for mask in range(1, 1 << n):
subseq = ""
for i in range(n):
if mask & (1 << i):
subseq += s[i]
# 检查是否为回文
if subseq == subseq[::-1]:
palindromes.append((mask, len(subseq)))
max_product = 0
# 枚举所有回文子序列对
for i in range(len(palindromes)):
for j in range(i + 1, len(palindromes)):
# 检查是否不相交
if palindromes[i][0] & palindromes[j][0] == 0:
max_product = max(max_product, palindromes[i][1] * palindromes[j][1])
return max_product
public class Solution {
public int MaxProduct(string s) {
int n = s.Length;
var palindromes = new List<(int mask, int length)>();
// 枚举所有可能的子序列
for (int mask = 1; mask < (1 << n); mask++) {
var subseq = new StringBuilder();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) {
subseq.Append(s[i]);
}
}
// 检查是否为回文
string subseqStr = subseq.ToString();
if (IsPalindrome(subseqStr)) {
palindromes.Add((mask, subseqStr.Length));
}
}
int maxProduct = 0;
// 枚举所有回文子序列对
for (int i = 0; i < palindromes.Count; i++) {
for (int j = i + 1; j < palindromes.Count; j++) {
// 检查是否不相交
if ((palindromes[i].mask & palindromes[j].mask) == 0) {
maxProduct = Math.Max(maxProduct, palindromes[i].length * palindromes[j].length);
}
}
}
return maxProduct;
}
private bool IsPalindrome(string s) {
int left = 0, right = s.Length - 1;
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
}
}
var maxProduct = function(s) {
const n = s.length;
const memo = new Map();
function isPalindrome(str) {
let left = 0, right = str.length - 1;
while (left < right) {
if (str[left] !== str[right]) return false;
left++;
right--;
}
return true;
}
function getLongestPalindrome(mask) {
if (memo.has(mask)) return memo.get(mask);
const chars = [];
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) {
chars.push(s[i]);
}
}
const str = chars.join('');
let maxLen = 0;
// Generate all subsequences of the selected characters
for (let subMask = mask; subMask > 0; subMask = (subMask - 1) & mask) {
const subChars = [];
for (let i = 0; i < n; i++) {
if (subMask & (1 << i)) {
subChars.push(s[i]);
}
}
const subStr = subChars.join('');
if (isPalindrome(subStr)) {
maxLen = Math.max(maxLen, subStr.length);
}
}
memo.set(mask, maxLen);
return maxLen;
}
let maxProduct = 0;
// Try all possible ways to split indices into two disjoint sets
for (let mask1 = 1; mask1 < (1 << n); mask1++) {
for (let mask2 = 1; mask2 < (1 << n); mask2++) {
// Check if masks are disjoint
if ((mask1 & mask2) === 0) {
const len1 = getLongestPalindrome(mask1);
const len2 = getLongestPalindrome(mask2);
maxProduct = Math.max(maxProduct, len1 * len2);
}
}
}
return maxProduct;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(2^n × n + k^2) | 其中n为字符串长度,k为回文子序列个数。枚举所有子序列需要O(2^n),每个子序列检查回文需要O(n),最后枚举回文对需要O(k^2) |
| 空间复杂度 | O(k) | 存储所有回文子序列的信息,k为回文子序列个数,最坏情况下k = O(2^n) |