306. 累加数【Normal】【穷举】

306. 累加数

难度中等

累加数 是一个字符串,组成它的数字可以形成累加序列。

一个有效的 累加序列 必须 至少 包含 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)组成

进阶:你计划如何处理由过大的整数输入导致的溢出?


思路:

穷举 累加序列 第二个数字的 start 和 end 的所有可能性

一个累加序列,当它的第一个数字 和 第二个数字 以及 总长度确定后,这整个累加序列也就确定了。

根据这个性质,我们可以穷举 累加序列的 第二个数字的 start 和 end 的所有可能性,

对每个可能性,进行一次合法性的判断。

当出现一次合法的累加序列后,即可返回 true。

当所有可能性都遍历完仍无法找到一个合法的累加序列时,返回 false。


记字符串 num 的长度为 n,

序列最新确定的两个数中,位于前面的数字为 first,

first 的最高位在 num 中的下标为 firstStart,

first 的最低位在 num 中的下标为 firstEnd。

记序列最新确定的两个数中,位于后面的数字为 second,

second 的最高位在 num 中的下标为 secondStart,

second 的最低位在 num 中的下标为 secondEnd。

在穷举第二个数字start和end的过程中,容易得到以下两个结论:

firstStart = 0,

firstEnd = secondStart - 1。


因此,我们只需要用两个循环来遍历 secondStart 和 secondEnd 所有可能性即可。

在判断累加序列的合法性时,用第415题.字符串的加法来算出 first 与 second 之和 third。

再将 third 与 num 接下来紧邻的 相同长度的字符串进行比较。

当 third 过长 或者 与接下来的字符串不相同时,则说明这不是一个合法的累加序列。

当相同时,则我们为这个序列新确定了一个数字。

如果 third 刚好抵达 num 的末尾时,则说明这是一个合法的序列。

当 num 还有多余的字符时,则需要更新 firstStart,firstEnd,secondStart,secondEnd, 继续进行合法性的判断。

仍需要注意的是,当某个数字长度大于等于 2 时,这个数字不能以 0 开头,这部分的判断可以在两层循环体的开头完成。


解答

/**
穷举 累加序列 第二个数字的start和end的所有可能性

一个累加序列,当它的第一个数字 和 第二个数字 以及 总长度确定后,这整个累加序列也就确定了。

根据这个性质,我们可以穷举 累加序列的 第二个数字的start和end 的所有可能性,
对每个可能性,进行一次合法性的判断。

当出现一次合法的累加序列后,即可返回 true。
当所有可能性都遍历完仍无法找到一个合法的累加序列时,返回 false。

记字符串 num 的长度为 n,

序列最新确定的两个数中,位于前面的数字为 first,
first 的最高位在 num 中的下标为 firstStart,
first 的最低位在 num 中的下标为 firstEnd。

记序列最新确定的两个数中,位于后面的数字为 second,
second 的最高位在 num 中的下标为 secondStart,
second 的最低位在 num 中的下标为 secondEnd。

在穷举第二个数字start和end的过程中,容易得到以下两个结论:
firstStart = 0,
firstEnd = secondStart - 1。

因此,我们只需要用两个循环来遍历 secondStart 和 secondEnd 所有可能性即可。

在判断累加序列的合法性时,用字符串的加法来算出 first 与 second 之和 third。

再将 third 与 num 接下来紧邻的 相同长度的字符串进行比较。

当 third 过长 或者 与接下来的字符串不相同时,则说明这不是一个合法的累加序列。

当相同时,则我们为这个序列新确定了一个数字。

如果 third 刚好抵达 num 的末尾时,则说明这是一个合法的序列。

当 num 还有多余的字符时,则需要更新 firstStart,firstEnd,secondStart,secondEnd, 继续进行合法性的判断。

仍需要注意的是,当某个数字长度大于等于 2 时,这个数字不能以 0 开头,这部分的判断可以在两层循环体的开头完成。
 */

/**
 * @param {string} num
 * @return {boolean}
 */
var isAdditiveNumber = function(num) {
    const n = num.length;
    // 双重循环,穷举第2个数的开始和结束位置
    for (let secondStart = 1; secondStart < n -1; secondStart++) {
        // 第1个数字是0,那第secondStart就只能是1了
        if (num[0] === '0' && secondStart !== 1) {
            break;
        }
        for(let secondEnd = secondStart; secondEnd < n - 1; secondEnd++) {
            // 如果第2个数字是0,那么第2个数字长度只能是一位
            if (num[secondStart] === '0' && secondEnd !== secondStart) {
                break;
            }
            // 对每一个可能性,都执行valid函数
            if (valid(secondStart, secondEnd, num)) {
                return true;
            }
        }
    }
    // 第2个数的start和end都穷举完了,还没找到
    return false;
};

function valid(secondStart, secondEnd, num) {
    const n = num.length;
    let firstStart = 0, firstEnd = secondStart - 1;

    while(secondEnd < n - 1) {                
        // 字符串加法算出来 third
        let s1 = num.slice(firstStart, firstEnd + 1);
        let s2 = num.slice(secondStart, secondEnd + 1);
        const third = addStrings(s1, s2);

        // 加出来的third的范围
        const thirdStart = secondEnd + 1;
        const thirdEnd = secondEnd + third.length;

        // 计算出来的thirdEnd如果超出来num长度范围
        // 再将计算出来的third 与 num 接下来紧邻的 相同长度的截取出来的字符串进行比较。
        if (thirdEnd > n - 1 || num.slice(thirdStart, thirdEnd + 1) !== third) {
            break;
        }

        // 理论上应该是加法得到的third刚好在num的末尾
        if (thirdEnd === n - 1) {
            return true;
        }

        // 到这儿了,说明计算正确
        // 更新变量,继续进行下一轮的计算,直到num末尾
        firstStart = secondStart;
        firstEnd = secondEnd;
        secondStart = thirdStart;
        secondEnd = thirdEnd;
    }
    return false;
}

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function(num1, num2) {
    // 模拟手写加法,即:
    // 模拟「竖式加法」
    // 两个指针从后往前
    let lastI = num1.length - 1;
    let lastJ = num2.length - 1;
    // console.log(`${lastI} - ${lastJ}`);
    // 进位
    let carry = 0;
    let res = [];
    // 3者之一(有进位,或者 num1 num2的指针 还没移动到 首位)
    while(lastI >= 0 || lastJ >= 0 || carry !== 0) {
        // 优化点:当下标为负数的时候,直接返回 0
        let a = num1.charAt(lastI) - '0';
        let b = num2.charAt(lastJ) - '0';
        let sum = a + b + carry;
        // console.log(`${a} + ${b} = ${sum}`);
        let mod = sum % 10;
        res.push(mod);
        carry = Math.floor(sum / 10);
        // 利用完最后一位,指针前移
        lastI--;
        lastJ--;
    }
    // console.log(res);
    return res.reverse().join('');
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值