每日一题 力扣 3474. 字典序最小的生成字符串 贪心 字符串 C++ 题解

文章目录

题目描述

力扣 3474. 字典序最小的生成字符串

示例 1:

输入:str1 = "TFFT", str2 = "abc"

输出:"abcba"

解释:

"abcba" 是满足条件的字典序最小的字符串。

str10 == 'T' :word0...2 = "abc" ,等于 str2 。

str11 == 'F' :word1...3 = "bcb" ,不等于 str2 。

str12 == 'F' :word2...4 = "cba" ,不等于 str2 。

str13 == 'T' :word3...5 不存在(因为 word 长度为 5),所以无需考虑。
示例 2:

输入:str1 = "T", str2 = "a"

输出:"a"

解释:

word 长度为 1 + 1 - 1 = 1 ,且 str10 == 'T' ,所以 word0 必须等于 str20 ,即 "a" 。
示例 3:

输入:str1 = "F", str2 = "a"

输出:"b"

解释:

word 长度为 1 ,且 str10 == 'F' ,所以 word0 不能等于 "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" 位置 i1i2i1 < 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,与输入规模线性相关。

踩坑记录

  1. 顺序不能乱:必须先处理 "T" 约束再处理 "F" 约束。如果先填了 'a' 再处理 "T",可能会覆盖掉必要的固定字符,导致逻辑错误。
  2. 字典序的细节 :处理 "F" 时,一定要从右往左找可修改位置。如果从左往右改,会把左边的 'a' 改成 'b',导致字典序变大(比如 "ab" 比 "ba" 小)。
  3. 冲突检测:处理 "T" 时,不仅要填充字符,还要检查是否和已固定的字符冲突。如果两个 "T" 的窗口重叠但要求的字符不一样,直接返回空字符串。

如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!

相关推荐
王老师青少年编程13 小时前
csp信奥赛C++高频考点专项训练之前缀和&差分 --【一维差分】:[NOIP 2018 提高组] 铺设道路
c++·前缀和·差分·csp·高频考点·信奥赛·铺设道路
叶小鸡13 小时前
小鸡玩算法-力扣HOT100-多维动态规划
算法·leetcode·动态规划
星马梦缘13 小时前
aaaaa
数据结构·c++·算法
菜菜的顾清寒14 小时前
力扣HOT100(42)链表-随机链表的复制
算法·leetcode·链表
lqqjuly14 小时前
模型剪枝与稀疏化:理论、算法与可运行实现
人工智能·算法·剪枝
喵星人工作室14 小时前
C++火影忍者1.1.2
开发语言·c++
逻辑君14 小时前
Foresight研究报告【20260011】
人工智能·线性代数·算法·矩阵
珊瑚里的鱼14 小时前
【动态规划】不同路径Ⅱ
算法·动态规划
basketball61614 小时前
C++ 中的 ptrdiff_t 详解
开发语言·c++
wunaiqiezixin14 小时前
互斥锁与自旋锁的区别
c++