算法—记忆化搜索

记忆化搜索其实就是带着"备忘录"的递归。通过"备忘录"实现递归的剪枝,提高递归效率。

斐波那契数

思路: 这里通过记忆化搜索(自顶向下的动态规划) 思想解决斐波那契数列问题,核心思路为:

首先,为了避免传统递归求解斐波那契数列时大量重复计算子问题(比如计算fib(5)时会重复计算fib(3)fib(2)等),代码引入一个长度为n+1的数组memory作为记忆容器,初始值设为-1,用于存储已经计算过的斐波那契数值,标记 "未计算" 状态。

然后,定义递归函数dfs实现核心计算逻辑:递归过程中先检查memory[n],若不为-1,说明该位置的斐波那契值已计算过,直接返回该值,避免重复递归;若为-1,则先处理边界条件(n=0n=1时,斐波那契值为自身),将结果存入memory后返回;对于其他情况,递归计算n-1n-2的斐波那契值,求和后存入memory[n],再返回该结果。

整个过程通过 "先查记忆容器,再递归计算,最后存储结果" 的方式,将原本指数级时间复杂度的纯递归优化为线性时间复杂度,既保留了递归的直观性,又通过记忆化消除了重复计算。

算法:

cpp 复制代码
class Solution {
public:
    int fib(int n) {
        vector<int> memory(n + 1, -1);
        return dfs(memory, n);
    }

    int dfs(vector<int>& memory, int n)
    {
        if(memory[n] != -1)
            return memory[n];

        if(n == 0 || n == 1)
        {
            memory[n] = n;
            return n;
        }

        memory[n] = dfs(memory, n - 1) + dfs(memory, n - 2);
        return memory[n];
    }
};

不同路径

**思路:**这道题采用记忆化搜索(带备忘录的深度优先搜索)来解决。

首先明确从网格右下角 (m,n) 到左上角 (1,1) 的路径数等价于从 (1,1) 到 (m,n) 的路径数,且到达 (m,n) 的路径数等于到达其上方位置 (m-1,n) 的路径数加上到达其左侧位置 (m,n-1) 的路径数(因为只能向右 / 向下走,反向则为向上 / 向左);

为避免递归过程中重复计算相同位置的路径数,引入二维备忘录memory存储已计算出的位置路径数,递归时先检查备忘录,若已有值则直接返回,无需重复计算;

同时设定边界条件:当 m 或 n 为 0 时(超出网格范围)路径数为 0,当 m 和 n 都为 1 时(起点)路径数为 1,最终通过递归累加求出从 (1,1) 到 (m,n) 的所有唯一路径数。

代码:

cpp 复制代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> memory(m + 1, vector<int>(n + 1, 0)); //记忆化搜索中的备忘录
        return dfs(m, n, memory);
    }

    int dfs(int m, int n, vector<vector<int>>& memory)
    {
        if(memory[m][n] != 0)
            return memory[m][n];

        if(m == 0 || n == 0)
            return 0;
        if(m == 1 && n == 1)
        {
            memory[m][n] = 1;
            return 1;
        }

        memory[m][n] = dfs(m - 1, n, memory) + dfs(m, n - 1, memory);
        return memory[m][n];
    }
};

最长递增子序列

**思路:**这道题用记忆化搜索来解决。

定义dfs(pos)表示以数组中第pos个元素为起点的最长递增子序列长度,为避免递归中重复计算相同位置的结果,引入一维备忘录memory存储每个位置的计算结果;

递归时先检查备忘录,若memory[pos]不为 0 则直接返回该值,否则初始化当前最长长度为 1(仅包含自身),然后遍历pos之后的所有位置i,若nums[i] > nums[pos],说明可以将nums[i]接在nums[pos]后形成更长的递增子序列,此时递归计算dfs(i)并加 1,与当前最长长度取最大值;

计算完成后将结果存入memory[pos],最后遍历数组所有位置作为起点调用dfs,取其中的最大值即为整个数组的最长递增子序列长度。

代码:

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> memory(n, 0);
        int ret = 0;
        for(int i = 0; i < n; i++)
        {
            ret = max(ret, dfs(i, nums, memory));
        }

        return ret;
    }

    int dfs(int pos, vector<int>& nums, vector<int>& memory)
    {
        if(memory[pos] != 0)
            return memory[pos];

        int ret = 1;
        for(int i = pos + 1; i < nums.size(); i++)
        {
            if(nums[i] > nums[pos])
            {
                ret = max(ret, dfs(i, nums, memory) + 1);
            }
        }

        memory[pos] = ret;
        return ret;
    }
};

