题目描述
累加数是一个字符串,组成它的数字可以形成累加序列。
一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
给定一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是累加数。
说明: 累加序列里的数不会以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。
样例描述
思路
方法一:字符串模拟 + 高精度加法 O(n^3)
- 枚举第一段的长度,枚举第二段的长度,看和是否匹配第三段的长度。枚举中不能枚举前导0的数。匹配的话不断的移动a,b,c。
- java的substring是表示首尾索引,并且左闭右开。
- 初始让a=-1且每段都初始指向该段前一个数,是类似虚拟头结点的思想,为了让处理第一个数和后面保持一致。
- 高精度加法模板要背过。(记得先转化为stringbuilder逆序,也可以直接从后往前遍历)
方法二:回溯 + 剪枝
- 对字符串进行拆分,遍历所有可能。从第三个数字开始,判断是否满足等于前两个数的和,满足的话就拆分,再继续找下一个数。否则的话继续拼接构成新的数字。防止溢出,拆分的数用long。
- 如果第三个数字的和大于前两个数字和,就没必要继续下去,直接剪枝,因为往后只能越来越大。
- 拆分过程中要保证不能出现前导0,除了0本身以外。如果不是第一位就不能为0,否则直接剪枝。
- 回溯中记录前两个数和,上一个数的值,当前下标以及已经生成的数的个数。
代码
class Solution {
public boolean isAdditiveNumber(String num) {
int n = num.length();
for (int i = 0; i < n; i ++ ) {
//至少三个数,所以让j的一个数也在范围内
for (int j = i + 1; j + 1 < n; j ++ ) {
//指向每一段的前一个数(也就是上一段的最后一个数)
int a = -1, b = i, c = j;
//由于不知道判断多少段和,所以设置不断循环直到末尾或者匹配失败
while (true) {
String s1 = num.substring(a + 1, b + 1);
String s2 = num.substring(b + 1, c + 1);
//高精度加法不能有前导0,有的话该段失败,直接break
//至少2个数,并且前一个是0
if (s1.length() > 1 && s1.charAt(0) == '0') {
break;
}
if (s2.length() > 1 && s2.charAt(0) == '0') {
break;
}
//将前两段加起来
String s3 = add(s1, s2);
//只有加起来的结果长度没有越界,并且与后面第三段匹配时,才更新每段的索引
if (c + s3.length() < n && num.substring(c + 1, c + s3.length() + 1).equals(s3)) {
a = b;
b = c;
c = c + s3.length();
//如果第三段的前一个,也就是上一段的结尾已经到达了总的末尾 说明成功
if (c == n - 1) {
return true;
}
}
else break;
}
}
}
//如果循环完没有成功,就失败了
return false;
}
//高精度加法模板
public String add(String a, String b) {
//先逆序,保证先处理个位
a = new StringBuilder(a).reverse().toString();
b = new StringBuilder(b).reverse().toString();
String res = "";
//只要有一个字符串没处理完(没越界),或者还有进位就不断循环处理
for (int i = 0, c = 0; i < a.length() || i < b.length() || c > 0; i ++ ) {
//还没处
if (i < a.length()) {
c += a.charAt(i) - '0';
}
if (i < b.length()) {
c += b.charAt(i) - '0';
}
//取模求该位的数位
res += c % 10;
c /= 10;
}
//最后要逆序
return new StringBuilder(res).reverse().toString();
}
}
方法二:回溯 + 剪枝
class Solution {
String num;
int n;
public boolean isAdditiveNumber(String num) {
this.num = num;
this.n = num.length();
return dfs(0, 0, 0, 0);
}
public boolean dfs(int index, long presum, long previous, int cnt) {
//已经到达末尾
if (index == n) {
//如果生成的数大于等于3个
return cnt >= 3;
}
//当前数
long val = 0;
for (int i = index; i < n; i ++ ) {
//如果有前导0,直接break
if (i > index && num.charAt(index) == '0') {
break;
}
val = val * 10 + (num.charAt(i) - '0');
//如果已经生了两个数
if (cnt >= 2) {
//如果当前数(第三个数)小于前面两个数的和,就往后继续拼接,否则直接break
if (val < presum) {
continue;
}
//比前面大,就break
else if (val > presum) {
break;
}
}
//使用这个数向下面递归 都是true的话就返回
if (dfs(i + 1, previous + val, val, cnt + 1)) {
return true;
}
}
//当前分支失败
return false;
}
}