隐藏的斐波那契?教你用 Swift 看穿数字里的加法魔法

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
    • 题解答案(Swift 实现)
    • 题解代码分析
      • 为什么要回溯?
      • 如何解决“数字太大超出范围”问题?
      • 前导零处理为什么这么写?
    • 示例测试及结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

你有没有遇到过这样的数字字符串:它里面的数字其实隐藏着一种规律——每个数字都是前两个数的和?比如 "112358""199100199"。这样的序列被称为累加数(Additive Number)。今天,我们就用 Swift 实现一个算法:给你一个数字字符串,判断它是不是一个累加数。它背后考的是字符串切分、回溯和大整数加法的组合,动手一次,算法思路和编码技巧全搞定。

描述

题目定义累加数如下:

  1. 最少包含 3 个数字。
  2. 除了前两个数字,后面每一个数字都等于前两个的和。
  3. 数字不能有前导零(除非数字本身就是 0)。

给一个数字字符串,如 "112358",判断它是否是累加数。如果是,返回 true,否则返回 false

示例:

"112358"       -> true(1,1,2,3,5,8)
"199100199"    -> true(1,99,100,199)
"1023"         -> false,不满足 1+0=2, 0+2≠3

字符串长度最多 35,因此最多要切割和判断的情况还在可控范围内。我们可以用 回溯大整数相加 来做。

题解答案(Swift 实现)

我们用回溯枚举前两个数的切分点,然后后续切出来的每段必须等于前两个的和,满足继续,否则剪枝。

import Foundation

func isAdditiveNumber(_ num: String) -> Bool {
    let chars = Array(num)
    let n = chars.count

    func addStrings(_ a: String, _ b: String) -> String {
        var i = a.count - 1, j = b.count - 1, carry = 0
        var sumDigits = [Character]()

        let ac = Array(a), bc = Array(b)
        while i >= 0 || j >= 0 || carry != 0 {
            let x = i >= 0 ? Int(String(ac[i]))! : 0
            let y = j >= 0 ? Int(String(bc[j]))! : 0
            let s = x + y + carry
            sumDigits.append(Character(String(s % 10)))
            carry = s / 10
            i -= 1; j -= 1
        }
        return String(sumDigits.reversed())
    }

    func backtrack(_ start: Int, _ path: [String]) -> Bool {
        if start == n {
            return path.count >= 3
        }

        for len in 1...n - start {
            if len > 1 && chars[start] == "0" {
                break
            }
            let curr = String(chars[start..<start+len])
            let m = path.count
            if m >= 2 {
                let sum = addStrings(path[m - 2], path[m - 1])
                if sum != curr {
                    continue
                }
            }
            if backtrack(start + len, path + [curr]) {
                return true
            }
        }
        return false
    }

    return backtrack(0, [])
}

这个 Demo 可以直接复制到 Swift Playground 里运行,结构清晰、用途明确。

题解代码分析

为什么要回溯?

我们需要尝试各种可能的前两位切分组合,而且长度未知,用回溯可以遍历所有可能性。在深度方向,每确定前两位,接下来看后面是否满足“每个都是前两个之和”,不满足就退回。

如何解决“数字太大超出范围”问题?

直接用 Int 容易溢出。这里我们自己写了 addStrings(a,b) 方法,用字符串按位相加,模拟高精度加法。它支持任意长度数字拼接,而且性能在线性可控。

前导零处理为什么这么写?

题目规定数字不能有前导零,除非数字就是 “0”。因此遇到 chars[start] == "0" 且长度超 1 时,我们直接 break 掉这一分支,剪掉无效搜索;这样既能减少组合,也符合题意。

示例测试及结果

let tests = [
    "112358",
    "199100199",
    "1023",
    "000",
    "101",
    "1203"
]

for s in tests {
    print(s, "->", isAdditiveNumber(s))
}

输出:

112358 -> true
199100199 -> true
1023 -> false
000 -> true   // 0,0,0 是合法序列
101 -> true   // 1,0,1
1203 -> false

可以看到,测试结果符合预期,各种边界情况都覆盖到。

时间复杂度

最多遍历前两个数字的分割:O(n²) 次,每次后续最多遍历 n 切分。每次回溯路径中拼接大数需要 O(n) 做加法,因此最坏时间复杂度约为:

O(n³ * m)

其中 n 是字符串长度,m 是加法长度(最多 n)。给定 n ≤ 35,时间在题目限制下是可以接受的。

空间复杂度

回溯路径最多保存 O(n) 级别的字符串,单个字符串存储也为 O(n),所以总空间是 O(n²)。加上递归调用栈的空间 O(n),整体空间开销也在可控范围。

总结

  • 字符串切分 + 回溯 适用于枚举前置组合的场景。
  • 大整数模拟加法 避免溢出限制,适合处理任意长度数字。
  • 剪枝机制(前导零、长度匹配) 显著减少无效组合,提高效率。

你可能感兴趣的:(Swift,swift,开发语言,ios)