猜数字大小 II

思路: 猜数字时,若选择数字 k,则需付出 k 的成本,且必然面临两种结果 ------ 猜大了(需在[1, k-1]继续猜)或猜小了(需在[k+1, n]继续猜);由于要 "确保猜对",必须按最坏情况(即成本更高的那个子区间)计算总成本,而我们的目标是在所有可选的 k 中,找到能让这个 "最坏成本" 最小的那个值。

整个区间[1, n]的最小最坏成本,可拆解为子区间[1, k-1][k+1, n]的最小最坏成本加上 k 值,k 可以是[1, n] 区间中的任意一个值,所以需要枚举 k 为每一个值的情况,然后找到所有确保胜利的情况中最小的成本。因为无论选哪个k,后续的子问题都需要用同样的逻辑求解,因此不同的父问题可能会重复求解同一个子区间(比如计算[1,5]时会用到[2,4],计算[2,5]时也会用到[2,4]),如果每次都重新计算子区间的解,会产生大量重复运算;因此还需要通过记忆化方式存储已计算过的区间结果,优化效率。

代码:

cpp 复制代码
class Solution {
    int memory[201][201];
public:
    int getMoneyAmount(int n) {
        return dfs(1, n);
    }

    int dfs(int left, int right)
    {
        if(left >= right)
            return 0;

        if(memory[left][right] != 0)
            return memory[left][right];

        int ret = INT_MAX;
        for(int head = left; head <= right; head++)
        {
            int x = dfs(left, head - 1);
            int y = dfs(head + 1, right);
            ret = min(head + max(x, y), ret);
        }
        memory[left][right] = ret;
        return ret;
    }
};

矩阵中的最长递增路径

思路: 要找到矩阵中最长的递增路径,首先定义递归函数 dfs(matrix,i, j) 表示从 matrix 矩阵中 (i,j) 位置出发能找到的最长递增路径长度;为避免重复计算,用二维数组 memory 缓存每个位置的计算结果,若该位置已缓存则直接返回;初始时每个位置自身构成长度为 1 的路径,随后遍历该位置上下左右四个方向的相邻位置,若相邻位置坐标合法且数值大于当前位置,则递归计算该相邻位置的最长递增路径长度并加 1,取所有合法方向中的最大值作为当前位置的最长路径长度,存入缓存后返回;最后遍历矩阵中每一个位置,调用 dfs 函数并记录所有结果的最大值,即为整个矩阵的最长递增路径长度。

代码:

cpp 复制代码
class Solution {
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
    int memory[201][201];
public:
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        m = matrix.size();
        n = matrix[0].size();
        int ret = 0;
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                ret = max(ret, dfs(matrix, i, j));
            }
        }

        return ret;
    }

    int dfs(vector<vector<int>>& matrix, int i, int j)
    {
        if(memory[i][j] != 0)
            return memory[i][j];

        int ret = 1;
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k];
            int y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] > matrix[i][j])
            {
                ret = max(ret, dfs(matrix, x, y) + 1);
            }
        }

        memory[i][j] = ret;
        return memory[i][j];
    }
};
相关推荐
倾心琴心1 小时前
【agent辅助pcb routing coding学习】实践7 length matching 算法学习
学习·算法·agent·pcb·routing
y = xⁿ1 小时前
【LeetCodehot100】T114:二叉树展开为链表 T105:从前序与中序遍历构造二叉树
java·算法·链表
灰色小旋风1 小时前
力扣20有效的括号(C++)
c++·算法·leetcode·职场和发展
逆境不可逃1 小时前
LeetCode 热题 100 之 160. 相交链表 206. 反转链表 234. 回文链表 141. 环形链表 142. 环形链表 II
算法·leetcode·链表
weiabc1 小时前
今日C/C++学习笔记20260223
c语言·c++·学习
CoovallyAIHub1 小时前
AAAI 2026 | 华中科大联合清华等提出Anomagic:跨模态提示零样本异常生成+万级AnomVerse数据集(附代码)
深度学习·算法·计算机视觉
南 阳2 小时前
Python从入门到精通day56
开发语言·python
悲伤小伞2 小时前
9-MySQL_索引
linux·数据库·c++·mysql·centos
npupengsir2 小时前
nano vllm代码详解
人工智能·算法·vllm
优化控制仿真模型2 小时前
【2026年最新】英语四级历年真题及答案解析PDF电子版(2015-2025年12月)
经验分享·pdf