目录
[Part1. 题目概览](#Part1. 题目概览)
[Part2. 题目分析](#Part2. 题目分析)
[Part3. 公式](#Part3. 公式)
[Part4. 优化算法](#Part4. 优化算法)
[Part5. 代码与细节](#Part5. 代码与细节)
[Part6. 结语](#Part6. 结语)
前言
Leetcode的这道题目很有综合性,涵盖了很多基础知识,我们来看看吧。
let's go!!!!!!!!
Part1. 题目概览
题目看上去还是比较简单的,就是统计在一个大字符串中的几个小字符串中满足条件的子字符串数量。
其中这个的数据规模是十的五次方,也就是说这个是NlogN级别的算法,所以暴力就被排除了。
Part2. 题目分析
现在我们知道了基本的基本的计算方法,我们来总结一下就可以得出公式,之后我们再根据公式再来优化算法。
Part3. 公式
Part4. 优化算法
我们对于每一个点都进行枚举,显然是不行的,这是N平方级别的时间复杂度,因此我们要优化。
首先,我们从pi来入手,我们可以先预处理一下整个的数组,将每个点对应的pi求出来,这样我们在具体计算的时候就是O(1)的知道pi的位置。
其次我们从min来入手,这里为什么要加个min?因为我们不知道什么时候pi会大于R,导致计算的方式改变,但是我们可以发现一个性质,就是当我们的i增加,pi也一定会增加(或者不变,总之不会减少),因此这个具有单调性,因此我们可以用二分查找,找到第一个pi大于R的i位置,也就是说,在这个i之前,我们计算就是按照pi-1-i+1来,后面就是按照R-i+1来,这样有什么好处呢?我们来看:
Part5. 代码与细节
cppclass Solution { public: vector<long long> countKConstraintSubstrings(string s, int k,vector<vector<int>>& queries) { int n = s.size(); vector<long long> sum(s.size() + 1);//前缀和数组 vector<long long> p(s.size(),n);//p[i]数组 提前初始化为n vector<int> num(2,0);//统计0 1出现的次数 { int right=0,left=0; for (; right < n; right++) //利用滑动窗口来填充p数组和前缀和数组 { num[s[right]-'0']++; while (num[0]>k&&num[1]>k) { num[s[left]-'0']--; p[left] = right;//此时right就是非法位置 直接赋值 sum[left+1]=sum[left]+right-1-left+1;//此时right为非法位置 要减去1才是合法的 left++; } } for(;left<n;left++)//在循环结束时 left还没到n 要让其跑完 填充 由于p数组已经在初始化的时候就已经赋值为n了 就不用这个在赋值 { sum[left+1]=sum[left]+right-1-left+1; } } vector<long long> ans(queries.size()); for(int i=0;i<queries.size();i++) { int left=queries[i][0],right=queries[i][1];//左右边界 int pos = upper_bound(p.begin(),p.end(),right)-p.begin();//二分查找 找第一个p[i]大于(不是大于等于 不是lower_upper())R的i if(pos<=left)//值得注意的是 这个pos可能在L的左边 这时候就按照R-i+1 直接等差数列求和 ans[i]=(long long)(right-left+1)*(right-left+2)/2; else ans[i]=sum[pos]-sum[left]+(right-pos+1)*(right-pos+2)/2; } return ans; } };
Part6. 结语
这道题综合了基本算法:滑动窗口、二分查找、前缀和。是复习和学习这些算法的好题。
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~





