题意理解

以长度为k的子串中如果某个字符出现多次,则判定该子串符合条件。计算一共有多少这样的子串。

方法一

枚举
从左向右遍历字符串,取出其中所有长度为k的子串。对于每个子串也遍历一遍,使用一个map<char, bool>类型find记录某个字符是否出现过,如果遇到了某个字符对应的find为真,表明该字符在子串中重复出现,此时答案ans加1并且跳出该子串的循环。

具体代码如下:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @param k int整型 
     * @return int整型
     */
    int numKLenSubstrRepeats(string s, int k) {
        // write code here
        int ans = 0;
        for (int i=0;i<s.size()-k+1;i++)
        {
            string ss = s.substr(i,k);
            map<char, bool> find;
            for (int j=0;j<ss.size();j++)
            {
                //找到重复字符要及时退出
                if (find.count(ss[j])) {ans++; break;}
                else find[ss[j]] = 1;
            }
            find.clear();
        }
        return ans;
    }
};

时间复杂度: O(Nk)O(Nk)。双重遍历,外层遍历长度为N的字符串中每个字符作为子串的起点,内层遍历每个长度为k的子串。
空间复杂度: O(k)O(k)。因为使用了clear()函数,map类型变量的大小最大为子串的长度k。

方法二

滑动窗口
因为两个相邻的子串,只有头尾不同,中间相同,所以尝试用滑动窗口代替内部对子串的循环。用map变量find记录某字符是否出现过,用map变量index记录某个字符在原字符串中的位置。用count记录当前所有字符都不重复时的字符个数;当count>kcount>k时,说明一个子串中无重复字符,此时整个窗口右移一位。用begin记录这一段不重复字符的起始位置。

从左向右遍历原字符串:
1、遇到未出现的字符就令对应的find为真。
2、遇到出现过的字符#,以从begin到重复字符#第一次出现的位置的这个区间(begin~index[#])中的字符为开头的子串都包含重复字符,ans一次性加上这段区间的长度,窗口缩短并右移一段距离,更新count、begin、index、find。

注意字符串的结尾要特殊处理。以begin~index[#]这段区间中的字符为开头时,后面不一定够取k个字符组成子串。

以"abcbefe",4为例,过程示意图如下: alt

具体代码如下:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @param k int整型 
     * @return int整型
     */
    int numKLenSubstrRepeats(string s, int k) {
        // write code here
        int ans = 0, count = 0, begin = 0;
        map<char, bool> find;
        map<char, int> index;
        for (int i=0;i<s.size();i++)
        {
            //count用来记录当前子串中有多少字符
            count++;
            //子串长度超过k后,第一个字符就要移除
            if (count>k)
            {
                find[s[begin]] = false;
                begin++;
                count--;
            }
            //如果子串中未出现该字符,就更新find
            if (!find[s[i]]) 
            {
                find[s[i]] = true;
                index[s[i]] = i;
            }
            else
            {
                //出现重复字符后,若干子串都包含重复字符。
                int t = s.size() - begin - k;
                ans = ans + min(index[s[i]] - begin, t) + 1;
                count = count - (index[s[i]] - begin) - 1;
                for (int j=begin;j<index[s[i]];j++)
                    find[s[j]] = false;
                begin = index[s[i]] + 1;
                index[s[i]] = i;
                //字符串末尾要特殊处理
                if (begin > (s.size()-k)) break;
            }
        }
        return ans;
    }
};

时间复杂度: O(N)O(N)。虽然有双重遍历,但是内部遍历加起来不超过原字符串长度;即每个字符只在外部循环遍历一次,内部循环遍历一次。
空间复杂度: O(N)O(N)。两个map类型变量的大小最大为字符的个数k。