leetcode
leetcode 251 ~ 300
累加数

累加数

难度:

标签:

题目描述

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

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

 

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

代码结果

运行时间: 19 ms, 内存: 16.1 MB


/*
 * 题目思路:
 * 使用Java Stream API的解法较为复杂,原因在于累加数的验证过程涉及递归。我们需要处理字符串分割、转换和校验的过程。
 * 我们首先尝试用流的方式来获取所有可能的第一个和第二个数字,然后检查剩余部分是否能形成累加序列。
 */
 
import java.util.stream.IntStream;
 
public class AdditiveNumberStream {
    public boolean isAdditiveNumber(String num) {
        int n = num.length();
        return IntStream.range(1, n / 2 + 1)
            .flatMap(i -> IntStream.range(i + 1, n)
                .filter(j -> isValid(num, i, j)))
            .findAny()
            .isPresent();
    }
 
    private boolean isValid(String num, int i, int j) {
        if (num.charAt(0) == '0' && i > 1) return false;
        if (num.charAt(i) == '0' && j - i > 1) return false;
        String first = num.substring(0, i);
        String second = num.substring(i, j);
        StringBuilder sb = new StringBuilder(first).append(second);
        while (sb.length() < num.length()) {
            String sum = addStrings(first, second);
            sb.append(sum);
            first = second;
            second = sum;
        }
        return sb.toString().equals(num);
    }
 
    private String addStrings(String num1, String num2) {
        StringBuilder sb = new StringBuilder();
        int carry = 0, i = num1.length() - 1, j = num2.length() - 1;
        while (i >= 0 || j >= 0 || carry != 0) {
            int x = i >= 0 ? num1.charAt(i--) - '0' : 0;
            int y = j >= 0 ? num2.charAt(j--) - '0' : 0;
            int sum = x + y + carry;
            sb.append(sum % 10);
            carry = sum / 10;
        }
        return sb.reverse().toString();
    }
}
 

解释

方法:

这个题解的思路是暴力搜索所有可能的累加数组合。具体来说: 1. 首先判断字符串长度是否小于3,如果是就直接返回False,因为累加序列至少要有3个数。 2. 然后用两个循环来枚举前两个数a和b。第一个数a的范围是[0,length//2],第二个数b的范围是[i+1,length],其中i是a的结束位置。 3. 在枚举a和b时,如果a或b以0开头且不是0本身,就跳过。 4. 对于每一对a和b,调用辅助函数my_check来检查以a和b开头能否形成有效的累加序列。 5. my_check函数递归地计算a+b,将结果与原字符串的剩余部分比较,如果吻合就继续递归检查b和a+b,否则返回False。 6. 如果找到任何一个有效的a和b组合,就返回True,否则在所有组合都检查完后返回False。

时间复杂度:

O(n^4)

空间复杂度:

O(n)

代码细节讲解

🦆
在算法中,如何确定第一个数a的取值范围为[0, length//2]?这个范围是否可能导致错过某些有效的累加数序列?
在累加数问题中,第一个数a的最大长度取length//2是基于累加数序列的性质和长度考虑的。因为累加数至少包含三个数,第三个数是前两个数的和,且第三个数的长度至少与前两个数中较长者相等。假设第一个数a取超过length//2的长度,那么剩余的字符串长度将不足以容纳第二个数b和至少与a等长的第三个数,从而无法形成有效的累加序列。因此,这个范围不会导致错过有效的累加数序列。
🦆
题解提到当遇到数字以'0'开头时会特别处理,为什么对以0开头的情况进行跳过或特殊处理?是否可能因此漏掉符合条件的累加数?
在大多数数字表示法中,除了数字0本身,以0开头的数字被视为非法或多余的前导0。因此,在处理累加数时,如果一个数以0开头并且长度超过1,通常应跳过这样的数。这种处理是为了防止如'01'或'003'等解释为有效数字的情况。在累加数的上下文中,这种处理是正确的,因为每个数字都应当是一个没有前导零的整数。因此,这种处理不会漏掉符合条件的累加数。
🦆
my_check函数中,如何确保每次递归调用都能正确匹配字符串的剩余部分?是否有可能在某些情况下返回错误的匹配结果?
my_check函数通过递归检查累加序列的正确性。它首先计算两个数a和b的和,然后将这个和转换成字符串,并检查当前字符串是否以这个和的字符串形式开始。如果是,递归进入下一个级别,以b和这个和作为新的a和b继续匹配字符串的剩余部分。这种方法理论上能够确保正确匹配字符串的每一个部分。然而,如果实现中存在逻辑错误,如基本情况和终止条件处理不当,或者数值运算中的问题,可能会导致错误的匹配结果。
🦆
在实现中,若输入字符串的长度特别长,递归深度是否会对性能或者系统资源造成影响?是否有方法可以避免过深的递归?
如果输入字符串的长度特别长,递归深度的确可能导致性能问题或者栈溢出,因为每一层递归都会消耗一定的栈空间和处理时间。为了避免递归过深,可以考虑使用迭代代替递归,或者使用尾调用优化(尽管Python本身不支持尾调用优化)。另一种方法是使用动态规划或其他非递归的算法框架,这样可以避免深层递归带来的问题。

相关问题

将数组拆分成斐波那契序列

给定一个数字字符串 num,比如 "123456579",我们可以将它分成「斐波那契式」的序列 [123, 456, 579]

形式上,斐波那契式 序列是一个非负整数列表 f,且满足:

  • 0 <= f[i] < 231 ,(也就是说,每个整数都符合 32 位 有符号整数类型)
  • f.length >= 3
  • 对于所有的0 <= i < f.length - 2,都有 f[i] + f[i + 1] = f[i + 2]

另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。

返回从 num 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []

 

示例 1:

输入:num = "1101111"
输出:[11,0,11,11]
解释:输出[110,1,111]也可以。

示例 2:

输入: num = "112358130"
输出: []
解释: 无法拆分。

示例 3:

输入:"0123"
输出:[]
解释:每个块的数字不能以零开头,因此 "01","2","3" 不是有效答案。

 

提示:

  • 1 <= num.length <= 200
  • num 中只含有数字