将数组拆分成斐波那契序列

标签: 字符串 回溯

难度: Medium

给定一个数字字符串 num,比如 "123456579",我们可以将它分成「斐波那契式」的序列 [123, 456, 579]

形式上,斐波那契式 序列是一个非负整数列表 f,且满足:

  • 0 <= f[i] < 231 ,(也就是说,每个整数都符合 32 位 有符号整数类型)
  • f.length >= 3
  • 对于所有的0 <= i < f.length - 2,都有 f[i] + f[i + 1] = f[i + 2]

另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。

返回从 num 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []

示例 1:

输入:num = "1101111"
输出:[11,0,11,11]
解释:输出[110,1,111]也可以。

示例 2:

输入: num = "112358130"
输出: []
解释: 无法拆分。

示例 3:

输入:"0123"
输出:[]
解释:每个块的数字不能以零开头,因此 "01","2","3" 不是有效答案。

提示:

  • 1 <= num.length <= 200
  • num 中只含有数字

Submission

运行时间: 28 ms

内存: 16.1 MB

class Solution:
    def splitIntoFibonacci(self, num: str) -> List[int]:
        def dfs(i):
            if i == n:
                return len(ans) > 2
            x = 0
            for j in range(i, n):
                if j > i and num[i] == '0':
                    break
                x = x * 10 + int(num[j])
                if x > 2**31 - 1 or (len(ans) > 2 and x > ans[-2] + ans[-1]):
                    break
                if len(ans) < 2 or ans[-2] + ans[-1] == x:
                    ans.append(x)
                    if dfs(j + 1):
                        return True
                    ans.pop()
            return False

        n = len(num)
        ans = []
        dfs(0)
        return ans

Explain

题解通过使用回溯法来尝试构建斐波那契序列。首先定义一个辅助函数 dfs(i),其中 i 表示当前正在处理的数字的起始位置。如果 i 等于字符串的长度且答案列表中至少有三个数,说明已经成功构建了一个序列,函数返回 True。然后逐个检查可能的数字,如果遇到以 '0' 开头的数字(并且长度超过1),则跳出循环。对每一个可能的数字,检查是否符合斐波那契序列的要求:1) 数字不能超过 2^31 - 1;2) 如果列表中已有至少两个数,该数必须等于最后两个数的和。如果当前数字满足要求,将其加入到序列中并递归调用 dfs(j+1)。如果递归调用返回 True,表示找到了合法的序列,当前函数也返回 True。如果当前路径不行,则将最后一个数字从序列中移除(回溯),尝试下一个可能的数字。如果所有可能都尝试完毕,返回 False。

时间复杂度: O(2^n)

空间复杂度: O(n)

class Solution:
    def splitIntoFibonacci(self, num: str) -> List[int]:
        def dfs(i):
            if i == n:
                return len(ans) > 2  # 当处理完整个字符串且答案序列长度大于2时,返回True
            x = 0
            for j in range(i, n):
                if j > i and num[i] == '0':
                    break  # 跳过以0为开头的数字(除非它自身是0)
                x = x * 10 + int(num[j])  # 将当前字符转为数字并添加到当前数 x
                if x > 2**31 - 1 or (len(ans) > 2 and x > ans[-2] + ans[-1]):
                    break  # 如果 x 太大或不符合斐波那契规则,则终止当前循环
                if len(ans) < 2 or ans[-2] + ans[-1] == x:
                    ans.append(x)  # 将 x 添加到当前答案序列中
                    if dfs(j + 1):
                        return True  # 递归地尝试下一位置,如果成功,则直接返回True
                    ans.pop()  # 如果不成功,回溯,移除 x
            return False  # 如果所有尝试都不成功,返回False

        n = len(num)
        ans = []  # 初始化答案列表
        dfs(0)
        return ans  # 返回最终答案,可能为空列表

Explore

回溯法适用于解决决策树的问题,尤其是在不确定符合条件的斐波那契数列数目和组合情况时。这个问题的解空间不固定且需要尝试多种组合来构建合法的斐波那契序列。动态规划更适合在有明确子问题重叠和最优子结构的问题上使用,而在斐波那契序列的构建中,每个决策点的选择直接影响后续路径,且没有重叠子问题,因此使用回溯法更为合适。

在许多数值系统中,前导零是无意义的,并且可能导致解析错误或不一致。然而,数字'0'本身是有效的并且是斐波那契序列的一个合法数字。因此,在算法中允许单独的'0'作为序列中的一个元素,但如果一个数字以'0'开头且长度超过1,如'01'或'001',则这种表示不被接受,因为它不符合数字的标准表示方式。

虽然Python的整数类型是动态的,可以处理非常大的数,但在实际问题中常常需要考虑实际应用的环境。例如,在LeetCode中,通常假设整数范围是有限的,符合常见的32位整数范围。检查`x > 2**31 - 1`是为了确保解决方案在所有编程环境中都是有效的,特别是在那些整数大小有固定上限的编程语言中,这可以防止整数溢出错误。此外,这也符合斐波那契序列在实际应用中的一些限制,避免生成过大的数字。

在Python中,由于整数类型是动态的,理论上加法操作不会导致传统意义上的整数溢出。Python会自动处理大于32位的整数,因此不会出现溢出错误。但是,在其他一些静态类型语言中,如C++或Java,这种操作确实可能导致溢出。因此,这种情况下在Python中使用是安全的,但如果在其他语言实现相同逻辑时,需要额外的检查来确保不会溢出。