【LeetCode 每日一题】1414. 和为 K 的最少斐波那契数字数目
Problem: 1414. 和为 K 的最少斐波那契数字数目
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O((log K)^2) 或 O(log K)
- 空间复杂度:O(log K)
整体思路
这段代码的目的是找到表示一个给定整数 k 所需的最少斐波那契数的数量。这里的斐波那契数是指不重复的、标准的斐波那契数列中的数(通常从 F_1=1, F_2=1 开始)。例如,k=7 可以表示为 5+2,所以需要 2 个斐波那
契数。
该算法采用了一种 贪心策略 (Greedy Strategy),并结合了 递归 (Recursion) 来实现。
-
核心贪心思想 (基于 宰克endorf定理)
- 该算法的正确性基于一个名为 宰克endorf定理 (Zeckendorf’s theorem) 的数学定理。该定理指出,任何正整数都可以唯一地表示为不相邻的斐波那契数的和。
- 这个定理的一个重要推论是,要找到表示
k的最少数量的斐波那契数,我们可以采用贪心策略:- 首先,找到小于或等于
k的最大斐波那契数,我们称之为fib_max。 - 将
fib_max选入我们的和中。 - 然后,对剩余的数
k - fib_max重复这个过程。
- 首先,找到小于或等于
- 由于斐波那契数列的性质 (
F_n < F_{n+1} < 2 * F_n),可以证明这种贪心选择总是最优的,并且不会选择到相邻的斐波那契数。
-
findMinFibonacciNumbers(递归主函数)- 这个函数实现了上述的贪心策略。
- 基准情形 (Base Case):
if (k == 0) { return 0; }。当k减为 0 时,说明我们已经用斐波那契数完全表示了原始的k,此时不再需要任何斐波那契数,返回 0。 - 递归步骤:
maxFibonacci(k): 调用一个辅助函数,找到小于或等于当前k的最大斐波那契数。k - maxFibonacci(k): 从k中减去这个最大的斐波那契数,得到剩余需要表示的数。findMinFibonacciNumbers(...): 对这个剩余的数进行递归调用,求解表示它所需的最少斐波那契数数量。... + 1: 将递归调用的结果加 1。这个+1代表我们当前选择的那个maxFibonacci(k)。
-
maxFibonacci(辅助函数)- 这个函数的功能是找到小于或等于
n的最大斐波那契数。 - 它通过一个
while循环,从a=1, b=1开始,迭代地生成斐波那契数列 (a,b,a+b, …)。 - 循环的条件是
while (b <= n)。 - 当循环结束时,
b刚刚超过了n,而a正好是最后一个小于或等于n的斐波那契数。因此,返回a。
- 这个函数的功能是找到小于或等于
完整代码
class Solution {/*** 计算表示整数 k 所需的最少斐波那契数的数量。* @param k 目标正整数* @return 最少的斐波那契数数量*/public int findMinFibonacciNumbers(int k) {// 基准情形:如果 k 已经是 0,则不需要任何斐波那契数。if (k == 0) {return 0;}// 贪心选择:// 1. 找到小于或等于 k 的最大斐波那契数。int maxFib = maxFibonacci(k);// 2. 对剩余部分 k - maxFib 进行递归求解,// 并将当前选择的这个斐波那契数 (计为1) 加入结果。return findMinFibonacciNumbers(k - maxFib) + 1;}/*** 辅助函数:找到小于或等于 n 的最大斐波那契数。* @param n 上限值* @return 小于或等于 n 的最大斐波那契数*/public int maxFibonacci(int n) {// 初始化斐波那契数列的前两项 (F_1=1, F_2=1)int a = 1;int b = 1;// 循环生成斐波那契数,直到 b 超过 nwhile (b <= n) {int temp = a + b; // 计算下一项a = b; // 更新 ab = temp; // 更新 b}// 当循环结束时,b 是第一个大于 n 的斐波那契数,// a 则是最后一个小于或等于 n 的斐波那契数。return a;}
}
时空复杂度
- 设
K是输入值。
时间复杂度:O((log K)^2) 或 O(log K)
-
maxFibonacci(n)的复杂度:- 斐波那契数列是指数级增长的。第
m个斐波那契数F_m大约是φ^m / sqrt(5),其中φ是黄金比例 (约1.618)。 - 因此,要生成一个值达到
n的斐波那契数,需要的迭代次数与log(n)成正比。 - 所以,
maxFibonacci(n)的时间复杂度是 O(log n)。
- 斐波那契数列是指数级增长的。第
-
findMinFibonacciNumbers(k)的复杂度分析:- 该函数是递归的。我们需要分析递归的深度和每次递归调用的开销。
- 递归深度:每次递归,
k的值都会减去一个maxFibonacci(k)。由于斐波那契数列的性质,k - maxFibonacci(k) < k / 2并不总是成立,但可以证明k - maxFibonacci(k)会显著减小。递归的深度也大致是 O(log K) 级别。 - 每次递归的开销:在每次
findMinFibonacciNumbers调用中,都会调用一次maxFibonacci。 - 简单分析 (上界):递归深度 O(log K),每次开销 O(log K),总时间复杂度为
O(log K * log K) =O((log K)^2)。 - 更精确的分析:可以预先生成所有小于等于
K的斐波那契数并存储起来。这样,每次贪心选择就变成了在列表中进行二分查找 (O(log(log K))),或者直接线性查找。如果预先生成斐波那契数(耗时 O(log K)),然后进行贪心选择,总时间可以优化到 O(log K)。对于当前代码,O((log K)^2)是一个更贴切的描述。
综合分析:
当前递归实现的时间复杂度为 O((log K)^2)。
空间复杂度:O(log K)
- 主要存储开销:
- 该算法是递归的,主要的额外空间开销来自于递归调用栈。
- 如上文分析,递归的最大深度与
log K成正比。
- 辅助变量:在每个函数调用中,只使用了几个基本类型的变量,占用 O(1) 空间。
综合分析:
算法所需的额外空间由递归栈深度决定。因此,其空间复杂度为 O(log K)。
