一、题目描述
- 累加数是一个字符串,组成它的数字可以形成累加序列。
- 一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
- 给你一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是累加数。如果是,返回 t r u e true true;否则,返回 f a l s e false false。
- 说明: 累加序列里的数不会以0开头,所以不会出现1, 2, 03或者1, 02, 3的情况。
示例:
输入 | 输出 | 解释 |
---|---|---|
“112358” | t r u e true true | 累加序列为: 1, 1, 2, 3, 5, 8。1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8 |
“199100199” | t r u e true true | 累加序列为: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199 |
提示:
- 1 < = n u m . l e n g t h < = 35 1 <= num.length <= 35 1<=num.length<=35
- n u m num num 仅由数字(0 - 9)组成
进阶:
计划如何处理由过大的整数输入导致的溢出?
二、求解思路
方法一:穷举法 + 字符串加法
当累加序列的第一个数字和第二个数字以及总长度确定后,这个累加序列也就确定了。因此穷举累加序列第一个数字和第二个数字的所有可能性,并做合法判断即可。因此代码实现可分为两部分:穷举和合法性判断。
- 穷举:记第一个数字的最高位和最低位下标为 f i r s t S t a r t firstStart firstStart 和 f i r s t E n d firstEnd firstEnd,第二个数字的最高位和最低位下标为 s e c o n d S t a r t secondStart secondStart 和 s e c o n d E n d secondEnd secondEnd,易得 f i r s t E n d + 1 = s e c o n d S t a r t firstEnd+1=secondStart firstEnd+1=secondStart。采用两个循环即可遍历 s e c o n d S t a r t secondStart secondStart 和 s e c o n d E n d secondEnd secondEnd的所有可能性组合。
- 合法性判断:通过字符串加法算出两数之和,并与接下来紧邻的相同长度的字符串进行比较,若不相同则返回 f a l s e false false,相同则原本第二个数字作为新的第一个数字,第三个数字作为新的第二个数字,再次进行合法性判断。当算出的两数之和到达累加序列尾部时,返回 t r u e true true。
注意:当某个数字长度大于等于 2 时,这个数字不能以 0 开头,这部分的判断可以在两层循环体的开头完成。
Java代码
class Solution {
public boolean isAdditiveNumber(String num){
int n = num.length();
for(int secondStart = 1; secondStart < n - 1; secondStart++){
if (num.charAt(0) == '0' && secondStart != 1){
break;
}
for(int secondEnd = secondStart; secondEnd < n - 1; secondEnd++){
if(num.charAt(secondStart) == '0' && secondStart != secondEnd){
break;
}
if(isvalid(secondStart, secondEnd, num)){
return true;
}
}
}
return false;
}
public boolean isvalid(int secondStart, int secondEnd, String num){
int n = num.length();
int firstStart = 0, firstEnd = secondStart - 1;
while(secondEnd <= n - 1){
String third = string_Add(num, firstStart, firstEnd, secondStart, secondEnd);
int thirdStart = secondEnd + 1;
int thirdEnd = secondEnd + third.length();
if(thirdEnd >= n || !num.substring(thirdStart, thirdEnd + 1).equals(third)){
break;
}
if(thirdEnd == n - 1){
return true;
}
firstStart = secondStart;
firstEnd = secondEnd;
secondStart = thirdStart;
secondEnd = thirdEnd;
}
return false;
}
public String string_Add(String s, int firstStart, int firstEnd, int secondStart, int secondEnd){
StringBuffer third = new StringBuffer();
int carry = 0, cur = 0;
while(firstEnd >= firstStart || secondEnd >= secondStart || carry != 0){
cur = carry;
if(firstEnd >= firstStart){
cur += s.charAt(firstEnd) - '0';
firstEnd--;
}
if(secondEnd >= secondStart){
cur += s.charAt(secondEnd) - '0';
secondEnd--;
}
carry = cur / 10;
cur %= 10;
third.append((char)(cur + '0'));
}
third.reverse();
return third.toString();
}
}
复杂度分析
- 时间复杂度: O ( n 3 ) O(n^3) O(n3)。需要两层循环来遍历第二个数字的起始位置和结束位置,每个组合需要 O ( n ) O(n) O(n) 来验证合法性。
- 空间复杂度: O ( n ) O(n) O(n)。字符串加法需要 O ( n ) O(n) O(n) 的空间来保存结果。
方法二:深度优先搜索(DFS)+ 剪枝
- 深度优先搜索(DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当某节点的所在边都己被探寻过或者在搜寻时节点不满足条件,搜索将回溯到发现该节点的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。
Java代码
class Solution {
public boolean isAdditiveNumber(String num){
return dfs(num, 0, 0, 0, 0);
}
private boolean dfs(String num, int index, int count, long prevprev, long prev){
if(index >= num.length()){
return count > 2;
}
long current = 0;
for(int i = index; i < num.length(); i++){
char c = num.charAt(i);
if(num.charAt(index) == '0' && i > index){
// 剪枝1:不能做为前导0,但是它自己是可以单独做为0来使用的
return false;
}
current = current * 10 + c - '0';
if(count >= 2){
long sum = prevprev + prev;
if(current > sum){
// 剪枝2:如果当前数比之前两数的和大了,说明不合适
return false;
}
if(current < sum){
// 剪枝3:如果当前数比之前两数的和小了,说明还不够,可以继续添加新的字符进来
continue;
}
}
// 当前满足条件了,或者还不到两个数,向下一层探索
if(dfs(num, i + 1, count + 1, prev, current)){
return true;
}
}
return false;
}
}
复杂度分析
- 时间复杂度:由于方法为暴力DFS,牵涉到复杂剪枝,所以讨论时间复杂度无意义。
- 空间复杂度: O ( n ) O(n) O(n),每一层只需要常数个变量,最多下探 n n n 层,空间复杂度与递归深度一致。