当前位置: 首页 > news >正文

【力扣题解】【76. 最小覆盖子串】容易理解版

76. 最小覆盖子串

总结和复盘

这是时隔1年4个月之后,再次写的题解,比第一次要清晰很多。
我刚开始,就是用方法一做的,提交之后报超出内存限制;
对方法一进行优化,得到方法二,提交之后就AC了。

这是第二次做这道题,
第一次的题解,是写给自己看的,有很多思考的过程,作为题解是不清晰的。
这一次的题解,会更好理解。

首先,我新增了两个成员函数,相当于把功能拆分开了,这样更容易我们去理解;
其次,由于使用了其他函数,于是我把过程之中用到的变量,提到了类中,做成了成员变量,这样我就方便在其他成员函数之中使用。

先做,再优化。

解法一

// 解法一 //
// 由于使用substr从s之中截取子字符串,并保存在answer之中,导致内存超出了限制

class Solution {
public:
    string answer; // 这道题的答案(由于保存了结果字符串,导致内存超出了限制)
    unordered_map<char, int> window; // 这就是滑动的窗口
    unordered_map<char, int> need; // 目标字符串 t 中每个字符的情况
    int left = 0, right = 0; // 用于滑动的指针
    int validCount = 0; // 「与字符串t中数量相等的字符」的数量
    
    string minWindow(string s, string t) {
        // 初始化 need
        for (char c : t)
            need[c]++;

        // 核心思想: 不断增大right找到一个可行解;
        while (right < s.length()) {
            char R = s[right];

            // 先看这第一步:R 是我们需要的字符时
            if (need.count(R) == 1) { 
                window[R]++; // 要放到窗口之中
                if (window[R] == need[R]) { // 该字符的数量已经达到要求
                    validCount++; // 已就绪的字符数量+1
                    if (validCount == need.size()) { // 如果所有字符都就绪,更新一下answer
                        updateAnswer(s);
                    }
                }
            }

            // 最后看这第三步
            // 你想啊,如果只是right++,left不动,那这个窗口岂不是越来越长了
            // 所以,要考虑一下什么时候left也要++
            // 核心思想:当前的窗口中,已经包含了不止一个解的时候,我们让left++,去优化一下
            // 因为题目要求找字符串s的最小字串
            // 比如:s="A-B-C---BAC--", t="ABC"
            // 不断的增加right指针,当这个窗口达到「A-B-C---BAC」时,
            // 这也是s的字串 且 也涵盖t中的所有字符
            // 但这不是最优解
            // 所以要增加left,让窗口变成「BAC」
            // 才是最优解
            moveLeftFindThePerfectAnswer(s, t);

            // 再看这第二步:R 不是我们需要的字符,则 right++ 继续寻找
            right++;
        }
        return answer;
    }

    void moveLeftFindThePerfectAnswer(const string& s, const string& t) {
        while (validCount == need.size()) { // 已经找到解的情况之下,才会优化解
            char L = s[left];
            if (need.count(L) == 1) { // 字符L是目标的其中一个字符时,代表可能可以优化
                updateAnswer(s);
                window[L]--;
                if (window[L] < need[L])
                    validCount--;
                if (window[L] == 0)
                    window.erase(L);
            }
            // 不管是否优化了,left都要增加。但至少要让窗口中保留一个有用的解,也就是while的条件
            left++;
        }
    }

    void updateAnswer(const string& s) {
        string tmp_answer = s.substr(left, right - left + 1);
        if (answer.empty()) { // 首次情况下
            answer = tmp_answer;
        } else if (tmp_answer.length() < answer.length()) { // 找到了更优化解的情况
            answer = tmp_answer;
        }
    }
};

解法二

// 解法二 //
// 优化answer的计算,不使用substr
// 因为答案是一个子串,是连续的,
// 所以使用start和len来保存即可,
// 到最后一刻,才使用substr从s之中截取子串出来

class Solution {
public:
    struct MyAnswer {
        int start = INT_MAX;
        int len = INT_MAX;
    };
    MyAnswer answer; // 这道题的答案
    unordered_map<char, int> window; // 这就是滑动的窗口
    unordered_map<char, int> need; // 目标字符串 t 中每个字符的情况
    int left = 0, right = 0; // 用于滑动的指针
    int validCount = 0; // 「与字符串t中数量相等的字符」的数量
    
