NC28 最小覆盖子串【牛客网】

零、原题链接


NC28 最小覆盖子串

一、题目描述

在这里插入图片描述

二、测试用例

在这里插入图片描述

三、解题思路

  1. 基本思路:
      滑动窗口 + 散列表
  2. 具体思路:
    • 先统计字符串 T 中的字符数量;
    • 建立滑动窗口:
      • 每次遍历字符,将该字符添加进窗口:
        • 同时判断是否是属于字符串 T 中的字符,是则对应字符数量 – ,如果该字符数量将为 0 ,则字符类型数量 --;
      • 如果字符类型数量为 0 ,则表示窗口已经包含了字符串 T 的所有字符,则开始收缩窗口:
        • 每次收缩窗口,则判断舍弃的字符是否是字符串 T 中的字符,是则对应字符数量 ++ ,
        • 如果该字符数量大于 0 ,则表示已经收缩到最小窗口,
          • 则判断是否为当前最短的,是则更新最短字符串的起始坐标;
          • 字符类型数量 ++ ;
    • 判断最短字符串下标是否合法,合法则返回对应字符串,不合法说明不存在对应字符串,则返回空字符串;

四、参考代码

时间复杂度: O ( ∣ S ∣ + ∣ T ∣ ) \Omicron(|S|+|T|) O(S+T)【两个字符串都只遍历了一遍】
空间复杂度: O ( ∣ T ∣ ) \Omicron(|T|) O(T)【只需要额外存放字符串 T 的字符数量】

