Leetcode:累加數

累加數是一個字符串,組成它的數字可以形成累加序列。

一個有效的累加序列必須至少包含 3 個數。除了最開始的兩個數以外,字符串中的其他數都等於它之前兩個數相加的和。

給定一個只包含數字 '0'-'9' 的字符串,編寫一個算法來判斷給定輸入是否是累加數。

說明: 累加序列裏的數不會以 0 開頭,所以不會出現 1, 2, 03 或者 1, 02, 3 的情況。

示例 1:

輸入: "112358"
輸出: true 
解釋: 累加序列爲: 1, 1, 2, 3, 5, 8 。1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8

示例 2:

輸入: "199100199"
輸出: true 
解釋: 累加序列爲: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199

進階:
你如何處理一個溢出的過大的整數輸入?

 

思路

3個要點:

1、如何處理過大的溢出的數。

不用數的加法,直接用字符串進行相加運算,最後判斷字符串是否相同。所以需要一個字符串相加的輔助函數。

2、如何遞歸

數字累加和等於後面一個數,看上去像是猆波那契數列,是可以用遞歸來實現的。也即需要將一個問題劃分爲一個子問題。

遞歸方法:

例如“ABCDEF”,假設A+B=C,第一次劃分,A、B、CDEF,A+B!=CDEF,此時可從CDEF中找出是否有前A、B的和,如果有,也即C,那麼前面就可以是符合條件的串,接下來就看B、C、DEF能不能拆成符合條件的劃分,由於少了一個元素,而且將前2個元素也劃分好了,也即我們的問題變成了一個子問題。如此就可以一直遞歸下去。

需要注意的是遞歸只是用來驗證某種初始劃分的基礎上,繼續對後續元素劃分能否符合要求。所以,還需要一個初始劃分的循環給遞歸一個輸入狀態。初始狀態的集合要確保是全面的。

3、如何劃分初始狀態

將整個數組劃分爲3段,然後交給遞歸help函數去判斷能否符合累加規律。等同於在數組中插入2個隔板將數組分成3段,共有C_{n-1}^2。用代碼來實現,一個二重循環即可,循環的計數從[0,n-1),表示在當前元素後面插入隔板。內循環的起始值從外循環當前計數值加1開始。

代碼如下:

        bool isAdditiveNumber(string num) {
            for(int i=0; i+2<=num.size(); i++){
                for(int j=i+1; j+2<=num.size(); j++){
                    if(check(num.substr(0,i+1), num.substr(i+1,j-i), num.substr(j+1))) return true;
                }
            }
            return false;
        }
        bool check(string num1, string num2, string num){
            if(num1.size()>1 && num1[0]=='0' || num2.size()>1 && num2[0]=='0') return false;
            string sum=add(num1, num2);
            if(num==sum) return true;
            if(num.size()<=sum.size() || sum.compare(num.substr(0,sum.size()))!=0) return false;
            //或者用下面這條替換上面語句
            //if(num.size()<=sum.size() || sum!=num.substr(0,sum.size())) return false;
            else return check(num2, sum, num.substr(sum.size()));
        } 
        string add(string n, string m){
            string res;
            int i=n.size()-1, j=m.size()-1, carry=0;
            while(i>=0 || j>=0){
                int sum=carry+(i>=0 ? (n[i--]-'0') : 0) + (j>=0?  (m[j--]-'0') : 0);
                res.push_back(sum%10+'0');
                carry=sum/10;
            }
            if(carry) res.push_back(carry+'0');
            reverse(res.begin(), res.end());
            return res;
        }

注意事項:
check函數中不對第3個字符串即num判斷是否是以0開頭,因爲num可能是多個字符串,比如"000",人家想表達的是0+0=0,這個是不違反規則的。

注意 i+2<=num.size()與i<=nums.size()-2的區別。

無符號數減一個大於自身的數會翻轉變成一個非常大的數,循環會越界,甚至死循環。

優化初始狀態:

第3個串長度應該不小於前2個串。所以可以限定下前2個串的長度,修改計數器含義,令計數器爲串1和串2長度,最小長度均爲1,剩下的爲串3元素。可以讓串1長度小於等於數組長度一半,然後讓串2長度小於等於剩下數組的一半。如果還要再優化,可能出現初始集合不全的情況。代碼如下:

        bool isAdditiveNumber(string num) {
            for(int i=1; i<=num.size()/2; i++){
                for(int j=1; j<=(num.size()-i)/2; j++){
                    if(check(num.substr(0,i), num.substr(i,j), num.substr(i+j))) return true;
                }
            }
            return false;
        }

算法並非自己想出來。乃是leetcode大神的討論與分享。優化版本的初始集是大神手筆。

鏈接參考

https://leetcode.com/problems/additive-number/discuss/75576/0ms-concise-C%2B%2B-solution-(perfectly-handles-the-follow-up-and-leading-0s)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章