leetcode306. 累加数
累加数 是一个字符串,组成它的数字可以形成累加序列。
一个有效的 累加序列 必须 至少 包含 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
提示:
1 <= num.length <= 35
num 仅由数字(0 - 9)组成
进阶:你计划如何处理由过大的整数输入导致的溢出?
回溯算法
给定的 nums的长度只有 35,且要求序列的第三个数开始由前两个数相加而来。
容易想到通过 DFS 爆搜每个数的分割点,同时利用累加数的特性(第三个数起,每个数均由为前两数之和)进行剪枝。
具体的,我们可以实现一个 boolean dfs(int u) 函数,入参为当前决策到 num 的哪一位,返回值为决策结果(序列)是否为累加数序列,爆搜过程中的分割数序列存放到 list 中。
由于是 从位置 index 作为开始位置决策如何分割出当前数 x,我们可以枚举当前数的结束位置,范围为 [u,n−1],但需要注意分割数不能包含前导零,即如果 num[u]=0,则当前数只能为 0。
同时,一个合法的分割数必然满足「其值大小为前两数之和」,因此当前数 x 能够被添加到 list 的充要条件为:
- list 长度不足 2,即 x 为序列中的前两数,不存在值大小的约束问题,x 可以被直接到 listlistlist 并继续爆搜;
- list 长度大于等于 2,即 x 需要满足「其值大小为前两数之和」要求,以此条件作为剪枝,满足要求的 x 才能追加到 list 中并继续爆搜。
最后,在整个 DFS 过程中我们需要监测「当前数」与「前两数之和」是否相等,而分割数长度最大为 35,存在溢出风险,我们需要实现「高精度加法」,实现一个 check 函数,用于检查 a + b 是否为 c,其中 a、b 和 c 均为使用「逆序」存储数值的数组(最高位对应个位,举个 🌰,a=35,则有 [5, 3])。
若爆搜过程能顺利结束(得到长度至少为 3的序列),则说明能够拆分出累加数序列,返回 True,否则返回 False。
?至此,我们解决了本题,并通过引入「高精度」来回答了「进阶」部分的问题。
代码演示
class Solution {
String _num;
int n;
List<List<Integer>> lists = new ArrayList<>();
public boolean isAdditiveNumber(String num) {
if (num.length() < 3){
return false;
}
_num = num;
n = num.length();
return dfs(0);
}
/**
* 回溯算法
* @param index
* @return
*/
boolean dfs(int index){
int m = lists.size();
if (index == n){
return m >= 3;
}
//如果当前数字是0,那么0 只能当作一个单独数字,不能和别的进行组合,
//所以max = index + 1;后续选择时,只能选到index
//不是0 的话,可以一直向后选,直到最后一个位置.
int max = _num.charAt(index) == '0' ? index + 1 : n;
ArrayList<Integer> cur = new ArrayList<>();
for (int i = index;i < max;i++){
//将数字逆序加进来,例如 199 -> 加进来 变为 991
//为了解决数字溢出的问题.
cur.add(0,_num.charAt(i) - '0');
//判断加进来的标准
if (m < 2 || check(lists.get(m - 2),lists.get(m - 1),cur)){
lists.add(cur);
if (dfs(i + 1)){
return true;
}
lists.remove(lists.size() - 1);
}
}
return false;
}
/**
* 判断当前数字是否是前两个数字之和
* @param a
* @param b
* @param c
* @return
*/
public boolean check(List<Integer> a,List<Integer>b,List<Integer>c){
ArrayList<Integer> ans = new ArrayList<>();
int t = 0;
for (int i = 0;i < a.size() || i < b.size();i++){
if (i < a.size()){
t += a.get(i);
}
if (i < b.size()){
t += b.get(i);
}
ans.add(t % 10);
t /= 10;
}
if (t > 0){
ans.add(t);
}
if (ans.size() != c.size()){
return false;
}
for (int i = 0 ; i < c.size();i++){
if (c.get(i) != ans.get(i)){
return false;
}
}
return true;
}
}