斐波那契数列题型全解 Shepard-Wang

一 斐波那契数列如此简单

先来看一道 AcWing 上的简单题:

717. 简单斐波那契

以下数列0 1 1 2 3 5 8 13 21 …被称为斐波纳契数列。

这个数列从第3项开始,每一项都等于前两项之和。

输入一个整数N,请你输出这个序列的前N项。

输入格式

一个整数N。

输出格式

在一行中输出斐波那契数列的前N项,数字之间用空格隔开。

数据范围

0<N<46

输入样例:

5

输出样例:

0 1 1 2 3
#include<cstdio>
#include<iostream>

using namespace std;

int main(void)
{
    int a = 0, b = 1, n;
    
    scanf("%d", &n);
    
    while(n--)
    {
        printf("%d ", a);
        a += b;
        swap(a, b);
    }
    
    return 0;
}

这道题对标 LeetCode 上的另一道简单题

这道题中加上了取模运算

剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

提示:

0 <= n <= 100

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

#define MOD  (int)(1e9 + 7)

class Solution {
public:
    int fib(int n) 
    {
        int a = 0, b = 1;
        while(n-- > 0)
        {
            a += b;
            a %= MOD;
            swap(a, b);
        }
        return a;
    }
};

我们也可以用递归来写:

#define MOD  (int)(1e9 + 7)

class Solution {
public:
    int fib(int n) 
    {
        return f(n);
    }
private:
    int f(int n)
    {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        return (f(n - 1) + f(n - 2)) % MOD ;
    }
};

但是注意到这道题给的 n 会比较大,所以程序会运行超时。我们可以通过动态规划来合理的剪枝,具体参考下文

二 斐波那契问题还能这样求?

一样很简单吧?我们先来看看 LeetCode 评论区中有那些人才有其他的做法:

面向测试编程
int f[]={0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,134903163,836311896,971215059,807526948,778742000,586268941,365010934,951279875,316290802,267570670,583861472,851432142,435293607,286725742,722019349,8745084,730764433,739509517,470273943,209783453,680057396,889840849,569898238,459739080,29637311,489376391,519013702,8390086,527403788,535793874,63197655,598991529,662189184,261180706,923369890,184550589,107920472,292471061,400391533,692862594,93254120,786116714,879370834,665487541,544858368,210345902,755204270,965550172,720754435,686304600,407059028,93363621,500422649,593786270,94208912,687995182};
int fib(int n){
    return f[n];
}
动态规划

参考

上面我们直接递归会运行超时,现在我们合理剪枝

#define MOD  (int)(1e9 + 7)

int arr[150];

class Solution {
public:
    int fib(int n) 
    {
        arr[1] = 1;
        return f(n);
    }
private:
    int f(int n)
    {
        if(n == 0 || arr[n]) return arr[n];
        arr[n] = (f(n - 1) + f(n - 2)) % MOD ;
        return arr[n];
    }
};
神奇的 & 运算

题解

#define MOD  (int)(1e9 + 7)

class Solution 
{
public:
    int fib(int n) 
    {
        int arr[2] = {0, 1};
        for(int i = 2; i <= n; ++i)
        {
            arr[i & 1] = (arr[0] + arr[1]) % MOD;
        }
        return arr[n & 1];
    }
};
矩阵快速幂

题解

const int MOD = 1000000007;
const int N = 50;

struct mat
{
    int m[N][N];
    
    mat()
    {
        memset(m, 0, sizeof(m));
    }
    
    // 单位矩阵
    void setUnitMat()
    {
        for (int i = 0; i < N; ++i)
            m[i][i] = 1;
    }
};

class Solution {
public:
    int fib(int n)
    {
        if (n <= 1)
            return n;
        mat q;
        q.m[0][0] = q.m[0][1] = q.m[1][0] = 1;
        q.m[1][1] = 0;
        mat ans = fastPow(q, n - 1);

        return ans.m[0][0];
    }
private:
    const int n = 2;

