已解答
困难
相关标签
相关企业
提示
给你一个 二进制 字符串 s
和一个整数 k
。
另给你一个二维整数数组 queries
,其中 queries[i] = [li, ri]
。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
- 字符串中
0
的数量最多为k
。 - 字符串中
1
的数量最多为k
。
返回一个整数数组 answer
,其中 answer[i]
表示 s[li..ri]
中满足 k 约束 的 子字符串 的数量。
思路:
首先,对于一个给定的l到r区间,可以分成两部分,即l到k和k到r,分界处使得l到k中任意子串都满足k约束,k到r可能满足可能不满足k约束,这样,l到k这一段区间的子字符串的个数就是k-l+1,k-l,k-l-1.......1,总共(k-l+1)*(k-1+2)/2;就是单纯等差数列求和。对于k到r这个区间,则可以考虑用前缀和数组来求解,规定前缀和数组是从0到当前位置的所有子字符串的数目,这样两端点相减就是k到r这一块的子字符串的个数。那么,根据分析,需要求的就是每一个端点的前缀和数组和每个点满足右侧某段点rr到当前点的区间内的所有点都符合子字符串的最大rr。
对于求解右端点,可以利用双指针的算法,思路是找每个右端点是哪个左端点的的符合条件的右端点,这样,对于一个右端点更大的位置,其对应的左端点一定是更大的,两指针都是只能向右侧移动,具体求解办法就是遍历字符串,对于下标i,如果当前j下标到i下标符合k约束(代码中是找到最右侧不符合的那个那个i),则r[j]=i,否则j++一直到符合k约束。对于求的前缀和,则可以利用j++直到符合k约束这一步,因为此时的j就是对于i这个位置,以i为右端点的最长符合k约束的子字符串,因此对于以i为右端点,长度在j到i之间的子串,一定都符合k约束,因此i这个点作为右端点所能提供的子字符串的个数就是i-j+1,这样就可以更新前缀和了。
对于每个查找区间l到r,首先找到l的右端点,然后和r比较,如果右端点小于r,则分成两个部分l到右端点和右端点到r分别求子字符串的个数,否则则只需要求l到r的子字符串的个数(此时表示l到r内任意长度子字符串都符合k约束)。
本题存在很多边界情况,且数组表示的意义很多是不符合条件的第一个边界,因此某些地方求长度实际上是需要主要要减小的。另外,还需要注意前缀和的下标。
class Solution {
public:
typedef long long LL;
vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {
int n=s.size();
vector<LL>sum(n+1,0);
vector<int>count(2,0);
vector<int>right(n,n);
for(int i=0,j=0;i<n;i++)
{
count[s[i]-'0']++;
while(count[0]>k&&count[1]>k)
{
count[s[j]-'0']--;
right[j]=i;
j++;
}
sum[i+1]=(LL)sum[i]+i-j+1;
}
vector<LL>res;
for(int i=0;i<queries.size();i++)
{
int l=queries[i][0],r=queries[i][1];
int j=min(right[l],r+1);
LL t1=(LL)(j-l)*(j-l+1)/2;
LL t2=sum[r+1]-sum[j];
res.push_back(t1+t2);
}
return res;
}
};