【记忆化搜索】斐波那契数

文章目录

题目链接:509. 斐波那契数


题目解析

记忆化搜索 是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式(是一种常见的动态规划实现方式)。

可以通过记忆化搜索来优化 DFS(剪枝)以提高算法效率。

文章将通过斐波那契数这道题目来呈现记忆化搜索具体如何优化。

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。

也就是:

  • F(0) = 0,F(1) = 1
  • F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,我们需要返回计算后的 F(n) 。


DFS:深度优先搜索

算法原理

很经典的递归操作,通过编写 dfs 函数递归计算第 n 个斐波那契数。

dfs 函数

dfs 的任务是:根据斐波那契数的定义返回第 n 个斐波那契数(若 n 为 0 或 1,则返回 n,否则返回 F(n-1) + F(n-2)),参数是 n,返回值为 int。

java 复制代码
int dfs (int n);

代码实现

java 复制代码
class Solution {
    public int fib(int n) {
        // 搜索
        return dfs(n);
    }

    public int dfs (int n) {
        // 递归出口
        if (n == 0 || n == 1) {
            return n;
        }

        return dfs(n-1) + dfs(n-2);
    }
}

记忆化搜索

DFS 方法写的代码虽然简洁,但是实际算法的计算过程并不快,相反,存在很多重复计算的步骤,导致整体的时间复杂度达到 O(2n)。

给出一个例子:n = 5,求 F(5)

画出递归展开图如下:
#mermaid-svg-c3bn2m3W9LHnVzW7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c3bn2m3W9LHnVzW7 .error-icon{fill:#552222;}#mermaid-svg-c3bn2m3W9LHnVzW7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c3bn2m3W9LHnVzW7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .marker.cross{stroke:#333333;}#mermaid-svg-c3bn2m3W9LHnVzW7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c3bn2m3W9LHnVzW7 p{margin:0;}#mermaid-svg-c3bn2m3W9LHnVzW7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster-label text{fill:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster-label span{color:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster-label span p{background-color:transparent;}#mermaid-svg-c3bn2m3W9LHnVzW7 .label text,#mermaid-svg-c3bn2m3W9LHnVzW7 span{fill:#333;color:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node rect,#mermaid-svg-c3bn2m3W9LHnVzW7 .node circle,#mermaid-svg-c3bn2m3W9LHnVzW7 .node ellipse,#mermaid-svg-c3bn2m3W9LHnVzW7 .node polygon,#mermaid-svg-c3bn2m3W9LHnVzW7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .rough-node .label text,#mermaid-svg-c3bn2m3W9LHnVzW7 .node .label text,#mermaid-svg-c3bn2m3W9LHnVzW7 .image-shape .label,#mermaid-svg-c3bn2m3W9LHnVzW7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .rough-node .label,#mermaid-svg-c3bn2m3W9LHnVzW7 .node .label,#mermaid-svg-c3bn2m3W9LHnVzW7 .image-shape .label,#mermaid-svg-c3bn2m3W9LHnVzW7 .icon-shape .label{text-align:center;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node.clickable{cursor:pointer;}#mermaid-svg-c3bn2m3W9LHnVzW7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .arrowheadPath{fill:#333333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c3bn2m3W9LHnVzW7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c3bn2m3W9LHnVzW7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c3bn2m3W9LHnVzW7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster text{fill:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 .cluster span{color:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c3bn2m3W9LHnVzW7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c3bn2m3W9LHnVzW7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-c3bn2m3W9LHnVzW7 .icon-shape,#mermaid-svg-c3bn2m3W9LHnVzW7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c3bn2m3W9LHnVzW7 .icon-shape p,#mermaid-svg-c3bn2m3W9LHnVzW7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c3bn2m3W9LHnVzW7 .icon-shape .label rect,#mermaid-svg-c3bn2m3W9LHnVzW7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c3bn2m3W9LHnVzW7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c3bn2m3W9LHnVzW7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node>*{fill:#FFF4CC!important;stroke:#D4B884!important;stroke-width:1.5px!important;color:#000!important;rx:5!important;ry:5!important;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node span{fill:#FFF4CC!important;stroke:#D4B884!important;stroke-width:1.5px!important;color:#000!important;rx:5!important;ry:5!important;}#mermaid-svg-c3bn2m3W9LHnVzW7 .node tspan{fill:#000!important;} F(5)
F(4)
F(3)
F(3)
F(2)
F(2)
F(1)
F(2)
F(1)
F(1)
F(0)
F(1)
F(0)
F(1)
F(0)

