leetcode题解-696. Count Binary Substrings

从今天开始刷字符串部分的题目,这部分我会记录每一题的简短思路,方便最后写一个总结性的博客。接下来先看一下第一道题696. Count Binary Substrings:

题目:

Give a string s, count the number of non-empty (contiguous) substrings that have the same number of 0's and 1's, and all the 0's and all the 1's in these substrings are grouped consecutively.

Substrings that occur multiple times are counted the number of times they occur.

Example 1:
Input: "00110011"
Output: 6
Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".

Notice that some of these substrings repeat and are counted the number of times they occur.

Also, "00110011" is not a valid substring because all the 0's (and 1's) are not grouped together.
Example 2:
Input: "10101"
Output: 4
Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's.
Note:

s.length will be between 1 and 50,000.
s will only consist of "0" or "1" characters.

本题是给定一个01二进制字符串,看有多少个包含0和1数目相同的子字符串,而且要求是连续的0和1,也就是说像0101这样的不行,因为两个0不是连续的。首先我想到暴力解法,也就是两次循环,内循环求其符合要求的子串,代码如下所示:

    public static int countBinarySubstrings(String s) {
        int res = 0;
        //遍历每个字符
        for(int i=0; i//记录0或者1的个数
            int count=0, j;
            //是否出现两种字符的标志
            boolean flag = false;
            //遍历i之后的字符,寻找符合条件的子串
            for(j=i; j1; j++) {
                if (!flag) {//说明是前面连续出现的0或者1,如果前后两个字符相等那么就把count++,否则说明出现了第二个字符,把flag置为true;
                    if (s.charAt(j) == s.charAt(j + 1))
                        count++;
                    else {
                        flag = true;
                        count++;
                    }
                }else{//说明已经出现了两种字符,这时如果前后两个字符相等,就把count--,直到count变成0,说明找到了0和1个数相等的连续子串,如果不相等那么就直接返回,说明对于本字符没有符合条件的子串
                    if(s.charAt(j) == s.charAt(j+1)) {
                        count--;
                        if(count == 0)break;
                    }
                    else {
                        count --;
                        break;
                    }
                }
            }

            //如果count==0且flag为真(这个条件是为了避免最后一个字符没有执行内循环直接返回的情况,那时count肯定为0,但是flag为false)则res+1。
            if(count == 0 && flag)
                res++;

            //因为最后一个字符没有办法遍历到,所以添加这么一个条件,当最后一个字符与前面字符相等且count为1时,表明也是满足条件的子串
            if(flag && j == s.length()-1 && count == 1)
                res ++;

        }
        return res;
    }

但是上面这种方法效率太低,会爆TLE的错误,所以我们就要进行改进,观察到其实可以把字符串s转化为0和1的堆。譬如,“1111000011010001011”转化为“4 4 2 1 1 3 1 1 2 ”,也就是统计一下每个连续子串的个数,这样我们就可以方便的获得满足条件的子串个数,统计转化后的数组相邻元素之间最小的那个求和即可。代码如下:

    public static int countBinarySubstrings1(String s){
        int [] count = new int[s.length()];
        int tmp = 0;
        //将字符串转化为子串数组,其实下面这段代码可以改成i=1开始,到s.length(),这样就可以省去下面的判断语句
        for(int i=0; i1; i++){
            count[tmp] ++;
            if(s.charAt(i) != s.charAt(i+1))
                tmp ++;
        }
        if(s.length() > 1 && s.charAt(s.length()-1) == s.charAt(s.length()-2))
            count[tmp] ++;
        else
            count[tmp] ++;

        int res = 0;
        //对相邻元素箭的较小值进行求和
        for(int i=0; icount[i], count[i+1]);
        }
        return res;
    }

上面这段代码可以击败48%的用户,但是我发现了一种改进效率的黑科技,就是把字符串变成数组进行遍历,这样效率大幅度提升可以击败85%的用户,代码如下:

    public static int countBinarySubstrings5(String s){
        int len=s.length();
        if(len<=1) return 0;
        char[] sc= s.toCharArray();
        int [] count = new int[len];
        int tmp = 0;
        for(int i=0; i1; i++){
            count[tmp] ++;
            if(sc[i] != sc[i+1])
                tmp ++;
        }
        if(sc[len-1] == sc[len-2])
            count[tmp] ++;
        else
            count[tmp] ++;

        int res = 0;
        for(int i=0; icount[i], count[i+1]);
        }
        return res;
    }

我们继续看还有没有什么改进的方案,因为上面代码中使用了O(N)的空间复杂度,其实可以把这部分额外的空间给释放出来,我们看下面这段代码,不仅空间复杂度为常数,而且时间复杂度也稍有提升,可以击败90%的用户:

    public int countBinarySubstrings3(String s) {
        int len=s.length();
        if(len<=1) return 0;
        char[] sc= s.toCharArray();
        int i=0,prev=-1,res=0;
        while(iint j=i;
            char c=sc[i];
            //统计相同元素的个数
            while(iint cur=i-j;
            //对相邻连续子串的较小值进行求和。这里使用两个变量来代替之前的一个数组,而且再一次遍历中执行计数和求和两部分功能
            if(prev!=-1) res+=Math.min(prev,cur);
            prev=cur;
        }
        return res;
    }

此外,还有一种方案,跟这个相似,省去了内部循环计数的操作,而直接遍历一次通过交换两个相邻连续子串长度的方法,每次res只加1,而不是加相邻计数的较小值,代码如下,可以击败94%的用户:

    public int countBinarySubstrings2(String s) {
        int len=s.length();
        if(len<=1) return 0;
        char[] sc= s.toCharArray();
        int prevRunLength = 0, curRunLength = 1, res = 0;
        for (int i=1;iif (sc[i] == sc[i-1]) curRunLength++;
            else {
                prevRunLength = curRunLength;
                curRunLength = 1;
            }
            if (prevRunLength >= curRunLength) res++;
        }
        return res;
    }

你可能感兴趣的:(leetcode刷题)