计数二进制子串(力扣第696题)
题目:
给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。
重复出现的子串要计算它们出现的次数。
示例:
输入: "00110011"
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。
请注意,一些重复出现的子串要计算它们出现的次数。
另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。
输入: "10101"
输出: 4
解释: 有4个子串:“10”,“01”,“10”,“01”,它们具有相同数量的连续1和0。
分析
符合计数要求的子串,必须满足三个条件:
1、必须有0和1
2、0和1的数量相同
3、0和1必须连续结合在一起,即0和1不能交叉
方法一:
暴力法,即从第一个字符开始遍历,判断从这个字符位置开始向后的子串是否有符合要求的,如果有则计数,如果没有则进行下一次遍历。在遍历的过程,从当前子串的第一个字符开始,如果其下一个字符与当前字符不相同,那么就一定是“01”或者“10”,则符合要求,直接计数,然后开始下一次遍历;如果相同,则继续遍历,记录当前连续字符的个数,然后遇到不同的字符退出当前循环,重新对新的字符进行遍历,对之前连续字符的个数进行减1运算,直到为0,则停止,并计数。
暴力法,超出了时间要求,因此性能不好。
private int subStr_nums = 0;
public int countBinarySubstrings(String s) {
for (int i = 0; i < s.length() - 1; i++) {
extendsSub(s,i,i+1);
}
return subStr_nums;
}
private void extendsSub(String s,int start,int end){
if (s.charAt(start) != s.charAt(end)){
subStr_nums ++;
return;
}else if (s.charAt(start) == s.charAt(end)){
int nums1 = 0;
while (start < s.length()-1 && s.charAt(start) == s.charAt(end)){
nums1++;
start++;
end++;
}
while (end < s.length()-1 && s.charAt(end) == s.charAt(end+1) && nums1>0){
nums1--;
end++;
}
if (nums1 == 0){
subStr_nums++;
}
}
}
方法二:
因为要寻找的符合要求的子串是0和1必须连续,不能交叉,所以这个子串可以分为两个部分,当我们在遍历整个字符串的时候,可以定义两个变量,即curLen和preLen,分别记录当前遍历的连续子串的长度,和前一次遍历的另一个连续子串的长度,只要当前连续子串的长度小于前一次连续子串的长度,那么就一定可以找到符合要求的子串。同时,curLen变为preLen的条件是,遍历字符的时候遇到了不相同的字符。
public int countBinarySubstrings1(String s) {
int preLen = 0, curLen = 1, count = 0;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == s.charAt(i - 1)) {
curLen++;
} else {
preLen = curLen;
curLen = 1;
}
if (preLen >= curLen) {
count++;
}
}
return count;
}