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<s.length(); i++){
            //記錄0或者1的個數
            int count=0, j;
            //是否出現兩種字符的標誌
            boolean flag = false;
            //遍歷i之後的字符,尋找符合條件的子串
            for(j=i; j<s.length()-1; 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; i<s.length()-1; 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; i<tmp; i++){
            res += Math.min(count[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; i<len-1; 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; i<tmp; i++){
            res += Math.min(count[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(i<len){
            int j=i;
            char c=sc[i];
            //統計相同元素的個數
            while(i<len && sc[i]==c) i++;
            int 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;i<len;i++) {
            if (sc[i] == sc[i-1]) curRunLength++;
            else {
                prevRunLength = curRunLength;
                curRunLength = 1;
            }
            if (prevRunLength >= curRunLength) res++;
        }
        return res;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章