1. 程式人生 > >leetcode-91-解碼方法

leetcode-91-解碼方法

是把 進入 不變 一位 i++ a-z 時間 數字 增加

題目描述:

一條包含字母 A-Z 的消息通過以下方式進行了編碼:

‘A‘ -> 1
‘B‘ -> 2
...
‘Z‘ -> 26

給定一個只包含數字的非空字符串,請計算解碼方法的總數。

示例 1:

輸入: "12"
輸出: 2
解釋: 它可以解碼為 "AB"(1 2)或者 "L"(12)。

示例 2:

輸入: "226"
輸出: 3
解釋: 它可以解碼為 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

要完成的函數:

int numDecodings(string s)

說明:

1、這道題給定一個字符串,字符串中只含有數字,數字1可以解碼為A,數字2可以解碼為B……數字26可以解碼為Z。

要求判斷有多少種解碼的方式,最終返回解碼方式的個數。

比如[2,2,6],那麽可以解碼為2-2-6也就是BBF,也可以解碼為2-26也就是BZ,也可以解碼為26-2也就是ZB。

一共三種解碼方式,最終返回3。

2、這道題有兩種做法,一種是動態規劃,另一種是遞歸。

動態規劃比較快,可以beats 100.00% of cpp submissions,而遞歸只能beats 2.67% of cpp submissions。筆者把兩種做法都實現了一下,附在下文。

我們先看一下動態規劃的思路,動態規劃關鍵在於每個階段狀態的把握。

舉個例子,比如12224,我們首先對第一位,只能1這種解碼方法。

對第二位2,可以1-2(獨立),也可以12(合並),兩種解碼方法。

對第三位2,可以1-2-2(獨立),也可以12-2(獨立),也可以1-22(合並),三種解碼方法。

在這一步我們發現其實到當前位為止的解碼方法個數,就是上一步的解碼方法個數(在後面直接添加獨立的一位)+上一步獨立的個數(當然這裏要判斷能不能合並在一起)。

所以我們只需要記住上一步的解碼方法個數和上一步的獨立的個數,就可以分不同階段去處理。

再接下來對第四位2,可以1-2-2-2,也可以12-2-2,也可以1-22-2,這三個都是直接在後面添加獨立的一位,也可以1-2-22,也可以12-22,這兩個就是把先前獨立的一位給合並了,所以當前總的解碼個數是3+2=5,當前獨立的個數就是3,也是上一步的總解碼個數。

過程如下:

1 2 2 2
獨立可合並下一位的個數 1 1 2 3
總的解碼方式的個數 1 2 3 5
當前例子 1

1-2

12

1-2-2

12-2

1-22

1-2-2-2

12-2

1-22-2

1-2-22

12-22

規律十分清晰,但我們還有一個情況沒有考慮到,就是可能會出現數字0。

比如110,第二個1這一步,當前總的解碼方式有1-1和11,兩種,獨立可合並下一位的個數有一種。

然後到了0這一步,只能合並了,於是總的解碼方式變成上一步獨立可合並下一位的個數1,解碼方式是1-10,當前這一位的獨立可合並個數清空為0。

那還有不能合並的呢,比如130,3這一步,仍然是1-3和13,總的有2種,獨立的有1種。

到0這一步,不能合並,於是總的解碼方式變成0,完全不能解碼,返回0。

所以,構造代碼如下:(附詳解)

int numDecodings(string s)
    {
        if(s[0]==‘0‘)return 0;//邊界條件,如果第一位是字符0,那麽完全不能解碼,直接返回0
        int t1=1,t2=1,sum1,t3;//t1表示當前獨立可合並下一位的個數,t2表示當前總的解碼方式的個數
        for(int i=1;i<s.size();i++)//從第二位開始處理
        {
            sum1=(s[i-1]-‘0‘)*10+s[i]-‘0‘;//如果跟前一位合並,計算合並之後的數值
            if(sum1>=1&&sum1<=26)//如果數值符合條件,那麽可以合並
            {
                if(s[i]!=‘0‘)//當前位不是0,那麽t2加上t1的值,t1變成原本t2的值
                {
                    t3=t2;
                    t2+=t1;
                    t1=t3;
                }
                else//當前位是0,比如10這種情況,那麽t2變成t1的值,t1清空
                {
                    t2=t1;
                    t1=0;
                }
            }
            else//如果計算出來不能合並
            {
                if(s[i]!=‘0‘)//如果當前位不是0,比如227,後面的27就不能合並,於是t2不變,t1變成t2的數值
                    t1=t2;
                else//如果當前位是0,又不能合並,比如30這種情況,那麽直接返回0
                    return 0;
            }
        }
        return t2;//最終返回t2這個總的解碼方式的個數
    }

上述代碼實測0ms,beats 100.00% of cpp submissions。

如果有時間的話可以再看一下遞歸的做法,筆者最開始也是遞歸的思路,不斷地試探,這種思路比較熟悉。

沒時間的話就算啦,下面的文字可以直接略過。

舉個例子[2,2,2,2,2],我們先不斷遞歸,逐個處理,直至超出範圍,此時我們次數+1。

接著回退到上一層,也就是最後一個2,發現不能跟下一個數合並,於是再退到上一層,也就是倒數第二個2。

在這個時候發現可以合並,於是進入遞歸,但這時候下一個處理的數的位置要+2,而不是逐個處理。

接著再回退到上一層,發現第三個2和倒數第二個2可以合並,於是進入遞歸,這時候下一個要處理的數的位置+2,到達最後一個2那裏。

……

我們可以總結出遞歸的操作,對於每一位而言,分兩個步驟:

①進入對下一位的遞歸處理。

②結束①之後,判斷能否與下一位合並,進入對下下位的遞歸處理。

所以我們可以構造出如下代碼:

    int count=0,t;//全局變量
    void digui(string& s,int index)
    {
        if(index==s.size())//如果超出了範圍,說明當前的嘗試是成功的
        {
            count++;
            return;
        }
        digui(s,index+1);//第一個步驟
        t=(s[index]-‘0‘)*10+s[index+1]-‘0‘;//第二個步驟
        if(index+1<s.size()&&t<=26&&t>=1)
            digui(s,index+2);
    }
    int numDecodings(string s) 
    {
        digui(s,0);
        return count;
    }

上述代碼可以解決大部分情況,但是對於0的存在無能為力。比如10,只有一種解碼方式,但按照上述代碼,返回結果是2。

但其實處理到0這一位的時候,當前嘗試是失敗的,應該結束這種嘗試。

所以我們稍微修改一下代碼,如下:

    int count=0,t;
    void digui(string& s,int index)
    {
        if(s[index]==‘0‘)//增加了對於0的判斷
            return;
        if(index==s.size())
        {
            count++;
            return;
        }
        digui(s,index+1);
        t=(s[index]-‘0‘)*10+s[index+1]-‘0‘;
        if(index+1<s.size()&&t<=26&&t>=1)
            digui(s,index+2);
    }
    int numDecodings(string s) 
    {
        digui(s,0);
        return count;
    }

上述代碼可以通過測試,但是實測484ms,beats 2.67% of cpp submissions。遞歸太耗時間了。

leetcode-91-解碼方法