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)组成

解题思路

本题可以使用两种方法来实现:

  1. 回溯法:

    • 枚举前两个数的所有可能组合
    • 根据前两个数确定后续数字
    • 验证是否满足累加序列
    • 时间复杂度O(n^3)
  2. 迭代法:

    • 固定前两个数的长度
    • 迭代计算后续数字
    • 验证整个序列
    • 时间复杂度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 性能最优,内存占用小

代码亮点

  1. 🎯 使用字符串加法处理大数
  2. 💡 优化前导零的处理
  3. 🔍 高效的回溯剪枝
  4. 🎨 代码结构清晰,易于维护

常见错误分析

  1. 🚫 未处理前导零
  2. 🚫 大数相加溢出
  3. 🚫 边界条件判断错误
  4. 🚫 回溯条件设置不当

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
回溯法 O(n^3) O(n) 实现简单 效率较低
迭代法 O(n^2) O(n) 效率较高 实现复杂

相关题目

📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第306题。

💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!