斐波那契的拆分—回溯算法经典例题
给定一个数字字符串 s,将它拆分成一个长度大于 3 的斐波那契式的列表。也就是说,除第一个和第二个数字,列表中的每个数字都等于前两个数字的和。
例如:
s="1011112",输出:[10,1,11,12]。
s = "12345168",输出:[123,45,168]。
s = "1234",输出:[]
解题思路如下:
1) 我们先假设第一个数字是 s 字符串中的第一个数字,比如 "12345168910" 中的 1。然后再假设第二个数字是 s 字符串中的第二个数字,比如 "12345168910" 中的 2。
2) 接下来检查这个假设是否成立。假设不成立,因为 1+2=3,但 2+3≠4。所以我们退回到第二个数字,假设第二个数字是 s 字符串中的第二个数字和第三个数字,比如"12345168910"中的23,然后再次检查这个假设是否成立。通过判断,这个假设仍然不成立。
3) 事实上,尝试过后,我们发现第二个数字为 2,23,234,2345,23451,234516,2345168,23451689, 234516891,2345168910 这些假设都不成立。这时我们只好退回到第一个数字,使它变为字符串中的第一个和第二个数字,比如 "12345168910"中的 12。而第二个数字则是 s 字符串的下一个的数字—— 3。我们重复这个步骤直到找到成立的两个数字,或者将所有可能都尝试完得出没有解的结论。
4) 在以上过程中,假设第三个数字成立了,也就是说目前找到了一个数字是前两个数字的和。接下来,我们要检查第四个数字是否成立。我们重复这个步骤,一旦数字不成立,就退回到第二个数字,改变它,然后再继续。
比如,我们找到了 168=123+45。现在,我们需要验证第四个数字。因为后三位数字不是我们想要的 291,所以我们退回到第二个数字,改变它。尝试完所有的第二个数字的可能并失败后,我们会返回到第一个数字,改变它,然后再继续。
以上就是解题思路,但是我们还可以通过剪枝来提高解题的效率。
本题有两处可以提升效率的部分。
1) 在判断两个数字的和是否在剩余的字符串中时,如果两个数字的和小于当前数字,就可以直接得到否定的结论。比如当 s="42834",第一个数字是 4,第二个数字是 2 时,我们检查 4+2 是否等于 8。4+2 不但不等于 8,还小于 8,这意味着我们不需要再检查 4+2 是否等于 83。但是,如果 s="93129",第一个数字是 9,第二个数字是 3,那么我们除检查 9+3 是否等于 1,还需要检查 9+3 是否等于 12。
2) 像“01”“004”这样的数字不成立,可以直接跳过它们。
完整代码如下:
#将s拆分成一个长度大于3的斐波那契式的列表
def splitIntoFibonacci(s):
#res是当前结果列表,index是当前数字的开始坐标
#以res[-1], res[-2]为前两位数字,检查第三位数字是否成立
def helper(s,res, index):
if index ==n and len(res)>=3: #如果所有数字都被遍历并且res有3个以上数字
return True
for i in range(index,n):
if s[index] == "0" and i>index: #不成立的数字
break
num = int(s[index:i+1]) #动态变化的当前数字
#第三个数字不成立
if len(res)>=2 and num > res[-1] + res[-2]:
break
#第三个数字暂时不存在或第三个数字成立
if len(res)<=1 or num == res [-1] + res[-2]:
res.append(num)
if helper(s, res,i+1): #检查第四个数字
return True
res.pop()
return False
res = []
n = len(s)
helper(s,res,0)
return res