    mat&& fastPow(mat q, int n)
    {
        // ans 初始为单位矩阵
        mat ans;
        ans.setUnitMat();
        while (n > 0)
        {
            if (n & 1)
                ans = matrixMul(q, ans);
            q = matrixMul(q, q);
            n >>= 1;
        }
        return std::move(ans);
    }

    mat&& matrixMul(mat& q, mat& M)
    {
        mat ans;
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < n; ++j)
            {
                for (int k = 0; k < n; ++k)
                {
                    ans.m[i][j] += (q.m[i][k] * M.m[k][j]) % MOD;
                }
            }
        }
        return std::move(ans);
    }
};
205. 斐波那契

输入样例:

0
9
999999999
1000000000
-1

::heavy_check_mark: 自己的编译器上可以运行,放上去段错误了

去掉右值引用即可通过

#define _CRT_SECURE_NO_WARNINGS

#include<cstdio>
#include<iostream>

using namespace std;


#define MOD (int)10000 
#define N 2


struct mat
{
    int a[N][N];

    mat()
    {
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < N; ++j)
            {
                a[i][j] = 0;
            }
        }
    }

    void setUnitMat()
    {
        for (int i = 0; i < N; ++i)
            a[i][i] = 1;
    }
};

mat&& matMul(mat& a, mat& b)
{
    mat res;
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            for (int k = 0; k < N; ++k)
            {
                res.a[i][j] += (a.a[i][k]  * b.a[k][j]) % MOD;
                res.a[i][j] % MOD;
            }
        }
    }
    return std::move(res);
}

mat&& fastPow(mat q, int n)
{
    mat u;
    u.setUnitMat();
    while (n > 0)
    {
        if (n & 1) u = matMul(u, q);
        q = matMul(q, q);
        n >>= 1;
    }
    return std::move(u);
}

int main(void)
{
    int n;
    mat q;
    q.a[0][0] = q.a[0][1] = q.a[1][0] = 1;
    q.a[1][1] = 0;

    scanf("%d", &n);
    while (n != -1)
    {
        if (n <= 1)
        {
            printf("%d\n", n);
            scanf("%d", &n);
            continue;
        }
        mat res = fastPow(q, n - 1);
        printf("%d\n", res.a[0][0] % MOD);
        scanf("%d", &n);
    }

    return 0;
}

AC 代码

#define _CRT_SECURE_NO_WARNINGS

#include<cstdio>
#include<iostream>

using namespace std;


#define MOD (int)10000 
#define N 2


struct mat
{
    int a[N][N];

    mat()
    {
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < N; ++j)
            {
                a[i][j] = 0;
            }
        }
    }

    void setUnitMat()
    {
        for (int i = 0; i < N; ++i)
            a[i][i] = 1;
    }
};

mat matMul(mat& a, mat& b)
{
    mat res;
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            for (int k = 0; k < N; ++k)
            {
                res.a[i][j] += (a.a[i][k]  * b.a[k][j]) % MOD;
                res.a[i][j] % MOD;
            }
        }
    }
    return res;
}

mat fastPow(mat q, int n)
{
    mat u;
    u.setUnitMat();
    while (n > 0)
    {
        if (n & 1) u = matMul(u, q);
        q = matMul(q, q);
        n >>= 1;
    }
    return u;
}

int main(void)
{
    int n;
    mat q;
    q.a[0][0] = q.a[0][1] = q.a[1][0] = 1;
    q.a[1][1] = 0;

    scanf("%d", &n);
    while (n != -1)
    {
        if (n <= 1)
        {
            printf("%d\n", n);
            scanf("%d", &n);
            continue;
        }
        mat res = fastPow(q, n - 1);
        printf("%d\n", res.a[0][0] % MOD);
        scanf("%d", &n);
    }

    return 0;
}
斐波那契额数列的大数运算

观察到 f[100]687995182 当 n 很大时会超过整形的范围

参考

#include<cstdio>
#include<cstring>


int prev[100], cur[100], tmp[100];
int prevPos = 0, curPos = 0;

