Medium
题目描述
给定一个由大写英文字母组成的字符串 s。
你可以在字符串的任意位置(包括开头或结尾)最多插入一个大写英文字母。
返回在最多插入一个字母后,结果字符串中可以形成的 “LCT” 子序列的最大数量。
示例 1:
输入:s = "LMCT"
输出:2
解释:我们可以在字符串 s 的开头插入一个 "L",得到 "LLMCT",它有 2 个子序列,索引分别为 [0, 3, 4] 和 [1, 3, 4]。
示例 2:
输入:s = "LCCT"
输出:4
解释:我们可以在字符串 s 的开头插入一个 "L",得到 "LLCCT",它有 4 个子序列,索引分别为 [0, 2, 4]、[0, 3, 4]、[1, 2, 4] 和 [1, 3, 4]。
示例 3:
输入:s = "L"
输出:0
解释:由于无法通过插入单个字母获得子序列 "LCT",结果为 0。
提示:
1 <= s.length <= 10^5s由大写英文字母组成
解题思路
这道题要求在插入一个字符后最大化 “LCT” 子序列的数量。我们需要理解 “LCT” 子序列的形成规律:对于每个位置 i,如果 s[i] = 'C',那么它左边的 ‘L’ 数量与右边的 ‘T’ 数量的乘积就是以该 ‘C’ 为中心的 “LCT” 子序列数量。
解题思路:
预处理数组:
preL[i]:位置 i 左边(包含 i)的 ‘L’ 数量preLC[i]:位置 i 左边(包含 i)的 “LC” 子序列数量sufT[i]:位置 i 右边(包含 i)的 ‘T’ 数量sufCT[i]:位置 i 右边(包含 i)的 “CT” 子序列数量
计算基础值:不插入任何字符时的 “LCT” 子序列数量
base = Σ(preLC[i] * sufT[i]),其中s[i] = 'T'枚举插入位置:对于每个可能的插入位置 i(包括字符串开头和结尾),计算插入不同字符的收益:
- 插入 ‘L’:增加
sufCT[i]个子序列 - 插入 ‘C’:增加
preL[i] * sufT[i]个子序列 - 插入 ‘T’:增加
preLC[i]个子序列
- 插入 ‘L’:增加
选择最优方案:取所有可能的插入方案中收益最大的那个
这种方法的关键在于通过预处理避免重复计算,时间复杂度为 O(n)。
代码实现
class Solution {
public:
long long numOfSubsequences(string s) {
int n = s.length();
// 预处理数组
vector<long long> preL(n + 1, 0), preLC(n + 1, 0);
vector<long long> sufT(n + 1, 0), sufCT(n + 1, 0);
// 计算前缀数组
for (int i = 0; i < n; i++) {
preL[i + 1] = preL[i] + (s[i] == 'L' ? 1 : 0);
preLC[i + 1] = preLC[i] + (s[i] == 'C' ? preL[i + 1] : 0);
}
// 计算后缀数组
for (int i = n - 1; i >= 0; i--) {
sufT[i] = sufT[i + 1] + (s[i] == 'T' ? 1 : 0);
sufCT[i] = sufCT[i + 1] + (s[i] == 'C' ? sufT[i + 1] : 0);
}
// 计算基础值
long long base = 0;
for (int i = 0; i < n; i++) {
if (s[i] == 'T') {
base += preLC[i];
}
}
long long maxVal = base;
// 枚举插入位置
for (int i = 0; i <= n; i++) {
// 插入 'L'
long long gainL = sufCT[i];
maxVal = max(maxVal, base + gainL);
// 插入 'C'
long long gainC = preL[i] * sufT[i];
maxVal = max(maxVal, base + gainC);
// 插入 'T'
long long gainT = preLC[i];
maxVal = max(maxVal, base + gainT);
}
return maxVal;
}
};
class Solution:
def numOfSubsequences(self, s: str) -> int:
n = len(s)
# 预处理数组
preL = [0] * (n + 1)
preLC = [0] * (n + 1)
sufT = [0] * (n + 1)
sufCT = [0] * (n + 1)
# 计算前缀数组
for i in range(n):
preL[i + 1] = preL[i] + (1 if s[i] == 'L' else 0)
preLC[i + 1] = preLC[i] + (preL[i + 1] if s[i] == 'C' else 0)
# 计算后缀数组
for i in range(n - 1, -1, -1):
sufT[i] = sufT[i + 1] + (1 if s[i] == 'T' else 0)
sufCT[i] = sufCT[i + 1] + (sufT[i + 1] if s[i] == 'C' else 0)
# 计算基础值
base = 0
for i in range(n):
if s[i] == 'T':
base += preLC[i]
max_val = base
# 枚举插入位置
for i in range(n + 1):
# 插入 'L'
gain_L = sufCT[i]
max_val = max(max_val, base + gain_L)
# 插入 'C'
gain_C = preL[i] * sufT[i]
max_val = max(max_val, base + gain_C)
# 插入 'T'
gain_T = preLC[i]
max_val = max(max_val, base + gain_T)
return max_val
public class Solution {
public long NumOfSubsequences(string s) {
int n = s.Length;
// 预处理数组
long[] preL = new long[n + 1];
long[] preLC = new long[n + 1];
long[] sufT = new long[n + 1];
long[] sufCT = new long[n + 1];
// 计算前缀数组
for (int i = 0; i < n; i++) {
preL[i + 1] = preL[i] + (s[i] == 'L' ? 1 : 0);
preLC[i + 1] = preLC[i] + (s[i] == 'C' ? preL[i + 1] : 0);
}
// 计算后缀数组
for (int i = n - 1; i >= 0; i--) {
sufT[i] = sufT[i + 1] + (s[i] == 'T' ? 1 : 0);
sufCT[i] = sufCT[i + 1] + (s[i] == 'C' ? sufT[i + 1] : 0);
}
// 计算基础值
long baseVal = 0;
for (int i = 0; i < n; i++) {
if (s[i] == 'T') {
baseVal += preLC[i];
}
}
long maxVal = baseVal;
// 枚举插入位置
for (int i = 0; i <= n; i++) {
// 插入 'L'
long gainL = sufCT[i];
maxVal = Math.Max(maxVal, baseVal + gainL);
// 插入 'C'
long gainC = preL[i] * sufT[i];
maxVal = Math.Max(maxVal, baseVal + gainC);
// 插入 'T'
long gainT = preLC[i];
maxVal = Math.Max(maxVal, baseVal + gainT);
}
return maxVal;
}
}
var numOfSubsequences = function(s) {
const n = s.length;
// 预处理数组
const preL = new Array(n + 1).fill(0);
const preLC = new Array(n + 1).fill(0);
const sufT = new Array(n + 1).fill(0);
const sufCT = new Array(n + 1).fill(0);
// 计算前缀数组
for (let i = 0; i < n; i++) {
preL[i + 1] = preL[i] + (s[i]
复杂度分析
| 复杂度 | 大小 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
其中 n 是字符串 s 的长度。我们需要遍历字符串三次:一次计算前缀数组,一次计算后缀数组,一次枚举插入位置。空间复杂度主要来自四个预处理数组的存储。