Given a string S
of digits, such as S = "123456579"
, we can split it into a Fibonacci-like sequence [123, 456, 579].
Formally, a Fibonacci-like sequence is a list F
of non-negative integers such that:
0 <= F[i] <= 2^31 - 1
, (that is, each integer fits a 32-bit signed integer type);F.length >= 3
;- and
F[i] + F[i+1] = F[i+2]
for all0 <= i < F.length - 2
.
Also, note that when splitting the string into pieces, each piece must not have extra leading zeroes, except if the piece is the number 0 itself.
Return any Fibonacci-like sequence split from S
, or return []
if it cannot be done.
Example 1:
Input: "123456579" Output: [123,456,579]
Example 2:
Input: "11235813" Output: [1,1,2,3,5,8,13]
Example 3:
Input: "112358130" Output: [] Explanation: The task is impossible.
Example 4:
Input: "0123" Output: [] Explanation: Leading zeroes are not allowed, so "01", "2", "3" is not valid.
Example 5:
Input: "1101111" Output: [110, 1, 111] Explanation: The output [11, 0, 11, 11] would also be accepted.
Note:
1 <= S.length <= 200
S
contains only digits.
這道題給了我們一個字符串,讓我們分割成斐波那契序列,至少要分成三個數,並且滿足斐波那契數列的性質。關於其性質,博主有個口訣可以快速記憶,那就是大學食堂里今天的湯是昨天的湯加上前天的湯。題目中給的例子挺多的,便於理解題意,其中例子4還強調了不能有leading zeros。但是關於overflow的test case卻只字未提,害的博主fail了N多次,才最終handle了所有的溢出的錯誤。由例子5我們可以看出,符合題意的數列其實可能不止一種,但是本題就讓返回一個就行了。不管返回幾個,總之不是求極值,DP在這里就不好使了,只能用遞歸了,由於不知道如何分割,所以肯定需要遍歷所有的情況。我們用一個數組out來記錄已經組成的序列,用結果res來保存結果。當out數組的個數大於等於3,並且已經遍歷完了字符串S,那么此時就是可以把out數組中的內存賦值給結果res了,那么之后只要檢測結果res不為空時,直接返回就可以了,這是個很好的剪枝操作,因為此題只需要一個正確答案即可(返回所有情況將作為follow up在本文的底部討論)。
現在來考慮遞歸函數的主體該怎么寫,既然不知道要如何分割,那么就要嘗試所有的情況,一個數字,兩個數字,一直到末尾,那么就可以遍歷字符串S,然后取子串即可。但從什么位置開始呢,每次都從頭嗎,這道題都數字不能重復使用,所以應該用個變量start來記錄當前遍歷到的位置,那么我們從start位置起,每次取 i-start+1 長度的子串 cur,此時在轉為int之前,需要先處理leading zeros的情況,判斷若cur長度大於1,且首字符為0,直接break,還就是若cur的長度大於10,也break,為啥呢?因為整型的最大值是 2147483647,只有10位,所以當cur長度大於10時,一定會溢出。當cur長度為10時,也有可能溢出,這個在之后處理。好,現在將cur轉為長整型 long,因為長度為10也可能溢出,所以要先轉為長整型,然后在判斷若大於整型最大值 INT_MAX,直接break。接下來就要考慮是否要加入out數組了,當out數字的個數不到2個的時候,我們可以直接加入當前數字,若大於等於2個,需要考慮是否滿足斐波納切數列的性質,即當前數字是否等於前兩個數字之和,滿足的話才加入,不然就跳過,注意這里不能直接break,因為之后的數字也許可能滿足要求。加入out數組之后,就可以調用遞歸了,此時起始位置傳入 i+1,之后再恢復out的狀態即可,參見代碼如下:
class Solution { public: vector<int> splitIntoFibonacci(string S) { vector<int> res, out; helper(S, 0, out, res); return res; } void helper(string& S, int start, vector<int>& out, vector<int>& res) { if (!res.empty()) return; if (start >= S.size() && out.size() >= 3) { res = out; return; } for (int i = start; i < S.size(); ++i) { string cur = S.substr(start, i - start + 1); if ((cur.size() > 1 && cur[0] == '0') || cur.size() > 10) break; long num = stol(cur), len = out.size(); if (num > INT_MAX) break; if (out.size() >= 2 && num != (long)out[len - 1] + out[len - 2]) continue; out.push_back(num); helper(S, i + 1, out, res); out.pop_back(); } } };
討論:這道題只讓我們返回了一個斐波那契數列,一個很好的follow up就是返回所有滿足題意的序列,就像例子5一樣,把兩種符合題意的組合都返回出來。其實改起來相當的容易,只需要將結果res換成一個二維數組來保存所有的情況,然后在遞歸函數中,首先判斷如果已經遍歷到了S的末尾,並且out數組中的個數大於等於3了,那么將out數組加入結果res即可,其余部分和上面的解法並沒有啥區別,代碼參見評論區一樓。
類似題目:
參考資料:
https://leetcode.com/problems/split-array-into-fibonacci-sequence/