1、读题
读完题的第一想法,这不是斐波那契数列吗?
然后再想了想,简直天差地别。
2、审题
你也许已经发现了此题最重要的问题,但我们先避之不谈,来看看题目告诉了我们哪些重点:
- 首先,累加数必须至少包含3个数。
- 其次,输入的字符串仅包含字符 ‘0’ - ‘9’,因而我们不用担心异常数据。
- 累加数不能以0打头,但是0可以作为单独一个数存在。
- 可能存在超出数字范围的大数
这么看来,此题是很简单的问题。前提是,你解决了最重要的困难。
3、思路
直接来说重点吧。
稍微看了下case,你就会发现此题面临的最大的一个问题——我们如何去分词?
我们该怎么从一串数字的字符中,去分出头两个数字,并用它们去组成后续?
题目仅输入了一串字符串,没有任何可以用于去拆分数字的分隔,这意味着我们需要自行去尝试拆分数字。
想到这,递归和回溯成为了重点之一。
然后呢?怎么处理大数的存储问题?
题目给出的字符串长度限界为1 <= num.length <= 35
,根据题目限制的至少3个数字组成,所以长度最长的数字理论上可以达到17位。当然力扣准备的案例具体有多长我就不得而知了
这个长度超过了java中int
型的范围,但仍在long
型的范围之内。
但是我一劳永逸地选择了使用字符串来存储。这样的话,即使数字达到long
型以外的天文数字,我的解法也仍然可以支持。
来看看实现吧
4、开工!
class Solution {
public boolean isAdditiveNumber(String num) {
String num1, num2;
int f = 1;
while (f <= num.length() / 2) {
int s = f + 1;
num1 = getNumber(num, 0, f);
//第一个数返回null时,说明头部为0,无法组成累加数
if (num1 == null) {
return false;
}
while (s - f <= num.length() / 2) {
num2 = getNumber(num, f, s);
//第二个数返回null时,说明此时的开头无法组成有效数字,跳过此次循环
if (num2 == null) {
break;
}
//判断以当前两个数能否组成累加数
if (verify(num, s, num1, num2)) {
return true;
}
s++;
}
f++;
}
return false;
}
private String getNumber(String num, int s, int e) {
String n = num.substring(s, e);
return n.length() > 1 && n.startsWith("0") ? null : n;
}
private boolean verify(String num, int next, String num1, String num2) {
String sum = sum(num1, num2);
//剩余长度不足以达到sum的长度时
if (sum.length() > num.length() - next) {
return false;
}
//截取下一段相同长度的字符
String sequence = num.substring(next, next + sum.length());
if (!sequence.equals(sum)) {
return false;
}
next += sum.length();
if (next == num.length()) {
return true;
}
//校验下一段
return verify(num, next, num2, sum);
}
private String sum(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int i = num1.length() - 1;
int j = num2.length() - 1;
int next = 0;
while (i >= 0 || j >= 0 || next != 0) {
int n = i >= 0 ? num1.charAt(i) - '0' : 0;
n += j >= 0 ? num2.charAt(j) - '0' : 0;
//加上进制
n += next;
//更新进制和当前数字
next = n / 10;
n %= 10;
//记录当前位字母
sb.append(n);
i--;
j--;
}
return sb.reverse().toString();
}
}
5、解读
除了主函数外,我另外实现了3个函数,我们先从简单的开始看。
首先是getNumber()
方法,它主要用于从原字符串上截取部分片段下来。同时,做了防止数字以0
打头的防范。
private String getNumber(String num, int s, int e) {
String n = num.substring(s, e);
return n.length() > 1 && n.startsWith("0") ? null : n;
}
然后是sum()
方法,其作用如其名,用来做字符串加法的。
此实现可以直接套用在一些需要做大数加法的地方。
代码实现很简单,大家可以结合注释自行理解。
private String sum(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int i = num1.length() - 1;
int j = num2.length() - 1;
int next = 0;
while (i >= 0 || j >= 0 || next != 0) {
int n = i >= 0 ? num1.charAt(i) - '0' : 0;
n += j >= 0 ? num2.charAt(j) - '0' : 0;
//加上进制
n += next;
//更新进制和当前数字
next = n / 10;
n %= 10;
//记录当前位字母
sb.append(n);
i--;
j--;
}
return sb.reverse().toString();
}
verify()
方法承载了递归的主要实现逻辑。
我们首先将传入的前两个数相加,获得他们的和的字符串。
再截取原字符串上后续相同长度的字符,比较是否一致。
如果不一致,说明当前的两个数无法实现累加数序列,则返回false。
如果一致,则以后一个字符串和相加得到的字符串,作为下一递归的入参。
直到判断不一致或原字符串遍历完成。
private boolean verify(String num, int next, String num1, String num2) {
String sum = sum(num1, num2);
//剩余长度不足以达到sum的长度时
if (sum.length() > num.length() - next) {
return false;
}
//截取下一段相同长度的字符
String sequence = num.substring(next, next + sum.length());
if (!sequence.equals(sum)) {
return false;
}
next += sum.length();
if (next == num.length()) {
return true;
}
//校验下一段
return verify(num, next, num2, sum);
}
当然,不要忘记修改下一个字符串截取时的起点位置。
之后就是主函数了,承载了回溯的实现。
其主要作用是从原字符串中选取出头两个子串,以它们为起点开始递归判断。
public boolean isAdditiveNumber(String num) {
String num1, num2;
int f = 1;
while (f <= num.length() / 2) {
int s = f + 1;
num1 = getNumber(num, 0, f);
//第一个数返回null时,说明头部为0,无法组成累加数
if (num1 == null) {
return false;
}
while (s - f <= num.length() / 2) {
num2 = getNumber(num, f, s);
//第二个数返回null时,说明此时的开头无法组成有效数字,跳过此次循环
if (num2 == null) {
break;
}
//判断以当前两个数能否组成累加数
if (verify(num, s, num1, num2)) {
return true;
}
s++;
}
f++;
}
return false;
}
由于题目规定了累加数序列至少得有3个数,所以可想而知,单个数字的长度最多不能超过字符串长度的一半。
所以主函数中,两重循环就以此作为判断条件,也算是剪枝的一环了。
6、提交
感觉自己写了一大堆,还有多重循环和字符串操作,但没想到耗时居然这么短。
7、大佬安利时间
看了不少大牛,实现思路基本一致。
不过这里放出这一位大牛的题解。其回溯思路实现得及其简洁,值得学习。
8、总结
星期一!你是带着怎样的心情回到职场的呢?
总之社畜是不怎么开心就是了。毕竟只想躺着拿钱
总之年关将至,你的业务是已经上线、手头放空?还是仍在赶进度中呢?
但总之,今天也是腊八节,有习惯的记得喝腊八粥哦。