小明最近在做病毒自动检测,他发现,在某些library 的代码段的二进制表示中,如果包含子串并且恰好有k个1,就有可能有潜在的病毒。library的二进制表示可能很大,并且子串可能很多,人工分析不可能,于是他想写个程序来先算算到底有多少个子串满足条件。如果子串内容相同,但是开始或者结束位置不一样,则被认为是不同的子串。
注:子串一定是连续的。例如"010"有6个子串,分别是 "0, "1", "0", "01", "10", "010"
第一行是一个整数k,表示子串中有k个1就有可能是病毒。其中 0 <= k <= 1 000 000
第二行是一个字符串,就是library的代码部分的二进制表示。字符串长度 <= 1 000 000。并且字符串中只包含"0"或"1".
输出一个整数,所有满足只包含k个1的子串的个数。
1 1010
6
满足条件的子串有:"1", "1", "10", "01", "10", "010".
2 01010
4
满足条件的子串有: "101", "0101", "1010", "01010".
100 01010
0
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
int k=scanner.nextInt();
scanner.nextLine();
String s=scanner.nextLine();
int num=0,len=s.length();
int[] dp=new int[k+2];
long result=0;
dp[0]=1;
for(char c:s.toCharArray()){
if(c=='1') num++;
if(num-k>=0) result+=dp[(num-k)%(k+2)];
dp[(num+1)%(k+2)]=0;
dp[num%(k+2)]++;
}
System.out.println(result);
}
} 这是一个子串满足问题,输入一个由0,1字符组成的字符串str,求满足包含k个1的连续字串个数。 毫不夸张的说,可能大部分人第一眼就是求出所有子串,再计数所有满足k的子串,但仔细一想,问题没有那么简单! 思路就是,采用滑动窗口的方式,从字符串数组左侧依次向右滑动,直至满足k个1,在下一个数不为1且数组不越界的情况下窗口向右滑动,同时计数。 核心代码如下:public class Main { public static int NumSubString(int k, String str) { char[] chars = str.toCharArray(); int res = 0; int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] == '1') { res++; } } if (res < k) { return 0; } res = 0; for (int i = 0; i < len; i++) { /*滑动索引*/ int index = i; int count = 0; while (count < k && index < len) { if (chars[index] == '1' && ++count == k) { res++; index++; break; } index++; } /*满足k后继续右滑*/ while (index < len && chars[index] != '1') { res++; index++; } } return res; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int k = sc.nextInt(); sc.nextLine(); String str = sc.next(); System.out.println(NumSubString(k,str)); }
package 快手2020校园招聘秋招笔试工程C试卷;
import java.lang.*;
import java.util.*;
/**
* Project: 通过率:100%
* Author : en_zhao
* Email :
* Date : 2020/08/03
*/
public class Main001{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
sc.nextLine();
String str = sc.nextLine();
//防溢出
long res = 0;
long[] sum = new long[str.length() + 1];
char[] str_arr = str.toCharArray();
int count = 0;
//时间:O(n)扫描 空间: O(n)
//sum[i]表示第(i+1)个1左边的0的个数
//最后 记录末尾0的个数 数据可能造成空间浪费
for(int i = 0;i < str.length();i++){
if(str_arr[i] == '1'){
count++;
}else{ //记录第n个1左边0的个数
sum[count]++;
}
}
//计算res
if(k == 0){ // 当k==0 直接求连续0的子串之和
for(int i = 0; i <= count;i++){
res += sum[i]*(sum[i]+1)/2;
}
}else{ // 当k!=0 求左右两边的子串组合(+1是因为空串"")
for(int i = 0; i + k <= count;i++){
res += (sum[i]+1) * (sum[i+k] + 1);
}
}
System.out.println(res);
}
} import java.util.Scanner;
public class Main{
/**
* 仔细观察和计算,相信大家都能够发现,对于任何一个已经计算好的字符串,往它的后面追加一个字符后
* 对结果的有影响的字符串只是倒数第k+1个'1'之后的子串
* 例: k = 2; str = 01010; result = 4
* 如果往str后面追加1,str = 010101,对最终结果有影响的是0101(01'0101')子串
* 如果往str后面追加0,str = 010100,对最终结果有影响的是010100('010100')子串
* 影响主要有两个方面:
* 影响一:不管追加的是0,还是1,最终结果都会增加1个匹配字串
* 影响二:如果倒数第k位1前面有0,则每个0都会增加1个匹配子串
*
* 那么可以根据这一特性保存最后k位'1'左边的0的数量,就能够利用前面字串已经计算好的结果,来对追加后字符串的结果进行计算
*
*
*/
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int k = Integer.parseInt(input.nextLine());
String str = input.nextLine();
long result = 0; //结果可能会非常大
int oneNum = 0;
int[] kCharNum = new int[k + 1]; //建立k+1位的数组,记录最后k位'1'左边0的数量,以及后面可能出现的'1'左边0的数量,所以需要k+1位
kCharNum[0] = 1; //因为影响一,所以提前加1,也是为了解决k=0的情况
for(int i = 0; i < str.length(); i++){
if(str.charAt(i) == '1'){
oneNum++;
//num已经加1了,需要将倒数k+1位'1'的数据清空,重新利用计数
kCharNum[oneNum % (k + 1)] = 0;
}
if(oneNum >= k){
//result += 从当前最近1的位子往左数k位的那个1,它左边0的数量 + 1
//因为已经提前加了1,所以只需加上倒数第k位0的数量(里面已经包含了加1)
result += kCharNum[(oneNum - k) % (k + 1)];
}
//如果当前字符是'0',那么加1
//如果当前字符是'1',则提前加1
//所以都需要加1
kCharNum[oneNum % (k + 1)]++;
}
System.out.println(result);
input.close();
}
}
上个 的暴力吧:
#include <bits/stdc++.h>
const int maxn = 1e6 + 5;
char s[maxn];
int sum[maxn],k;
int main() {
scanf("%d%s", &k,s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + (s[i] == '1');
long long ans = 0;
for (int j = 1; j <= n; ++j) {
ans += std::upper_bound(sum, sum + j, sum[j] - k) -
std::lower_bound(sum, sum + j, sum[j] - k);
}
printf("%lld\n", ans);
return 0;
}
这个明明是个O(n)的尺取,额外空间是O(1)的。
弄个左右指针框住一段恰好k个1的最短的区间,然后左右延伸出去数一数两边的0。
#include <iostream>
(720)#include <vector>
#include <string>
using namespace std;
int main(){
int k, zero_cnt = 0;
long long res = 0;
string s;
char ch;
cin >> k;
cin >> s;
vector<int> keys, zeros; //分别记录1的位置和1前面的0的个数
for(int i=0;i<s.length();i++){
ch = s[i];
if(ch == '1') {
keys.push_back(i);
zeros.push_back(zero_cnt);
zero_cnt = 0; //计数归零
}
else if(ch == '0') zero_cnt += 1;
}
if(ch == '0') zeros.push_back(zero_cnt);
else zeros.push_back(0);
if(k == 0){
long long num;
for(int i=0;i<zeros.size();i++){
num = zeros[i];
res += num * (num + 1) / 2;
}
}
else if(keys.size() < k) res = 0;
else{
int i = 0, j = k-1;
while(j < keys.size()){
res += (zeros[i] + 1) * (zeros[j+1] + 1);
i++; j++;
}
}
cout << res;
return 0;
}
2 4 9 10 14 18
考虑第5个1,位置是14,假如k=3,第2个1的位置是4。
那么包含9,10,11这三个1的所有可能性可以表示为:
右侧滑动范围(14,18],左侧滑动范围((4,9],可能性有(18-14)*(9-4)=20种。
然后改变末位1的位置求得总共的可能性。
下附代码:
package main
import "fmt"
func main() {
var k,ans int
var str string
fmt.Scan(&k,&str)
pos:= []int{-1}
for i,v:=range(str){
if v==49{ //"1"的ASCII码为49
pos=append(pos, i)
}
}
if k>=len(pos){
fmt.Println(0)
return
}
pos=append(pos, len(str))
if k==0{
for i:=k;i<len(pos)-1;i++{
ans+=(pos[i+1]-pos[i]-1)*(pos[i+1]-pos[i])/2
}
}else {
for i := k; i < len(pos)-1; i++ {
ans += (pos[i+1] - pos[i]) * (pos[i-k+1] - pos[i-k])
}
}
fmt.Println(ans)
}
//引用楼上思路:数出所有‘1’的左右各有多少0,
//然后第n个1的左边的0的个数为a,
//第n + k个1的右边的0的个数为b,
//那么这个组合有(a + 1) * (b + 1)种可能,
//将所有可能加起来就可以了。
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<unordered_map>
using namespace std;
int main()
{
int k; string s;
while (cin >> k >> s)
{
int sum = 0;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == '1')
sum++;
}//计算1的个数
if (sum < k)
{
cout << 0 << endl; break;
}//特殊情况1的个数小于k
vector<pair<int,int>> zeros(s.size()+1,pair<int,int>(0,0));
long num = 0; long long res = 0;//结果必须用Long long 存储
int i = 0;
for (; i < s.size();i++)
{
if (s[i] == '1')//计算每个1右侧0的个数
{
//顺便计算k=0时的种类数,比如有三个连续0,则种类数位1+2+3=3*(1+3)/2
zeros[i].first = num; res +=(long long)num * (num + 1) / 2;// cout << num;
num = 0;
}
else
num++;
}
//cout << endl;
if(s[--i]=='0')//考虑循环中不是1结尾的情况
res+=(long long)num * (num + 1) / 2;
if (k == 0)
{
cout << res << endl; break;//k=0时特殊处理
}
num = 0;
i = s.size() - 1;
for (; i >=0; i--)//统计1的左侧0的个数
{
if (s[i] == '1')
{
zeros[i].second = num; //cout << num;
num = 0;
}
else
num++;
}
//cout << endl;
res = 0;
if (k == 1)//如果k等于1,则对于每个1,种类数等于左侧的0的个数+1与右侧0的个数+1的乘积
{
for (int i = 0; i < s.size(); i++)
{
if(s[i]=='1')
res+= (zeros[i].first + 1) * (zeros[i].second + 1);
}
cout << res << endl; break;
}
int left = 0; //如果k不等于1,则对于每个k个1,种类个数,等于最左侧的1左侧零的个数+1与最右侧1的右侧0的个数+1的乘积
while (left < s.size() && s[left]!= '1')
left++;
int l = 0; int right = 0; bool kk = 0;
while (right < s.size() && l < k)
{
if (s[right] == '1')l++; right++; kk = 1;
}//此时left和right之间包含k个1,且分别为左右端点
res = 0; if(kk)right--;
while (left <= right && right < s.size())
{
if (l == k && s[left] == '1')//计算
res += (zeros[left].first + 1) * (zeros[right].second + 1);
if (l == k)//左侧移动到下一个1
{
left++;
while (s[left] != '1')
left++;
l--;
}
else
break;
if (l < k)//右侧移动到下一个1
{
right++;
while (right<s.size()&&s[right] != '1')
right++;
l++;
}
}
cout << res << endl;
}
return 0;
} #include<iostream>
#include<string>
#include<vector>
using namespace std;
typedef long long ll;
ll get_res(string &s, int K) {
ll res = 0;
ll count = 0;
vector<int> Index;
Index.push_back(-1);
for (int i = 0; i < s.size(); i++) {
if (s[i] == '1') {
count++;
Index.push_back(i);
}
}
if (count < K)return res;
Index.push_back(s.size());
count = 0;
int i = 1;
if (K == 0) {
if(Index.size()==2)return ll((1+s.size())*s.size()/2);
while (i < Index.size()) {
count += (1+Index[i]-Index[i-1]-1)*(Index[i] - Index[i - 1] - 1)/2;
i++;
}
return count;
}
while (i + K - 1 < Index.size()-1) {
int temp1 = Index[i] - Index[i - 1] - 1;
count += 1+(Index[i]-Index[i-1]-1 + Index[i + K] - Index[i+K-1] - 1 + (Index[i] - Index[i - 1] - 1) * (Index[i + K] - Index[i+K-1] - 1));
i++;
}
return count;
}
int main(){
int K;
cin>>K;
string s;
cin>>s;
ll res = get_res(s,K);
cout<<res;
return 0;
}
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int main(){
int k;
cin>>k;
if(k==0){
getchar();
char c;
long long Number0=0;
long long sum=0;
while((c=getchar())!=-1){
if(c=='0'){
Number0++;
}else if(c=='1'){
//cout<<Number0<<"个0"<<endl;
sum+=Number0*(Number0+1)/2;
Number0=0;
}
}
sum+=Number0*(Number0+1)/2;
//cout<<Number0<<"个0"<<endl;
cout<<sum<<endl;
return 0;
}
char buff[1000001];
cin>>buff;
int sum=0;//组合个数
int l=0;//左边的点,一般右自由度计算完毕,就用它计算左自由度,计算完之后再指向下一个节点的下一个方便下次计算
int r=0;
int lNumb=0;//当前窗口左边的自由度
int rNumb=0;//当前窗口右边的自由度
int count=0;//当个数满足后,计算右边自由度,乘法即可求出数量
while(buff[r]!=0){
if(buff[r]=='1'){//右边找到1时,如果++count==k,就开始计算右边自由度,如果>k说明自由度计算完毕
if(count==k){//已经计算完右边自由度,该计算左边自由度
do{
lNumb++;
}
while(buff[l++]!='1');
//cout<<"左边自由度"<<lNumb<<endl;
//cout<<"右边自由度"<<rNumb<<endl;
sum+=lNumb*rNumb;
lNumb=0;
rNumb=0;
count--;
continue;
}else{
count++;
}
}
if(count==k){//计算右边自由度,得到结果
rNumb++;
}
r++;
}
if(count==k){
do{
lNumb++;
}
while(buff[l++]!='1');
//cout<<"左边自由度"<<lNumb<<endl;
//cout<<"右边自由度"<<rNumb<<endl;
sum+=lNumb*rNumb;
}
cout<<sum<<endl;
return 0;
} 第一阶段,k!=0时, 优化前面楼主的代码,子串匹配问题。子串要求有k个1,则创建一个k+1大小的数组,进行子串表示。(低n个字符是否能满足子串,当第n个字符为0时,跟前面k个1有关,当第n个字符为1时,跟前面k-1个1有关)。长度为k+1的数组status存放了相连的k+1个1后面0的个数。如第n个字符为子串的最后一个字符,则该子串必须包含第n字符以及前面相连最近的k个为1的字符,可以在字符的前面加0,每添加一个连续的0,则多一个子串的组合。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
sc.nextLine();
String str = sc.nextLine();
char[] strChar = str.toCharArray();
int[] status = new int[k + 2];
status[0] = 1;
int num = 0;
long result = 0;
for (char c : strChar) {
if (c == '1') num++;
if (c == '1') status[(num) % (k + 1)] = 0;
if (num >= k) result+= status[(num - k) % (k + 1)];
status[num % (k + 1)]++;
}
System.out.println(result);
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int k = scan.nextInt();
String s = scan.next();
char[] arr = s.toCharArray();
int count = 0, N = s.length(), map[] = new int[N + 2];
long result = 0;
map[0] = -1;
for(int i = 0; i < N; i++) {
char c = arr[i];
// 当 K 为 0 的情况下
if(c == '0') {
if(k == 0) {
result += (i - map[count]);
} else if(count >= k) {
result += map[count - k + 1] - map[count - k];
}
} else {
map[++count] = i;
if(count >= k && k != 0) { // 这里 k!=0 因为当 k 为0, 则所有 1 直接失效了
result += map[count - k + 1] - map[count - k];
}
}
}
System.out.println(result);
}
}