No.0003-CareerCup

Given a length n, return the number of strings of length n that can be made up of the letters 'a', 'b', and 'c', where there can only be a maximum of 1 'b' and can only have up to two consecutive 'c's.
Example:
findStrings(3) returns 19
since the possible combinations are:
aaa,aab,aac,aba,abc,aca,acb,baa,bac,bca,caa,cab,cac,cba,cbc,acc,bcc,cca,ccb and the invalid combinations are:
abb,bab,bba,bbb,bbc,bcb,cbb,ccc
給出一個長度n,求只包含字母a、b、c,且最多只能有1個b,不包含連續的超過兩個的c的,長度為n的字符串的數目。

1. 詢問

首先要完全明白題意。根據題目的意思,a和b很好理解,c的約束條件則意味著ccacc這樣的是滿足的。

2. 分析

暴力破解

把所有abc構成的長度為n的字符串都造出來,然后剔除掉不符合的。復雜度O(3^n)。

為何低效

有在明知道前面已經不可能的情況下還繼續去試,這就是暴力破解低效的原因。
假如使用DFS遞歸,則每一步都需要知道之前的狀態,如果每一步再檢查整個字符串一次過于低效,于是選擇把狀態放入參數里面一起帶著傳下去。什么參數很重要?有沒有使用過'b',和末尾'c'的數目。根據這兩個參數,就可以知道如何添加字母。
這是一種解法,但時間復雜度不是很直觀。雖然知道比起暴力破解肯定效率提高了很多,但具體多少?
而受到這種思路的啟發,可以用DP來解決這個問題。

DP解法

和上面的思路類似,構造三維dp矩陣,dp[i][j][k],i代表對應字符串的長度,j可選0或1,0表示沒有使用b,1表示使用過;k可選012,表示末尾連續c的長度。
因此,在已知上一個dp[i]的情況下,下一個dp[i+1]也可以推導出來:

  • dp[i+1][0][0] = dp[i][0]:這表示在之前沒有使用'b'的字符串后新添加一個'a',那么無論之前后面有多少'c',被'a'分隔之后,現在的末尾連續'c'都是0了。
  • dp[i+1][1][0] = dp[i]:假如之前使用過'b',那么末尾添加'a',否則添加'b'。
  • dp[i+1][0][1] = dp[i][0][0]:要求添加完成之后末尾只能有一個'c',那么只能在最后加'c',也就是說之前末尾不是'c'。'b'的使用情況繼承。
  • dp[i+1][1][1] = dp[i][1][0]:同上。
  • dp[i+1][0][2] = dp[i][0][1]:類似地,要有兩個'c',之前需要有一個'c'。
  • dp[i+1][1][2] = dp[i][1][1]:同上。

由此,得到所有遞推公式。
確定初始邊界值:dp[1][0][0] = 1 (a); dp[1][0][1] = 1 (c); dp[1][1][0] = 1 (b); dp[1][1][1],dp[1][0][2],dp[1][1][2]=0。
時間復雜度O(n),空間復雜度O(n)。

3. 代碼

class Solution:
    def findString(self, n):
        if not n:
            return 0
        dp = [[[0] * 3 for _ in range(2)] for _ in range(n + 1)]
        dp[1][0][0] = 1
        dp[1][0][1] = 1
        dp[1][1][0] = 1
        for i in range(2, n + 1):
            dp[i][0][0] = dp[i - 1][0][0] + dp[i - 1][0][1] + dp[i - 1][0][2]
            dp[i][1][0] = 0
            for k in range(2):
                for h in range(3):
                    dp[i][1][0] += dp[i - 1][k][h]
            dp[i][0][1] = dp[i - 1][0][0]
            dp[i][1][1] = dp[i - 1][1][0]
            dp[i][0][2] = dp[i - 1][0][1]
            dp[i][1][2] = dp[i - 1][1][1]
        ret = 0
        for k in range(2):
            for h in range(3):
                ret += dp[-1][k][h]
        return ret

4. 總結

難度Hard。DP在只求一個數值時確實是非常高效的解法。假如要列出所有符合條件的字符串,可能DFS+遞歸更加好用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容