[leetcode日记]76.最小覆盖子串

题目

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = “ADOBECODEBANC”, T = “ABC” 输出: “BANC” 说明:

如果 S 中不存这样的子串,则返回空字符串 “”。 如果 S 中存在这样的子串,我们保证它是唯一的答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

像这种字符串子串的问题,脑海里出现了两个声音:动态规划、滑动窗口。
很明显,这道题目适合用滑动窗口来解决。我只需要用两个指针,初始时左指针在最左边,右指针指向第一个满足条件的串(如测试样例中,一开始又指针就指向第一个C),统计串满足条件时的长度,然后移动左指针,直到删掉了某个“必须字符”,这时开始不断右移指针,直到重新包含这个“必须字符”。如此交替移动左右指针,就可以完成任务。
以S = “ADOBECODEBANC”, T = "ABC"为例。
初态left指向第一个A(坐标0),right指向第一个C(坐标5),长度为6;
右移left,直到指向下一个“必须字符”,也就是第一个B(坐标3),此时窗口中减少的“必须字符”是A,右移指针直到找到A(下标11),此时长度为9;
重新开始右移left,指向下一个“必须字符”,也就是第一个C(坐标5),此时左指针删去的B因为不是必须字符(上一次右指针移动的时候经过过其它B),可以保持右指针不动,直接统计长度,长度为7.
重新开始右移left,指向下一个“必须字符”,也就是第二个B(坐标9),此时左指针删去的是必要字符C,右移右指针指向最后一个C(坐标12),长度为4.
因此本题所求的最小长度是4.

至于如何判断是否是“必须字符”,就用hashmap统计字幕出现的次数,如果次数大于必须的次数,就不是“必须字符”,否则就认为是必须字符。

依照此种算法,可得代码:

代码

char * minWindow(char * s, char * t){
    if (s == NULL || t == NULL) {
        return NULL;
    }

    int len = (int)strlen(s);
    int lent = (int)strlen(t);

    char *out = (char *)malloc(len + 1);
    if (out == NULL) {
        return NULL;
    }

    int outLen = 0;
    memset(out, 0, len + 1);
    if (len < lent || lent == 0) {
        return out;
    }

    int map[128] = {0};
    int mapt[128] = {0};
    int cnt = 0;
    /* 1. 初始窗口是短串长度, 先遍历一遍 得到长串中能匹配到短串的字符个数 */
    for (int i = 0; i < lent; i++) {
        mapt[t[i]]++;
        map[s[i]]++;
    }
    for (int i = 0; i < 128; i++) {
        if (mapt[i] > 0) {
            /* 临界点,只要 <= 短串 就是有效计数, 再大就是多余的了 */
            cnt += (map[i] >= mapt[i]? mapt[i] : map[i]);
        }
    }

    /* 2. 当计数等于短串长度就是满足条件了 */
    if (cnt == lent) {
        outLen = lent;
        memcpy(out, s, outLen);
        return out;
    }

    /* 3. 滑动窗口 */
    int l = 0;
    int r = lent;
    for (; r < len; r++) {
        if (mapt[s[r]] == 0) {
            continue;
        }

        map[s[r]]++;
        if (map[s[r]] <= mapt[s[r]]) {
            /* 临界点,只要 <= 短串 就是有效计数, 再大就是多余的了 */
            cnt++;
        }

        if (cnt == lent) {
            /* 从左侧开始校验 找到临界点(map[s[l]] == mapt[s[l]])删除多余数据 */
            while (mapt[s[l]] == 0 || map[s[l]] > mapt[s[l]]) {
                map[s[l++]]--;
            }

            int cpLen = r - l + 1;
            if (outLen == 0 ||  cpLen < outLen) {
                memset(out, 0, len + 1);
                memcpy(out, s + l, cpLen);
                outLen = cpLen;
            }

            /* 临界点 */
            cnt--;
            map[s[l++]]--;
        }
    }

    return out;
}

运行结果

在这里插入图片描述

结果已经比较理想了。

最近在学Java,预计近期会发几篇Java相关的学习内容。
感谢大家的支持和点赞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邵政道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值