    string minWindow(string s, string t) {
        // 初始化 need
        for (char c : t)
            need[c]++;

        // 核心思想: 不断增大right找到一个可行解;
        while (right < s.length()) {
            char R = s[right];

            // 先看这第一步:R 是我们需要的字符时
            if (need.count(R) == 1) { 
                window[R]++; // 放到窗口之中
                if (window[R] == need[R]) { // 该字符的数量已经达到要求
                    validCount++; // 已就绪的字符数量+1
                    if (validCount == need.size()) { // 如果所有字符都就绪,更新一下answer
                        updateAnswer(s);
                    }
                }
            }

            // 最后看这第三步
            // 你想啊,如果只是right++,left不动,那这个窗口岂不是越来越长了
            // 所以,要考虑一下什么时候left也要++
            // 核心思想:当前的窗口中,已经包含了不止一个解的时候,我们让left++,去优化一下
            // 因为题目要求找字符串s的最小字串
            // 比如:s="A-B-C---BAC--", t="ABC"
            // 不断的增加right指针,当这个窗口达到「A-B-C---BAC」时,
            // 这也是s的字串 且 也涵盖t中的所有字符
            // 但这不是最优解
            // 所以要增加left,让窗口变成「BAC」
            // 才是最优解
            moveLeftFindThePerfectAnswer(s, t);

            // 再看这第二步:R 不是我们需要的字符,则 right++ 继续寻找
            right++;
        }

        // 要考虑没有找到解的情况,返回空字符串
        return (answer.start == INT_MAX && answer.len == INT_MAX)
            ? "" : s.substr(answer.start, answer.len);
    }

    void moveLeftFindThePerfectAnswer(const string& s, const string& t) {
        while (validCount == need.size()) { // 已经找到解的情况之下,才会优化解
            char L = s[left];
            if (need.count(L) == 1) { // 字符L是目标的其中一个字符时,代表可能可以优化
                updateAnswer(s);
                window[L]--;
                if (window[L] < need[L])
                    validCount--;
                if (window[L] == 0)
                    window.erase(L);
            }
            // 不管是否优化了,left都要增加。但至少要让窗口中保留一个有用的解,也就是while的条件
            left++;
        }
    }

    void updateAnswer(const string& s) {
        int new_length = right - left + 1;
        if (answer.start == INT_MAX && answer.len == INT_MAX) { // 首次情况下
            answer.start = left;
            answer.len = new_length;
        } else if (new_length < answer.len) { // 找到了更优化解的情况
            answer.start = left;
            answer.len = new_length;
        }
    }
};

作者:坤坤学编程
链接:时隔1年4个月之后,再次写的题解,比第一次要清晰很多。
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章:

  • 进程等待与进程替换
  • LeetCode每日精进:876.链表的中间结点
  • 本地部署DeepSeek-R1模型(新手保姆教程)
  • Elasticsearch:15 年来致力于索引一切,找到重要内容
  • kubernetes源码分析 kubelet
  • k8s启空容器用于排查问题
  • OpenCV机器学习(1)人工神经网络 - 多层感知器类cv::ml::ANN_MLP
  • 伯克利 CS61A 课堂笔记 08 —— Strings and Dictionaries
  • RunLoop 详解
  • 浏览器自动化与AI Agent结合项目browser-use初探
  • 【虚幻引擎UE】UE4.23到UE5.5的核心功能变化
  • 2. grafana插件安装并接入zabbix
  • 数据结构:数组
  • 【微服务学习一】springboot微服务项目构建以及nacos服务注册
  • Android adb测试常用命令大全
  • 数据结构之队列,哈希表
  • 【Vue】打包vue3+vite项目发布到github page的完整过程
  • 【CubeMX+STM32】SD卡 U盘文件系统 USB+FATFS
  • 【FastAPI 使用FastAPI和uvicorn来同时运行HTTP和HTTPS的Python应用程序】
  • encodeURI(),encodeURIComponent()区别
  • 手机表面细菌菌落总数可能比马桶高10倍,医生详解如何洗手
  • 习近平向多哥新任领导人致贺电
  • “大鼻子情圣”德帕迪约因性侵被判缓刑,还有新的官司等着他
  • 高波︱忆陈昊:在中年之前离去
  • 兵韬志略|美2026国防预算未达1万亿,但仍寻求“暗度陈仓”
  • 工行回应两售出金条疑似有杂质:情况不属实,疑似杂质应为金条售出后的外部附着物