esc to dismiss
x

信息

题解

题意: 要将整个序列, 切割成多个子序列, 让子序列成斐波那契.

Info
  • 斐波那契至少三个元素
  • 但最终有多少子序列, 是没有上限的, 因此直接for是找不到边界的.
  • 后一个序列必然以前一个的终点为起点

对于这种不定数量的枚举, 只能回溯. 除了自身回溯的边界, 题目给出了一些其他限制条件, 方便剪枝.

  • 每个子序列, 不能以 0 开头
  • 每个子序列的大小需要小于 \(2^{31}\)
  • 找到一组结果即可返回, 不用全部
  • 一组序列至少三个,
    • 只有两个时组不成 斐波那契
    • 已经有多于两个时, 后一个必须等于前两个的加和
pub fn split_into_fibonacci(num: String) -> Vec<i32> {
    const MAX : i64 = 1 << 31;
    fn backtrack(
        ans: &mut Vec<i64>, num: &[u8],
        length: usize, idx: usize, 
        sum: i64, prev: i64 // 前两个数的加和, 前一个数
    ) -> bool {
        if idx == length {
            // 返回是否已经找到结果, 如果是, 可以提前终止, 剪枝4
            return ans.len() >= 3;
        }
        // 从 idx 开始, 枚举所有可能的数
        let mut curr = 0;
        for i in idx..length {
            // 自身为0是可以的, 但是下一个数不能以0开头
            // 因此跳过后续枚举, 剪枝1
            if i > idx && num[idx] == b'0' {
                break;
            }
            curr = curr * 10 + (num[i] - b'0') as i64;
            if curr >= MAX {
                // 题目要求每个数, 大于0, 小于 2**31
                // 因此如果发现当前的数不符合, 则整个尝试无效, 剪枝2
                break;
            }
            if ans.len() >= 2 {
                // 斐波那契序列, 至少有3个, 因此在已经有2个数时,
                // 第三个不能比前两个的加和小, 如果小, 也不用再套一层, 直接再吃一位
                // 如果大, 那就必然组不成序列, 整个尝试无效, 剪枝3
                if curr < sum {
                    continue;
                } else if curr > sum {
                    break;
                }
            }
            ans.push(curr);
            if backtrack(ans, num, length, i+1, prev+curr, curr) {
                return true;
            }
            ans.pop();
        }
        return false;
    }

    let mut ans = vec![];
    if backtrack(&mut ans, num.as_bytes(), num.as_bytes().len(), 0, 0, 0) {
        ans.into_iter().map(|n| n as i32).collect()
    } else {
        vec![]
    }
}
x