void print()
{
    // 数字在数组中存放时低位在前,需要倒序输出
    for(int i = prevPos; i >= 0; --i)
        printf("%d", prev[i]);
    printf(" ");
}

void add()
{
    memcpy(tmp, cur, sizeof(cur));
    
    // 更新 prevPos
    // 更新原因:prev 中大于 prevPos 的元素肯定是 0 ,但是 cur 中的元素可能会进位 
    prevPos = curPos;
    for(int i = 0; i <= prevPos; ++i)
    {
        cur[i] += prev[i];
        // 进位的前提是在 i + 1 在 prevPos 范围内
        if(cur[i] > 9 && i + 1 <= prevPos) 
        {
            cur[i] -= 10;
            ++cur[i + 1];
        }
    }
    // 如果最高位大于 9,需要增加向后增加高位,此时需要更新 curPos
    if(cur[prevPos] > 9)
    {
        cur[prevPos] -= 10;
        // 增加最大位
        ++curPos;
        ++cur[curPos];
    }
    
    memcpy(prev, tmp, sizeof(cur));
}

int main(void)
{
    cur[0] = 1;
    int n;
    scanf("%d", &n);
    while(n--)
    {
        print();
        add();
    }
    
    return 0;
}

三 走向离谱

873. 最长的斐波那契子序列的长度

如果序列 X_1, X_2, …, X_n 满足下列条件,就说它是 斐波那契式 的:

n >= 3 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2} 给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

(回想一下,子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)

示例 1:

输入: [1,2,3,4,5,6,7,8]
输出: 5
解释:
最长的斐波那契式子序列为:[1,2,3,5,8] 。

示例 2:

输入: [1,3,7,11,12,14,18]
输出: 3
解释:
最长的斐波那契式子序列有:
[1,11,12],[3,11,14] 以及 [7,11,18] 。

提示:

3 <= A.length <= 1000
1 <= A[0] < A[1] < ... < A[A.length - 1] <= 10^9
(对于以 Java,C,C++,以及 C# 的提交,时间限制被减少了 50%)

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/length-of-longest-fibonacci-subsequence 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

暴力

使用 set

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) 
    {
        int N = arr.size();
        set<int> S;
        for(auto& i : arr)
            S.insert(i);
        int ans = 0;
        for(int i = 0; i < N - 1; ++i)
        {
            for(int j = i + 1; j < N; ++j)
            {
                int f1 = arr[i], f2 = arr[j];
                int cnt = 2;
                // 继续向后寻找斐波那契数
                while(S.find(f1 + f2) != S.end())
                {
                    ++cnt;
                    f1 = f1 + f2;
                    swap(f1, f2);
                }
                ans = max(ans, cnt);
            }
        }
        // 斐波那契数列最少为 3 个
        return ans >= 3 ? ans : 0;
    }
};

时间复杂度:O(n^2logn)(while循环的复杂度是 logn)

空间复杂度:O(n)

动态规划

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) 
    {
        int N = arr.size();
        unordered_map<int, int> idx;
		// 建立元素与下标对应关系
        for(int i = 0; i < N; ++i)
            idx[arr[i]] = i;

        unordered_map<int, int> longest;
        int ans = 0;
        for(int i = 0; i < N - 1; ++i)
        {
            for(int j = i + 1; j < N; ++j)
            {
                // arr[k] + arr[i] == arr[j] 
                if(arr[j] - arr[i] < arr[i] && idx.count(arr[j] - arr[i]))
                {
                    // k 应该从 idx 中寻找得到下标
                    int k = idx[arr[j] - arr[i]];
                    longest[i * N + j] = longest[k * N + i] + 1;
                    ans = max(ans, longest[i * N + j] + 2);
                }
            }
        }
        return ans;
    }
};

1414. 和为 K 的最少斐波那契数字数目

给你数字 k ,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。

斐波那契数字定义为:

F1 = 1 F2 = 1 Fn = Fn-1 + Fn-2 , 其中 n > 2 。 数据保证对于给定的 k ,一定能找到可行解。

