Thinking——斐波拉契数列不用递归

斐波拉契数列

这个数列生成规则很简单,每一项都是前两项的和,举例
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……
用数学符号来描述更好

$$ F_{n}=F_{{n-1}}+F_{{n-2}}(n≧2) $$

递归方法求解

这个几行代码就可以解决

// n从1开始
function fib(n) {
    if (n <= 2)
        return 1;
    return fib(n - 1) + fib(n - 2);
}

但是分析一下这个算法的执行过程,以f(6)为例
图片描述
以上的递归树中,很多节点被重复计算,譬如f(2)就被重复执行了5次,另外,递归调用中每一个函数都要保留上下文,所以空间上开销也不小。所以这个方法并不是很理想。

循环方法求解

其实还是利用斐波拉契数列的特性

// n从1开始
function fibFor(n) {
    if (n <= 2)
        return 1;
    let arr = [];
    //arr[0] = 0;
    arr[2] = arr[1] = 1;
    for (let i = 3; i <= n; i++) {
        arr[i] = arr[i - 1] + arr[i - 2];
    }
    return arr[n];
}

当然这个方法还可以压缩一下,因为我们没必要存储一个数组,只用三个变量就行了。

// n从1开始
function fibFor2(n) {
    if (n <= 2)
        return 1;
    let before2 = 1;
    let before1 = 1;
    let now = null;
    for (let i = 3; i <= n; i++) {
        now = before1 + before2;
        before2 = before1;
        before1 = now;
    }
    return now;
}

这其实就是动态规划的思想了,自底向上,用子问题解决父问题。

进一步:LCS问题

最长公共子序列(Longest Common Subsequence)是指,给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度,注意是子序列,不是子串。
举个例子:
对于”ABCD” 和 “EDCA”,这个LCS是 “A” (或 D或C),返回1
对于“ABCD” 和 “EACB”,这个LCS是“AC“(或”AB“)返回2

思路

对于这个问题,设字符串a长度为m,字符串b为n,考察它们的最后一个字符串,如果a[m]==b[n],则Longestm = Longestm-1,否则,Longestm = Max(Longestm-1, Longestm)。找到这个关系后,首先想到的是用递归方法解决问题:

package main

import "fmt"

func max(x, y int) int {
    if x > y {
        return x
    }
    return y
}

func LongestCommonSequence(a string, b string) int {
    aChars := []rune(a)
    bChars := []rune(b)
    aLen := len(aChars)
    bLen := len(bChars)
    if aLen == 0 || bLen == 0 {
        return 0
    }
    if aChars[aLen-1] == bChars[bLen-1] {
        return LongestCommonSequence(string(aChars[:aLen-1]), string(bChars[:bLen-1])) + 1
    } else {
        return max(
            LongestCommonSequence(string(aChars[:aLen-1]), b),
            LongestCommonSequence(a, string(bChars[:bLen-1])))
    }
}

func main() {
    a := "ACDE"
    b := "CBE"
    fmt.Printf("Longest sub sequence length of %s and %s is %d\n", a, b,
        LongestCommonSequence(a, b))
}

这样的解法跟上面斐波拉契的解法一样,会遇到子问题重复计算,而且递归栈要保存临时结果,会占用较大的内存。所以,我们也可以思考一下使用自底而上的方法。考察一下递归表达式,它涉及到两个变量(m,n),所以我们可以用一个二维数组来保存之前的结果。
实现:

func LongestCommonSequenceDP(a string, b string) int {
    aChars := []rune(a)
    bChars := []rune(b)
    aLen := len(aChars)
    bLen := len(bChars)
    if aLen == 0 || bLen == 0 {
        return 0
    }

    record := make([][]int, aLen+1)
    for i := 0; i <= aLen; i++ {
        record[i] = make([]int, bLen+1)
        record[i][0] = 0
    }

    for j := 0; j <= bLen; j++ {
        record[0][j] = 0
    }

    for i := 1; i <= aLen; i++ {
        for j := 1; j <= bLen; j++ {
            if aChars[i-1] == bChars[j-1] {
                record[i][j] = record[i-1][j-1] + 1
            } else {
                record[i][j] = max(record[i][j-1], record[i-1][j])
            }
        }
    }

    return record[aLen][bLen]
}

Salamander
上帝在我很小的时候送给我了两个苹果,一个红苹果,一个蓝苹果。红苹果代表疯狂,蓝苹果代表思考
6.7k 声望
407 粉丝
0 条评论
推荐阅读
Java AtomicInteger类使用
这个问题发生的原因是++counter不是一个原子性操作。当要对一个变量进行计算的时候,CPU需要先从内存中将该变量的值读取到高速缓存中,再去计算,计算完毕后再将变量同步到主内存中。这在多线程环境中就会遇到问...

pigLoveRabbit2阅读 2.3k

杨辉三角的5个特性,一个比一个牛皮!
杨辉三角按照杨辉于1261年所编写的《详解九章算法》一书,里面有一张图片,介绍此种算法来自于另外一个数学家贾宪所编写的《释锁算书》一书,但这本书早已失传无从考证。但可以肯定的是这一图形的发现我国不迟于1...

小傅哥3阅读 1.3k

不会数学的程序员,只能走到初级开发工程师!
在我还是初级程序员时,每天也都粘贴着代码和包装着接口。那个阶段并没有意识到数学能在编程中起到什么作用,就算学了数学的部分知识,也没法用到编程中。但后来随着编程越来越久,逐步接手核心代码块开发时候,...

小傅哥3阅读 527

封面图
stackoverflow 提问:“计算两个整数的最小公倍数的最有效方法是什么?”
作者:小傅哥博客:[链接]源码:[链接]沉淀、分享、成长,让自己和他人都能有所收获!😄一、前言嘿,小傅哥怎么突然讲到最大公约数了?这么想你肯定是没有好好阅读前面章节中小傅哥讲到的RSA算法,对于与欧拉结果...

小傅哥3阅读 1.3k

封面图
视频清晰度优化指南
随着移动互联网的深入发展,视频消费场景逐渐变成主流,早期由于手机硬件的限制问题,导致生产出来的视频画质、清晰度存在较大的问题,用户体验不太好,当时的网络也处于4G的发展阶段,网络的限制也无法持续支持...

得物技术2阅读 857

为什么不能用斐波那契散列,做数据库路由算法?
斐波那契数列出现在印度数学中,与梵文韵律有关。在梵语诗歌传统中,人们对列举所有持续时间为 2 单位的长 (L) 音节与 1 单位持续时间的短 (S) 音节并列的模式很感兴趣。用给定的总持续时间计算连续 L 和 S 的不...

小傅哥1阅读 737

封面图
“3D 元宇宙技术”在汽车新零售领域的应用与实践
随着不久前汽车之家新零售项目震撼发布,我们直击用户看车选车痛点首次提出 ABC 新体验模式,以元宇宙科技打造沉浸式交互服务,开放元宇宙能源空间站体验店,为用户打造更“有用”的体验。

之家技术阅读 5.1k

封面图
6.7k 声望
407 粉丝
宣传栏