你有没有遇到过这样的数字字符串:它里面的数字其实隐藏着一种规律——每个数字都是前两个数的和?比如 "112358"
、"199100199"
。这样的序列被称为累加数(Additive Number)。今天,我们就用 Swift 实现一个算法:给你一个数字字符串,判断它是不是一个累加数。它背后考的是字符串切分、回溯和大整数加法的组合,动手一次,算法思路和编码技巧全搞定。
题目定义累加数如下:
给一个数字字符串,如 "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,因此最多要切割和判断的情况还在可控范围内。我们可以用 回溯 加 大整数相加 来做。
我们用回溯枚举前两个数的切分点,然后后续切出来的每段必须等于前两个的和,满足继续,否则剪枝。
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),整体空间开销也在可控范围。