小明最近在做病毒自动检测,他发现,在某些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); } }