文章目录

题目描述

示例 1:
输入:str1 = "TFFT", str2 = "abc"
输出:"abcba"
解释:
"abcba" 是满足条件的字典序最小的字符串。
str1[0] == 'T' :word[0...2] = "abc" ,等于 str2 。
str1[1] == 'F' :word[1...3] = "bcb" ,不等于 str2 。
str1[2] == 'F' :word[2...4] = "cba" ,不等于 str2 。
str1[3] == 'T' :word[3...5] 不存在(因为 word 长度为 5),所以无需考虑。
示例 2:输入:str1 = "T", str2 = "a"
输出:"a"
解释:
word 长度为 1 + 1 - 1 = 1 ,且 str1[0] == 'T' ,所以 word[0] 必须等于 str2[0] ,即 "a" 。
示例 3:输入:str1 = "F", str2 = "a"
输出:"b"
解释:
word 长度为 1 ,且 str1[0] == 'F' ,所以 word[0] 不能等于 "a" 。字典序最小的字符是 "b" 。
提示:1 <= n, m <= 105
1 <= n + m - 1 <= 105
str1 只包含字符 'T' 和 'F' 。
str2 只包含小写英文字母。
思路简述
本题的核心目标是生成字典序最小 的字符串,同时满足 "T" 和 "F" 的约束。整体策略分为两个部分:先处理 "T" 的强制约束 ,再处理 "F" 的禁止约束,并在每一步中都优先保证字典序最小。
第一步:处理 "T" 约束(固定必须相等的位置)
"T" 约束是硬性要求:如果 str1[i] == 'T',那么从 i 开始的子串必须完全等于 str2。
- 我们先初始化结果字符串
word为全 'a'(字典序最小的默认值),并创建一个fixed数组标记哪些位置被 "T" 约束固定死了(后续不能随意修改)。 - 遍历所有 "T" 的位置,将对应子串的字符强制赋值为
str2的字符。如果在赋值过程中发现某个位置已经被之前的 "T" 约束固定为不同的字符,说明冲突,直接返回空字符串。
第二步:处理 "F" 约束(避免子串等于 str2)
"F" 约束要求对应子串不能 等于 str2。此时我们已经用 "T" 约束固定了部分位置,剩下的位置默认是 'a'。
- 遍历所有 "F" 的位置,检查当前窗口内的子串是否已经不等于
str2:如果是,直接跳过,如果不幸完全等于str2,我们需要修改一个字符来打破这个相等关系。 - 为了保证字典序最小,我们应该尽可能修改窗口最右侧的非固定字符(将 'a' 改为 'b')。因为越靠右的字符对字典序的影响越小,且 'b' 是比 'a' 大的最小字符。
关键证明:修改最右侧字符不会违反之前的 "F" 约束
这时我们可能会担心:修改当前窗口的字符后,会不会导致前面某个已经处理好的 "F" 窗口又变成等于 str2 了?
这里可以通过反证法证明这种情况不可能发生 :
假设存在两个 "F" 位置 i1 和 i2(i1 < i2),处理 i2 时修改了某个字符,导致 i1 窗口又等于 str2。通过分析字符串的前后缀关系、字符的固定性,最终会推导出 "a" 必须等于 "b" 的矛盾结论。因此,每次修改最右侧的非固定字符是安全的,不会破坏之前的约束。
代码实现
cpp
class Solution {
public:
string generateString(string str1, string str2) {
int n = str1.size(), m = str2.size();
int len = n + m - 1; // 结果字符串的长度
string s(len, 'a'); // 初始化为全 'a',保证字典序最小
vector<int> fixed(len, 0); // 标记哪些位置被 'T' 约束固定(1=固定,0=可修改)
// 第一步:处理所有 'T' 约束
for (int i = 0; i < n; i++) {
if (str1[i] == 'T') {
// 遍历当前 'T' 对应的子串窗口 [i, i+m-1]
for (int j = i; j < i + m; j++) {
// 如果该位置已被固定,且与当前要填的字符冲突 → 无解
if (fixed[j] && s[j] != str2[j - i]) {
return "";
} else {
// 填充字符并标记为固定
s[j] = str2[j - i];
fixed[j] = 1;
}
}
}
}
// 第二步:处理所有 'F' 约束
for (int i = 0; i < n; i++) {
if (str1[i] == 'F') {
bool flag = false; // 标记当前窗口是否已经不等于 str2
int idx = -1; // 记录窗口内最右侧的可修改位置
// 从右往左遍历窗口 [i, i+m-1]
for (int j = i + m - 1; j >= i; j--) {
// 只要有一个字符不等于 str2,说明当前窗口已经满足 'F' 约束
if (str2[j - i] != s[j]) {
flag = true;
}
// 同时记录最右侧的非固定位置(如果还没找到的话)
if (idx == -1 && !fixed[j]) {
idx = j;
}
}
if (flag) {
// 情况1:窗口已经不等于 str2,直接跳过
continue;
} else if (idx != -1) {
// 情况2:窗口等于 str2,但有可修改的位置 → 修改最右侧的 'a' 为 'b'
s[idx] = 'b';
} else {
// 情况3:窗口等于 str2,且所有位置都被固定 → 无解
return "";
}
}
}
return s;
}
};
复杂度分析
- 时间复杂度 : O ( n × m ) O(n \times m) O(n×m)
处理 "T" 和 "F" 约束时,每个约束都需要遍历长度为 m m m 的窗口。在最坏情况下(如str1全是 'T' 或 'F'),总操作数与 n × m n \times m n×m 成正比。 - 空间复杂度 : O ( n + m ) O(n + m) O(n+m)
主要开销为结果字符串s和标记数组fixed,长度均为 n + m − 1 n + m - 1 n+m−1,与输入规模线性相关。
踩坑记录
- 顺序不能乱:必须先处理 "T" 约束再处理 "F" 约束。如果先填了 'a' 再处理 "T",可能会覆盖掉必要的固定字符,导致逻辑错误。
- 字典序的细节 :处理 "F" 时,一定要从右往左找可修改位置。如果从左往右改,会把左边的 'a' 改成 'b',导致字典序变大(比如 "ab" 比 "ba" 小)。
- 冲突检测:处理 "T" 时,不仅要填充字符,还要检查是否和已固定的字符冲突。如果两个 "T" 的窗口重叠但要求的字符不一样,直接返回空字符串。
如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!