示例 1:

输入:k = 7
输出:2 
解释:斐波那契数字为:1,1,2,3,5,8,13,……
对于 k = 7 ,我们可以得到 2 + 5 = 7 。

示例 2:

输入:k = 10
输出:2 
解释:对于 k = 10 ,我们可以得到 2 + 8 = 10 。

示例 3:

输入:k = 19
输出:3 
解释:对于 k = 19 ,我们可以得到 1 + 5 + 13 = 19 。

提示:

1 <= k <= 10^9

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

三个证明至关重要:

  • 不可能 含有两个连续的斐波那契数

  • 可以不 使用重复的斐波那契数

    对于目标数 k,设 k = 2Fx
    2Fx = Fx + Fx = F(x - 2) + F(x - 1) + F(x) = F(x - 2) + F(x + 1)
    2Fx > F(x - 1) + F(x) 所以 F(x + 1) 必在小于 k 的斐波那契数中,所以可以用 F(x + 1) 与 F(x - 2) 的组合替换 2F(x)
    
  • 解一定包含小于等于 k 的最大斐波那契数

    k 中要包含小于等于 k 的最大斐波那契数(F(max))。如果这一点我们可以接受的话,那么如果 k - F(max) > 0,此时的问题相当于之前的子问题。子问题中 k 为 k - F(max) 继续寻找下一个 F(max) 即可

class Solution {
public:
    int findMinFibonacciNumbers(int k) 
    {
        vector<int> fib(2, 1);
        // top 记录 fib 最高一位的下标
        int top = 1;
        while(fib[top] <= k)
        {
            fib.push_back(fib[top - 1] + fib[top]);
            ++top;
        }
        int ans = 0;
        // 从后向前遍历,如果 k >= fib[i] 则减去,ans 增加 1
        for(int i = top; i >= 0; --i)
        {
            if(k >= fib[i])
            {
                ++ans;
                k -= fib[i];
            }
            if(!k) break;
        }
        return ans;
    }
};

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

给定一个数字字符串 S,比如 S = “123456579”,我们可以将它分成斐波那契式的序列 [123, 456, 579]。

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

0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型); F.length >= 3; 对于所有的0 <= i < F.length - 2,都有 F[i] + F[i+1] = F[i+2] 成立。 另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。

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

示例 1:

输入:"123456579"
输出:[123,456,579]

示例 2:

输入: "11235813"
输出: [1,1,2,3,5,8,13]

示例 3:

输入: "112358130"
输出: []
解释: 这项任务无法完成。

示例 4:

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

示例 5:

输入: "1101111"
输出: [110, 1, 111]
解释: 输出 [11,0,11,11] 也同样被接受。

提示:

1 <= S.length <= 200 字符串 S 中只含有数字。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

int length;
class Solution {
public:
    vector<int> splitIntoFibonacci(string S) 
    {
        vector<int> ans;
        length = S.length();
        bruteforce(S, ans, 0, 0);
        return ans;
    }
private:
    bool bruteforce(string& S, vector<int>& ans, int size, int idx)
    {
        // S 遍历完成
        if(idx == length)
        {
            // 如果 ans 元素个数大于 3,返回真
            return size >= 3;
        }
        for(int i = idx; i < length; ++i)
        {
            // 位数大于 1 的数不能以 0 开头
            if(S[idx] == '0' && i > idx)
                break;
            long long num = stoll(S.substr(idx, i - idx + 1));
            // 不能超过 32-bit int 的最大值
            if(num > INT_MAX) break;
            // 如果 ans 长度大于 2 且 num 大于 ans 前两个元素之和,不用继续向后找
            if(size >= 2 && (long long)ans[size - 2] + ans[size - 1] < num)
                break;
            if(size <= 1 || (long long)ans[size - 2] + ans[size - 1] == num)
            {
                ans.push_back(num);
                // 更新状态
                if(bruteforce(S, ans, size + 1, i + 1))
                    return true;
                // 回溯
                ans.pop_back();
            }
        }
        return false;
    }
};