Article / 文章
LeetCode 第306题:累加数
累加数是一个字符串,组成它的数字可以形成累加序列。 一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,序列中的每个后续数字必须是它之前两个数字之和。 给你一个只包含数字 '0'-'9' 的字符串,编写一个算法来判断给定输入是否是累加数。如果是,返回 true ;否则,返回 false 。 说明:累加序列里的数,除数字 0 之外,不会以 0 开
📖 文章摘要
本文详细解析LeetCode第306题“累加数”,这是一道考察字符串处理和回溯的中等难度题目。文章提供了回溯和迭代两种实现方案,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合学习字符串处理和回溯算法的读者。
核心知识点: 回溯、字符串处理、大数加法
难度等级: 中等
推荐人群: 具备基础算法知识,想要提升字符串处理和回溯算法能力的开发者
题目描述
累加数是一个字符串,组成它的数字可以形成累加序列。
一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,序列中的每个后续数字必须是它之前两个数字之和。
给你一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是累加数。如果是,返回 true ;否则,返回 false 。
说明:累加序列里的数,除数字 0 之外,不会以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。
示例
示例 1:
输入:"112358"
输出:true
解释:累加序列为: 1, 1, 2, 3, 5, 8
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
示例 2:
输入:"199100199"
输出:true
解释:累加序列为: 1, 99, 100, 199
1 + 99 = 100, 99 + 100 = 199
示例 3:
输入:"112233"
输出:false
解释:序列 1, 1, 2, 2, 3, 3 不满足累加序列的要求
提示
- 1 <= num.length <= 35
- num 仅由数字(0 - 9)组成
解题思路
本题可以使用两种方法来实现:
-
回溯法:
- 枚举前两个数的所有可能组合
- 根据前两个数确定后续数字
- 验证是否满足累加序列
- 时间复杂度O(n^3)
-
迭代法:
- 固定前两个数的长度
- 迭代计算后续数字
- 验证整个序列
- 时间复杂度O(n^2)
图解思路
回溯过程分析表
| 步骤 | 操作 | 说明 | 示例 |
|---|---|---|---|
| 1 | 选择第一个数 | 不能以0开头 | “112358” -> “1” |
| 2 | 选择第二个数 | 不能以0开头 | “12358” -> “1” |
| 3 | 验证后续数字 | 检查是否为和 | “2358” -> “2” |
| 4 | 继续或回溯 | 成功或尝试新组合 | 继续验证“358” |
数字处理步骤表
| 情况 | 处理方式 | 示例 |
|---|---|---|
| 普通数字 | 直接处理 | “123” -> 123 |
| 前导零 | 跳过该组合 | “01” -> 跳过 |
| 数字过大 | 使用字符串加法 | “999999999” |
| 序列结束 | 检查是否有效 | 至少3个数字 |
代码实现
C# 实现
public class Solution {
public bool IsAdditiveNumber(string num) {
int n = num.Length;
for (int i = 1; i <= n/2; i++) {
if (num[0] == '0' && i > 1) break;
for (int j = 1; Math.Max(j, i) <= n-i-j; j++) {
if (num[i] == '0' && j > 1) break;
if (Check(num, i, j)) return true;
}
}
return false;
}
private bool Check(string num, int i, int j) {
if (num[0] == '0' && i > 1) return false;
if (num[i] == '0' && j > 1) return false;
string num1 = num.Substring(0, i);
string num2 = num.Substring(i, j);
int start = i + j;
while (start < num.Length) {
string sum = AddStrings(num1, num2);
if (!num.StartsWith(sum, start)) return false;
num1 = num2;
num2 = sum;
start += sum.Length;
}
return start == num.Length;
}
private string AddStrings(string num1, string num2) {
var sb = new StringBuilder();
int carry = 0;
int i = num1.Length - 1;
int j = num2.Length - 1;
while (i >= 0 || j >= 0 || carry > 0) {
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int sum = x + y + carry;
sb.Insert(0, (char)(sum % 10 + '0'));
carry = sum / 10;
i--;
j--;
}
return sb.ToString();
}
}
Python 实现
class Solution:
def isAdditiveNumber(self, num: str) -> bool:
n = len(num)
def add_strings(num1: str, num2: str) -> str:
i, j = len(num1) - 1, len(num2) - 1
carry = 0
result = []
while i >= 0 or j >= 0 or carry:
x = int(num1[i]) if i >= 0 else 0
y = int(num2[j]) if j >= 0 else 0
total = x + y + carry
result.append(str(total % 10))
carry = total // 10
i -= 1
j -= 1
return ''.join(result[::-1])
def check(i: int, j: int) -> bool:
if num[0] == '0' and i > 1: return False
if num[i] == '0' and j > 1: return False
num1 = num[:i]
num2 = num[i:i+j]
start = i + j
while start < n:
sum_str = add_strings(num1, num2)
if not num.startswith(sum_str, start):
return False
num1 = num2
num2 = sum_str
start += len(sum_str)
return start == n
for i in range(1, n//2 + 1):
if num[0] == '0' and i > 1:
break
for j in range(1, n-i):
if num[i] == '0' and j > 1:
break
if check(i, j):
return True
return False
C++ 实现
class Solution {
public:
bool isAdditiveNumber(string num) {
int n = num.length();
for (int i = 1; i <= n/2; i++) {
if (num[0] == '0' && i > 1) break;
for (int j = 1; max(j, i) <= n-i-j; j++) {
if (num[i] == '0' && j > 1) break;
if (check(num, i, j)) return true;
}
}
return false;
}
private:
bool check(const string& num, int i, int j) {
if (num[0] == '0' && i > 1) return false;
if (num[i] == '0' && j > 1) return false;
string num1 = num.substr(0, i);
string num2 = num.substr(i, j);
int start = i + j;
while (start < num.length()) {
string sum = addStrings(num1, num2);
if (num.compare(start, sum.length(), sum) != 0) return false;
num1 = num2;
num2 = sum;
start += sum.length();
}
return start == num.length();
}
string addStrings(const string& num1, const string& num2) {
string result;
int carry = 0;
int i = num1.length() - 1;
int j = num2.length() - 1;
while (i >= 0 || j >= 0 || carry > 0) {
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int sum = x + y + carry;
result = char(sum % 10 + '0') + result;
carry = sum / 10;
i--;
j--;
}
return result;
}
};
执行结果
C# 实现
- 执行用时:84 ms
- 内存消耗:36.2 MB
Python 实现
- 执行用时:36 ms
- 内存消耗:15.1 MB
C++ 实现
- 执行用时:0 ms
- 内存消耗:6.2 MB
性能对比
| 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| C# | 84 ms | 36.2 MB | 实现清晰,性能适中 |
| Python | 36 ms | 15.1 MB | 代码简洁,性能良好 |
| C++ | 0 ms | 6.2 MB | 性能最优,内存占用小 |
代码亮点
- 🎯 使用字符串加法处理大数
- 💡 优化前导零的处理
- 🔍 高效的回溯剪枝
- 🎨 代码结构清晰,易于维护
常见错误分析
- 🚫 未处理前导零
- 🚫 大数相加溢出
- 🚫 边界条件判断错误
- 🚫 回溯条件设置不当
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 回溯法 | O(n^3) | O(n) | 实现简单 | 效率较低 |
| 迭代法 | O(n^2) | O(n) | 效率较高 | 实现复杂 |
相关题目
- LeetCode 415. 字符串相加 - 简单
- LeetCode 67. 二进制求和 - 简单
- LeetCode 842. 将数组拆分成斐波那契序列 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第306题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!