306. 累加数Normal穷举

Posted pre_eminent

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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('');
;

以上是关于306. 累加数Normal穷举的主要内容,如果未能解决你的问题,请参考以下文章

Python|Leetcode《306》|累加数

leetcode 1月10日每日一题 306. 累加数

leetcode 1月10日每日一题 306. 累加数

306. 累加数-leetcode自解(DFS)

LeetCode 306 累加数[回溯 DFS] HERODING的LeetCode之路

373. 查找和最小的 K 对数字Normal暴力穷举