📌  相关文章
📜  对 Q 查询的斐波那契数字总和范围内可被 K 整除的数字进行计数(1)

📅  最后修改于: 2023-12-03 15:25:10.601000             🧑  作者: Mango

题目介绍

本题的目标是编写一个程序,计算给定范围内斐波那契数字总和中,可被给定整数K整除的数字个数。

具体而言,你需要实现一个函数count_fibo_sum_range(start: int, end: int, k: int) -> int,其中:

  • start表示斐波那契数字范围的起点(包含);
  • end表示斐波那契数字范围的终点(包含);
  • k表示要统计的可被整除的数值。

函数需要返回斐波那契数字范围内可被k整除的数字个数。

举例而言,对于输入start=1, end=10, k=4,计算出的斐波那契数字总和为88,其中可被4整除的数字有3, 8,因此函数应该返回2

实现思路

该题需要计算给定斐波那契数字范围内的斐波那契数列之和。一种简单的方法是通过循环迭代实现,但这种方法的时间复杂度为$O(n)$,在给定的约束条件下可能无法满足运行时限。

换一种思路,我们可以注意到,任意相邻的两个斐波那契数之间,其比率非常接近黄金比例$\phi={(1+\sqrt{5})}/{2}$,即$n_{i-1}/n_i \approx \phi$。因此,斐波那契数列可以视为一个等比数列,其通项公式为$n_i=\left({\phi^i-\psi^i}\right)/{\sqrt{5}}, i\in Z$,其中$\psi={(1-\sqrt{5})}/{2}$。 这个公式的证明可以通过数学归纳法得出。

利用这个公式,我们可以提出两个进一步的观察:

  • 在给定范围内,斐波那契数列中总有一部分数字是可以被特定整数k整除的。具体而言,如果我们定义$n_0=0,n_1=1$,那么从第三项$n_2$开始,后续所有的斐波那契数字都有$n_i=n_{i-1}+n_{i-2}$成立。因此,如果取余数$n_i\mod k$的结果满足$n_i\mod k=0$,那么就可以判定此数字为可被k整除的数字。
  • 我们无需将斐波那契数字计算出来,因为我们仅需要对其做求和,而不关心其具体数值。因此,我们可以考虑计算首项为$n_s$、末项为$n_e$的斐波那契数列的和。

有了上述结论之后,我们可以实现一个时间复杂度为$O(\log n)$,满足即可通过本题的算法:

  1. 首先,确定给定斐波那契数字范围内,最小的斐波那契数字lo和最大的斐波那契数字hi。可以分别通过求解$\phi^{lo} > k$和$\phi^{hi} > k\times(\sum_{i=0}^{hi} f_i)/f_{hi}$(其中$f_i$为第$i$个斐波那契数字)两个不等式得到。
  2. 接下来,可以编写一个函数fib_sum(n: int) -> Tuple[int, int],用来计算前n个斐波那契数列的和以及前n+1个斐波那契数列的和,返回结果为(f_{n}, f_{n+1})。实现过程可以参考提议思路的等比数列公式。
  3. 计算出斐波那契数字lohi的索引,分别记为lo_idxhi_idx。则计算出其对应的前缀和(lo_sum, lo_prev)(hi_sum, hi_prev)即可通过简单的计算得到答案。具体而言:
    lo_sum, lo_prev = fib_sum(lo_idx)
    hi_sum, hi_prev = fib_sum(hi_idx)
    base_sum, _ = fib_sum(lo_idx - 1)
    fib_sum_range = hi_sum - lo_sum + lo_prev - base_sum
    return (hi_idx - lo_idx + 1) - div_ceil(fib_sum_range, k)

其中,base_sumlo_idx之前(不包含lo_idx)斐波那契数字的和,div_ceil是向上取整的整数除法操作。全文代码如下所示:

from typing import Tuple

def count_fibo_sum_range(start: int, end: int, k: int) -> int:
    def div_ceil(a, b):
        div, mod = divmod(a, b)
        return div + (mod != 0)
    def fib_sum(n: int) -> Tuple[int, int]:
        if n <= 0:
            return 0, 0
        elif n == 1:
            return 1, 0
        else:
            fib_n, fib_n_1 = fib_sum(n // 2)
            fib_2n, fib_2n_1 = fib_n * (2 * fib_n_1 + fib_n), fib_n ** 2 + fib_n_1 ** 2
            return (fib_2n, fib_2n_1) if n % 2 == 0 else (fib_2n_1, fib_2n + fib_2n_1)

    # Step 1: Calculate bounds.
    phi = (1 + 5 ** 0.5) / 2
    if start == 0 and k == 1:
        return 0
    elif start == 0:
        lo = 1
    else:
        lo = div_ceil((k + 1) / 2, phi) - 1
    hi = div_ceil(k * (fib_sum(end + 1)[0] - 1), phi) - 1

    # Step 2: Compute sum of Fibonacci numbers.
    lo_idx, hi_idx = lo + 1, hi + 1
    lo_sum, lo_prev = fib_sum(lo_idx)
    hi_sum, hi_prev = fib_sum(hi_idx)
    base_sum, _ = fib_sum(lo_idx - 1)
    fib_sum_range = hi_sum - lo_sum + lo_prev - base_sum

    # Step 3: Count numbers that are divisible by k.
    return (hi_idx - lo_idx + 1) - div_ceil(fib_sum_range, k)

总结

斐波那契数列是一种被广泛使用的数学工具。本题的实现过程利用了斐波那契数列的一些特定性质,并结合了快速计算斐波那契数列前缀和的技巧,实现了一个时间复杂度为$O(\log n)$的算法。实现过程可能相对复杂,但如果理解斐波那契数列的性质,便可以轻松写出该算法。