【滑动窗口】LeetCode2953:统计完全子字符串

简介: 【滑动窗口】LeetCode2953:统计完全子字符串

题目

给你一个字符串 word 和一个整数 k 。

如果 word 的一个子字符串 s 满足以下条件,我们称它是 完全字符串:

s 中每个字符 恰好 出现 k 次。

相邻字符在字母表中的顺序 至多 相差 2 。也就是说,s 中两个相邻字符 c1 和 c2 ,它们在字母表中的位置相差 至多 为 2 。

请你返回 word 中 完全 子字符串的数目。

子字符串 指的是一个字符串中一段连续 非空 的字符序列。

示例 1:

输入:word = “igigee”, k = 2

输出:3

解释:完全子字符串需要满足每个字符恰好出现 2 次,且相邻字符相差至多为 2 :igigee, igigee, igigee 。

示例 2:

输入:word = “aaabbbccc”, k = 3

输出:6

解释:完全子字符串需要满足每个字符恰好出现 3 次,且相邻字符相差至多为 2 :aaabbbccc, aaabbbccc, aaabbbccc, aaabbbccc, aaabbbccc, aaabbbccc 。

参数范围

1 <= word.length <= 105

word 只包含小写英文字母。

1 <= k <= word.length

滑动窗口

周赛的时候,忽略了:完全子字符串的长度是k的1到26倍。

时间复杂度

O(nmm)。n是字符串的长度,m的字符种类,也就是26。枚举字符结尾:O(n);枚举窗口长度:O(m);统计合法字符数量O(m)。

变量解析

m_aCharNums iWindowWidth个字符,各字母的数量

代码

核心代码

class Solution {
public:
int countCompleteSubstrings(string word, int k) {
m_iK = k;
int pre = 0;
int iRet = 0;
for (int i = 0; i < word.length(); i++)
{
if (i && (abs(word[i] - word[i - 1]) > 2))
{
iRet += Do(word.data()+pre, i - pre);
pre = i;
}
}
iRet += Do(word.data() + pre, word.length() - pre);
return iRet;
}
int Do(const char* p ,int len)
{
int iRet = 0;
auto AddNum = &
{
for (int i = 0; i < 26; i++)
{
if ((0 != m_aCharNums[i]) && (m_iK != m_aCharNums[i]))
{
return;
}
}
iRet++;
};
int iWindowWidth = 0;
for (int i = 1; (i <= 26)&&((iWindowWidth=m_iK*i)<= len); i++)
{
memset(m_aCharNums, 0, sizeof(m_aCharNums));
int j = 0;
for (; j < iWindowWidth; j++)
{
m_aCharNums[p[j] - ‘a’]++;
}
AddNum();
for (;j < len; j++)
{
m_aCharNums[p[j] - ‘a’]++;
m_aCharNums[p[j - iWindowWidth] - ‘a’]–;
AddNum();
}
}
return iRet;
}
int m_aCharNums[26];
int m_iK;
};

测试用例

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
string s;
int k, res;
{
Solution slu;
s = “ba”;
k = 1;
auto res = slu.countCompleteSubstrings(s, k);
Assert(3, res);
}
{
Solution slu;
s = “gvgvvgv”;
k = 2;
auto res = slu.countCompleteSubstrings(s, k);
Assert(1, res);
}
{
Solution slu;
s = “igigee”;
k = 2;
auto res = slu.countCompleteSubstrings(s, k);
Assert(3, res);
}
{
Solution slu;
s = “aaabbbccc”;
k = 3;
auto res = slu.countCompleteSubstrings(s, k);
Assert(6, res);
}
//CConsole::Out(res);

}

优化

不用每次都判断无法字符的数量,只会修改两个字符的数量,只判断这两个字符就可以了。

时间复杂度

O(nm)。

代码

class Solution {
public:
  int countCompleteSubstrings(string word, int k) {
    m_iK = k;
    int pre = 0;
    int iRet = 0;
    for (int i = 0; i < word.length(); i++)
    {
      if (i && (abs(word[i] - word[i - 1]) > 2))
      {
        iRet += Do(word.data()+pre, i - pre);
        pre = i;
      }
    }
    iRet += Do(word.data() + pre, word.length() - pre);
    return iRet;
  }
  int Do(const char* p ,int len)
  {
    int iRet = 0;   
    int iWindowWidth = 0;
    for (int i = 1; (i <= 26)&&((iWindowWidth=m_iK*i)<= len); i++)
    {
      memset(m_aCharNums,  0, sizeof(m_aCharNums));
      int j = 0;
      for (; j < iWindowWidth; j++)
      {
        m_aCharNums[p[j] - 'a']++;
      }
      int iVilidCount = std::count(m_aCharNums, m_aCharNums + 26, 0) + std::count(m_aCharNums, m_aCharNums + 26, m_iK);
      if (26 == iVilidCount)
      {
        iRet++;
      }
      auto Change = [&](int index, int iAdd)
      {
        bool bOld = (0 == m_aCharNums[index]) || (m_iK == m_aCharNums[index]);
        m_aCharNums[index] += iAdd;
        bool bNew = (0 == m_aCharNums[index]) || (m_iK == m_aCharNums[index]);
        iVilidCount += (int)bNew - (int)bOld;
      };
      for (;j < len; j++)
      {
        Change(p[j] - 'a',1);
        Change(p[j - iWindowWidth] - 'a', -1);
        iRet += (26 == iVilidCount);
      }
    }
    return iRet;
  }
  int m_aCharNums[26];
  int m_iK;
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

。也就是我们常说的专业的人做专业的事。 |

|如果程序是一条龙,那算法就是他的是睛|

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境:

VS2022 C++17



相关文章
|
3月前
【LeetCode 26】239.滑动窗口最大值
【LeetCode 26】239.滑动窗口最大值
43 1
|
3月前
|
JavaScript
力扣3333.找到初始输入字符串Ⅱ
【10月更文挑战第9天】力扣3333.找到初始输入字符串Ⅱ
41 1
|
3月前
|
C++
Leetcode第43题(字符串相乘)
本篇介绍了一种用C++实现的字符串表示的非负整数相乘的方法,通过逆向编号字符串,将乘法运算转化为二维数组的累加过程,最后处理进位并转换为字符串结果,解决了两个大数相乘的问题。
32 9
|
3月前
|
算法 C++
Leetcode第八题(字符串转换整数(atoi))
这篇文章介绍了LeetCode上第8题“字符串转换整数(atoi)”的解题思路和C++的实现方法,包括处理前导空格、正负号、连续数字字符以及整数溢出的情况。
25 0
|
3月前
【LeetCode 22】459.重复的子字符串
【LeetCode 22】459.重复的子字符串
34 0
|
3月前
【LeetCode 20】151.反转字符串里的单词
【LeetCode 20】151.反转字符串里的单词
27 0
|
3月前
【LeetCode 19】541.反转字符串II
【LeetCode 19】541.反转字符串II
25 0
|
3月前
【LeetCode 18】6.2.反转字符串
【LeetCode 18】6.2.反转字符串
23 0
|
4月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
5月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
133 2