#include <unordered_map>
class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param S string字符串
     * @param T string字符串
     * @return string字符串
     */
    string minWindow(string S, string T) {
        unordered_map<char, int> m;

        for (int i = 0; i < T.length(); i++) {
            if (m.count(T[i]))
                m[T[i]]++;
            else
                m.emplace(T[i], 1);
        }

        int minS = -S.length() - 1, minE = 0;
        int num = m.size();
        for (int s = 0, e = 0; e < S.length(); e++) {
            if (m.count(S[e]) && --m[S[e]] == 0)// 添加字符
                num--;

            while (num == 0) { // 舍弃字符
                if (m.count(S[s]) && ++m[S[s]] > 0) {
                    if (e - s < minE - minS) {
                        minS = s;
                        minE = e;
                    }
                    num++;
                }
                s++;
            }
        }

        return minS < 0 ? "" : S.substr(minS, minE - minS + 1);
    }
};
<think>好的,我现在需要解决用户的问题:如何在C语言中实现NC28最小覆盖子串算法。首先,我得理解这个问题到底要求什么。 最小覆盖子串的问题应该是给定一个字符串S和一个字符串T,找出S中包含T所有字符的最短子串。比如,如果S是"ADOBECODEBANC",T是"ABC",那么最短的子串就是"BANC"。这个问题通常可以用滑动窗口(双指针)的方法来解决,因为需要高效地扩展和收缩窗口来找到最短符合条件的子串。 接下来,我需要回忆滑动窗口算法的基本思路。大致步骤应该是这样的:用左右两个指针维护一个窗口,先移动右指针扩大窗口直到包含所有T中的字符,然后尝试移动左指针缩小窗口,同时记录最短的子串长度和起始位置。过程中需要统计字符的出现次数,可能需要哈希表或者数组来记录。 然后,我得考虑如何在C语言中实现这个算法。C语言没有内置的哈希表,所以可能需要用数组来模拟,因为ASCII字符最多256个,可以创建两个数组need和window,分别记录T中各字符的需求次数和当前窗口中的字符次数。还需要一个变量valid来统计窗口中满足需求的字符种类数。 接下来,具体步骤应该是: 1. 初始化need数组,遍历T字符串,记录每个字符的出现次数。 2. 初始化左右指针left=0, right=0,以及valid=0,记录最小窗口的起始位置和长度。 3. 移动右指针,将字符加入窗口,如果该字符在need中存在且当前窗口中的数量达到了需求,valid加1。 4. 当valid等于T中不同字符的种类数时,开始移动左指针缩小窗口,同时更新最小窗口的信息。 5. 每次移动左指针时,如果移出的字符导致窗口不再满足条件,valid减1,并继续移动右指针。 需要注意的边界情况包括:S中没有覆盖T的子串时返回空字符串,处理重复字符的情况,以及字符计数时的正确性。 另外,C语言中字符串操作需要注意结尾的'\0',比如在最后提取子串时,可以使用strndup或者动态分配内存,并复制相应的字符。 然后,我需要考虑代码的结构。首先处理特殊情况,比如T的长度为0或者S比T短,直接返回空。然后初始化两个数组,统计T的字符需求。接着滑动窗口的主体部分,左右指针移动,统计窗口中的字符,并检查是否满足条件。最后根据记录的最小窗口信息,生成结果字符串。 在代码实现中,需要注意指针越界的问题,比如right不能超过字符串长度。还有字符计数的时候,要确保数组的索引正确,比如使用字符的ASCII码作为索引。 可能遇到的错误点包括:valid的判断条件是否正确,比如只有当window[c]等于need[c]时才增加valid,而不是超过的时候;更新最小窗口时是否正确处理起始位置和长度;以及在缩小左指针时如何调整计数和valid。 测试案例方面,可以用题目中的例子,比如S="ADOBECODEBANC",T="ABC",预期得到"BANC"。另外,测试T中有重复字符的情况,比如T="AABC",此时需要确保窗口中有足够的A和B、C的数量。 最后,需要确保代码的效率和正确性,比如使用数组而不是哈希表来提高访问速度,时间复杂度应该是O(N),因为左右指针各遍历一次字符串。</think>### C语言实现NC28最小覆盖子串的解决方案 #### 算法思路 最小覆盖子串问题要求找到字符串$S$中包含字符串$T$所有字符的最短连续子串。核心解决步骤如下: 1. **滑动窗口(双指针)**:通过左右指针$left$和$right$构建窗口,逐步扩展和收缩窗口以寻找最优解[^1]。 2. **字符统计**:使用数组`need`记录$T$中每个字符的需求量,数组`window`记录当前窗口内各字符的数量。 3. **有效性判断**:通过计数器$valid$判断当前窗口是否满足$T$的全部字符需求。 #### 代码实现 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_CHAR 128 // ASCII字符范围 char* minWindow(char* s, char* t) { if (strlen(t) == 0 || strlen(s) < strlen(t)) return ""; int need[MAX_CHAR] = {0}; // 统计t中字符需求 int window[MAX_CHAR] = {0}; // 统计当前窗口字符 int required = 0; // t中不同字符的种类数 // 初始化need数组 for (int i = 0; t[i]; i++) { if (need[(int)t[i]] == 0) required++; need[(int)t[i]]++; } int left = 0, right = 0; // 滑动窗口指针 int valid = 0; // 满足需求的字符种类数 int start = 0, min_len = INT_MAX; // 最小窗口的起始位置和长度 while (right < strlen(s)) { char c = s[right]; right++; // 更新窗口统计 if (need[(int)c] > 0) { window[(int)c]++; if (window[(int)c] == need[(int)c]) { valid++; } } // 当窗口满足条件时,尝试收缩左边界 while (valid == required) { // 更新最小窗口 if (right - left < min_len) { start = left; min_len = right - left; } char d = s[left]; left++; // 更新窗口统计 if (need[(int)d] > 0) { if (window[(int)d] == need[(int)d]) { valid--; } window[(int)d]--; } } } // 构造结果字符串 if (min_len == INT_MAX) return ""; char* res = (char*)malloc(sizeof(char) * (min_len + 1)); strncpy(res, s + start, min_len); res[min_len] = '\0'; return res; } // 测试代码 int main() { char s[] = "ADOBECODEBANC"; char t[] = "ABC"; char* result = minWindow(s, t); printf("最小覆盖子串: %s\n", result); // 输出 "BANC" free(result); return 0; } ``` #### 关键点说明 1. **滑动窗口效率**:时间复杂度为$O(|S| + |T|)$,空间复杂度为$O(1)$(固定大小数组)[^1]。 2. **字符统计优化**:直接通过ASCII码索引数组,比哈希表更高效。 3. **边界处理**:当$T$长度大于$S$时直接返回空字符串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值