LeetCode 3474. 字典序最小的生成字符串
题目描述
给你两个字符串,str1 和 str2,其长度分别为 n 和 m 。
如果一个长度为 n + m - 1 的字符串 word 的每个下标 0 <= i <= n - 1 都满足以下条件,则称其由 str1 和 str2 生成:
如果 str1[i] == 'T',则长度为 m 的 子字符串(从下标 i 开始)与 str2 相等,即 word[i..(i + m - 1)] == str2。
如果 str1[i] == 'F',则长度为 m 的 子字符串(从下标 i 开始)与 str2 不相等,即 word[i..(i + m - 1)] != str2。
返回可以由 str1 和 str2 生成 的 字典序最小 的字符串。如果不存在满足条件的字符串,返回空字符串 ""。
如果字符串 a 在第一个不同字符的位置上比字符串 b 的对应字符在字母表中更靠前,则称字符串 a 的 字典序 小于 字符串 b。
如果前 min(a.length, b.length) 个字符都相同,则较短的字符串字典序更小。
子字符串 是字符串中的一个连续、非空的字符序列。
思路分析
这道题属于字符串构造问题,关键在于同时满足多组"必须相等"和"必须不等"的约束。我们可以分两步解决:
-
强制满足所有
'T'条件对于每个
s[i] == 'T',将ans[i..i+m-1]直接赋值为t。如果不同位置要求冲突(比如同一个位置既要填t[j]又要填不同的字符),则直接无解。 -
处理所有
'F'条件在
'T'已经确定的基础上,先将未确定的位置(仍为'?')填充为默认字符'a',得到一个候选字符串。然后检查每个
'F'位置:如果当前子串恰好等于t,就说明违反了条件,需要破坏它。破坏的方法:从子串的末尾向前找到第一个原本是
'?'的位置(即未被'T'强制固定的位置),将其改为'b',这样该子串就不再等于t。如果找不到这样的位置(子串所有位置都被
'T'锁死),则无解。
为什么从后向前找?因为后面的子串起始索引更大,修改靠后的位置对后续子串的影响最小,可以避免不必要的冲突。
代码实现
cpp
class Solution {
public:
string generateString(string s, string t) {
int n = s.size(), m = t.size();
string ans(n + m - 1, '?'); // 初始化全为 '?'
// 步骤1:处理所有 'T' 条件
for (int i = 0; i < n; ++i) {
if (s[i] != 'T') continue;
for (int j = 0; j < m; ++j) {
char v = ans[i + j];
if (v != '?' && v != t[j]) return ""; // 冲突
ans[i + j] = t[j];
}
}
string old_ans = ans; // 记录哪些位置是原本由 'T' 确定的
// 步骤2:默认填充 'a'
for (char& c : ans) {
if (c == '?') c = 'a';
}
// 步骤3:处理所有 'F' 条件
for (int i = 0; i < n; ++i) {
if (s[i] != 'F') continue;
// 检查当前子串是否等于 t
if (string(ans.begin() + i, ans.begin() + i + m) != t)
continue; // 已经满足,跳过
bool same = false;
// 从后向前找第一个原本为 '?' 的位置
for (int j = i + m - 1; j >= i; --j) {
if (old_ans[j] == '?') {
ans[j] = 'b'; // 改为 'b' 破坏匹配
same = true;
break;
}
}
if (!same) return ""; // 无法破坏,无解
}
return ans;
}
};
复杂度分析
-
时间复杂度 :O(n·m)
最坏情况下,每个
'T'和'F'位置都需要遍历长度为m的子串。实际常数较小。 -
空间复杂度 :O(n + m)
存储
ans和old_ans字符串。
总结
本题通过先满足所有强制相等条件,再破坏所有强制不等条件的策略,利用贪心方法逐个解决冲突。关键在于:
- 将
'T'要求的子串直接填充,同时检测冲突。 - 对
'F'条件,仅当当前子串意外相等时才进行破坏,且破坏时优先选择原本可修改的位置。 - 从后向前修改可以减少对其他条件的干扰。
这种方法简单直观,且能有效判断无解情况。