【力扣时间】【306】【中等】累加数

1、读题

这儿

读完题的第一想法,这不是斐波那契数列吗?
然后再想了想,简直天差地别。

2、审题

你也许已经发现了此题最重要的问题,但我们先避之不谈,来看看题目告诉了我们哪些重点:

  1. 首先,累加数必须至少包含3个数。
  2. 其次,输入的字符串仅包含字符 ‘0’ - ‘9’,因而我们不用担心异常数据。
  3. 累加数不能以0打头,但是0可以作为单独一个数存在。
  4. 可能存在超出数字范围的大数

这么看来,此题是很简单的问题。前提是,你解决了最重要的困难。

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、总结

星期一!你是带着怎样的心情回到职场的呢?
总之社畜是不怎么开心就是了。毕竟只想躺着拿钱
总之年关将至,你的业务是已经上线、手头放空?还是仍在赶进度中呢?

但总之,今天也是腊八节,有习惯的记得喝腊八粥哦。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值