306. 累加数

中等 · 回溯

题目

306. 累加数 官方题解

方法一:回溯

一开始想过爆搜或者 dp,但爆搜复杂度太高,dp 也不好设计状态。这道题的递归性很强,本质是回溯:先枚举前两个数的切分点,之后每一步都用前两个数之和去匹配下一段子串,一旦匹配失败就回退,换一种切法。

回溯过程中要注意两点:

  • 不能有前导零,除非这个数本身就是 0。
  • 每一步都用前两个数之和去匹配下一段子串,匹配不上就直接返回 false,交给上一层去尝试别的切法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public:
    int getNum(string& num, int i, int j) {
        string t = num.substr(i, j - i);
        return stoi(t);
    }

    bool isAddNum(string& num, int i, int j, int k) {
        // 算第一个数和第二个数
        int a, b;
        a = getNum(num, i, j);
        b = getNum(num, j, k);
        int c = a + b;
        // 相加后,依次与第三个数比对,如果对就往下找,不对,直接返回 false
        int len = to_string(c).size();
        if (k + len > num.size()) return false;
        string c_s = num.substr(k, len);
        if (stoi(c_s) == c) return isAddNum(num, j, k, k + len);
        return false;
    }

    bool isAdditiveNumber(string num) {
        int n = num.size();
        // 枚举第二个数字
        for (int i = 1; i <= n - 2; i++) {
            // 判断第一个数字是否有前导零
            if (num[0] == '0' && i > 1) return false;

            // j:第三个数字
            for (int j = i + 1; j <= n - 1; j++) {
                // 判断第二个数字是否有前导零
                if (num[i] == '0' && j - i > 1) break;

                if (isAddNum(num, 0, i, j))
                    return true;
            }
        }

        return false;
    }
};

这里有几个错误:

  1. stoi 可能会溢出
  2. 漏加终止条件

这里为了简单,直接用 stoi 计算两数之和,没有处理大数溢出——题目里数字最长可以到 35 位,超出 int 甚至 long long 的表示范围时会出问题。实际提交前可以换成字符串加法来保证正确性。

时间复杂度:O(n³),枚举前两个数的切分点各需要 O(n),每次验证链条最长也是 O(n)。

空间复杂度:O(n),递归深度和子串拷贝都跟数字长度成正比。

有个问题就是,stoi 可能会溢出,题目里数字最长可以到 35 位,超出 int 甚至 long long 的表示范围时会出问题。实际提交前可以换成字符串加法来保证正确性。

官方题解给出的代码是用字符串加法来处理大数的,下面是官方题解的代码,valid 用 while 循环:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Solution {
public:
    bool isAdditiveNumber(string num) {
        int n = num.size();
        for (int secondStart = 1; secondStart < n - 1; ++secondStart) {
            if (num[0] == '0' && secondStart != 1) {
                break;
            }
            for (int secondEnd = secondStart; secondEnd < n - 1; ++secondEnd) {
                if (num[secondStart] == '0' && secondStart != secondEnd) {
                    break;
                }
                if (valid(secondStart, secondEnd, num)) {
                    return true;
                }
            }
        }
        return false;
    }

    bool valid(int secondStart, int secondEnd, string num) {
        int n = num.size();
        int firstStart = 0, firstEnd = secondStart - 1;
        while (secondEnd <= n - 1) {
            string third = stringAdd(num, firstStart, firstEnd, secondStart, secondEnd);
            int thirdStart = secondEnd + 1;
            int thirdEnd = secondEnd + third.size();
            if (thirdEnd >= n || !(num.substr(thirdStart, thirdEnd - thirdStart + 1) == third)) {
                break;
            }
            if (thirdEnd == n - 1) {
                return true;
            }
            firstStart = secondStart;
            firstEnd = secondEnd;
            secondStart = thirdStart;
            secondEnd = thirdEnd;
        }
        return false;
    }

    string stringAdd(string s, int firstStart, int firstEnd, int secondStart, int secondEnd) {
        string third;
        int carry = 0, cur = 0;
        while (firstEnd >= firstStart || secondEnd >= secondStart || carry != 0) {
            cur = carry;
            if (firstEnd >= firstStart) {
                cur += s[firstEnd] - '0';
                --firstEnd;
            }
            if (secondEnd >= secondStart) {
                cur += s[secondEnd] - '0';
                --secondEnd;
            }
            carry = cur / 10;
            cur %= 10;
            third.push_back(cur + '0');
        }
        reverse(third.begin(), third.end());
        return third;
    }
};