可以发现,就算 n = 5 这么简单的计算,画出来的二叉树的高度也达到了 5,而且可以发现许多重复计算的部分:F(3) 和 F(2)。
#mermaid-svg-tF1mo0b1HCPdcIm1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tF1mo0b1HCPdcIm1 .error-icon{fill:#552222;}#mermaid-svg-tF1mo0b1HCPdcIm1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tF1mo0b1HCPdcIm1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .marker.cross{stroke:#333333;}#mermaid-svg-tF1mo0b1HCPdcIm1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tF1mo0b1HCPdcIm1 p{margin:0;}#mermaid-svg-tF1mo0b1HCPdcIm1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster-label text{fill:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster-label span{color:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster-label span p{background-color:transparent;}#mermaid-svg-tF1mo0b1HCPdcIm1 .label text,#mermaid-svg-tF1mo0b1HCPdcIm1 span{fill:#333;color:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .node rect,#mermaid-svg-tF1mo0b1HCPdcIm1 .node circle,#mermaid-svg-tF1mo0b1HCPdcIm1 .node ellipse,#mermaid-svg-tF1mo0b1HCPdcIm1 .node polygon,#mermaid-svg-tF1mo0b1HCPdcIm1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .rough-node .label text,#mermaid-svg-tF1mo0b1HCPdcIm1 .node .label text,#mermaid-svg-tF1mo0b1HCPdcIm1 .image-shape .label,#mermaid-svg-tF1mo0b1HCPdcIm1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tF1mo0b1HCPdcIm1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .rough-node .label,#mermaid-svg-tF1mo0b1HCPdcIm1 .node .label,#mermaid-svg-tF1mo0b1HCPdcIm1 .image-shape .label,#mermaid-svg-tF1mo0b1HCPdcIm1 .icon-shape .label{text-align:center;}#mermaid-svg-tF1mo0b1HCPdcIm1 .node.clickable{cursor:pointer;}#mermaid-svg-tF1mo0b1HCPdcIm1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .arrowheadPath{fill:#333333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tF1mo0b1HCPdcIm1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tF1mo0b1HCPdcIm1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tF1mo0b1HCPdcIm1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster text{fill:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 .cluster span{color:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tF1mo0b1HCPdcIm1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tF1mo0b1HCPdcIm1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tF1mo0b1HCPdcIm1 .icon-shape,#mermaid-svg-tF1mo0b1HCPdcIm1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tF1mo0b1HCPdcIm1 .icon-shape p,#mermaid-svg-tF1mo0b1HCPdcIm1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tF1mo0b1HCPdcIm1 .icon-shape .label rect,#mermaid-svg-tF1mo0b1HCPdcIm1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tF1mo0b1HCPdcIm1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tF1mo0b1HCPdcIm1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tF1mo0b1HCPdcIm1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-tF1mo0b1HCPdcIm1 .yellow>*{fill:#FFF4CC!important;stroke:#D4B884!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .yellow span{fill:#FFF4CC!important;stroke:#D4B884!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .yellow tspan{fill:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .red>*{fill:#FCE4E4!important;stroke:#E6B8B8!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .red span{fill:#FCE4E4!important;stroke:#E6B8B8!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .red tspan{fill:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .purple>*{fill:#EAE6F8!important;stroke:#B3A2D9!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .purple span{fill:#EAE6F8!important;stroke:#B3A2D9!important;stroke-width:1.5px!important;color:#000!important;}#mermaid-svg-tF1mo0b1HCPdcIm1 .purple tspan{fill:#000!important;} F(5)
F(4)
F(3)
F(3)
F(2)
F(2)
F(1)
F(1)
F(0)
F(1)
F(0)
F(2)
F(1)
F(1)
F(0)

如果将之前已经展开那部分的返回值存入一个 备忘录,在下一次展开之前先搜索备忘录,就可以避免再次展开递归直接返回之前计算过的结果,从而剪枝、提高效率,算法的时间复杂度为 O(n)。

这就是 记忆化搜索 ,可以将其理解为 "带着记忆搜索" ,而这里的记忆主要依靠 "备忘录" (可以是哈希表、数组,是能够映射可变参数和返回值的一种数据结构)。

记忆数组

为了递归方便,将备忘录 memo 定义成全局的。

该备忘录在本题中可以是一个数组 ,其下标 n 表示第 n 个斐波那契数,而下标对应的值则是具体的第 n 个斐波那契数的值 F(n)。

java 复制代码
int[] memo;

在 DFS 开始之前,首先要对这个记忆数组进行初始化,将其中的值全部初始化为 -1,表示还没有存入搜索展开的值。

如何使用这个记忆数组呢?

  1. 在每一次递归展开之前,先查看备忘录,若当前可变参数 n 的值已经存在,直接返回该值
  2. 在每一次递归展开之后、返回上层之前,先将当前展开后的可变参数对应的值存入备忘录

代码实现

java 复制代码
class Solution {
    int[] memo = new int[31]; // 备忘录

    public int fib(int n) {
        // 初始化备忘录
        Arrays.fill(memo, -1);
        // 搜索
        return dfs(n);
    }

    public int dfs (int n) {
        // 在展开之前查看备忘录
        if (memo[n] != -1) {
            return memo[n];
        }

        // 递归出口
        if (n == 0 || n == 1) {
            memo[n] = n;       // 返回之前先存入备忘录
            return n;
        }

        // 展开并返回
        memo[n] = dfs(n-1) + dfs(n-2); // 返回之前先存入备忘录
        return memo[n];
    }
}

文章到这里就告一段落啦,若有错误请尽管指出~

完

相关推荐
Cc.Y15 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化
shuoshuohaohao20 小时前
MyBatis 小知识点
java·spring·mybatis
海上小飞龙20 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂20 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i20 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
谢亮_vipxieliang21 小时前
泛型擦除、通配符与 PECS 一次讲透
java·面试
八解毒剂21 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
乐迪信息21 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉
打工仔折腾 AI1 天前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
ZealSinger1 天前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox