首页 > 编程语言 >【算法】记忆化搜索

【算法】记忆化搜索

时间:2024-10-08 11:54:04浏览次数:3  
标签:return int dfs mem 算法 记忆 ret 搜索

【ps】本篇有 5 道 leetcode OJ。 

目录

一、算法简介

二、相关例题

1)斐波那契数

.1- 题目解析

.2- 代码编写

2)不同路径

.1- 题目解析

.2- 代码编写

3)最长递增子序列

.1- 题目解析

.2- 代码编写

4)猜数字大小 II

.1- 题目解析

.2- 代码编写

5)矩阵中的最长递增路径

.1- 题目解析

.2- 代码编写


一、算法简介

        记忆化搜索是一种典型的空间换时间的思想,可以看成带备忘录的爆搜递归。

        搜索的低效在于没有能够很好地处理重叠子问题。在搜索过程中,会有很多重复计算,如果我们能记录一些状态的答案,就可以减少重复搜索量。动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。

        根据记忆化搜索的思想,它是解决重复计算,而不是重复生成,也就是说,这些搜索必须是在搜索扩展路径的过程中分步计算的题目,也就是“搜索答案与路径相关″的题目,而不能是搜索一个路径之后才能进行计算的题目,必须要分步计算,并且搜索过程中,一个搜索结果必须可以建立在同类型问题的结果上,也就是类似于动态规划解决的那种。

        记忆化搜索的典型应用场景是可能经过不同路径转移到相同状态的dfs问题。更明确地说,当我们需要在有层次结构的图(不是树,即当前层的不同节点可能转移到下一层的相同节点)中自上而下地进行 DFS 搜索时,我们一般可以通过记忆化搜索的技巧降低时间复杂度。

二、相关例题

1)斐波那契数

509. 斐波那契数

.1- 题目解析

        斐波那契数列可以用很多种方法实现,例如循环、递推、递归、动态规划(dp)、记忆化搜索、矩阵快速幂等,能运用很多知识也十分锻炼代码能力。

        所谓记忆化搜索,就是在进行递归时,存在完全相同的子问题,此时可以把该子问题的结果存在一个“备忘录”中(一般为一个全局的哈希表),避免后续重复求解该子问题,进而增加代码的运行效率。

         由递归和记忆化搜索,可以引申出另一个算法——动态规划(dp)。

 

.2- 代码编写

//循环方式
class Solution {
public:
    int fib(int n) {
        if (n < 2)
            return n;
        int fib1 = 0, fib2 = 0, ret = 1;
        for (int i = 2; i <= n; ++i)
        {
            fib1 = fib2;
            fib2 = ret;
            ret = fib1 + fib2;
        }
        return ret;
    }
};
//dfs方式(递归)
class Solution {
public:
    int fib(int n) {
        return dfs(n);
    }
 
    int dfs(int n)
    {
        if(n <= 1)
            return n;
        return dfs(n - 1) + dfs(n - 2);
    }
};
//记忆化搜索方式
class Solution {
    int mem[31];//备忘录,本质是一个哈希表,存的是可变参数和递归返回值之间的映射
public:
    int fib(int n) {
        memset(mem,-1,sizeof(mem)); //初始化备忘录
        return dfs(n);
    }
    int dfs(int n)
    {
        if(mem[n]!=-1) //查找一下备忘录,若已进入过该状态,就不再进入了,直接向上返回(相当于剪枝)
            return mem[n];
            
        if(n==0 || n==1)
        {
            mem[n]=n; //填备忘录
            return n;
        }

        mem[n]=dfs(n-1)+dfs(n-2);//填备忘录
        return mem[n];
    }
};
//dp方式
class Solution {
    int dp[31];
public:
    int fib(int n) {
        dp[0]=0,dp[1]=1;//初始化
        for(int i=2;i<=n;i++)//填表顺序
            dp[i]=dp[i-1]+dp[i-2];//状态转移方程
        return dp[n];//返回值
    }
};

 

2)不同路径

62. 不同路径

.1- 题目解析

        对于网格中的某一个格子,它左侧的格子和上方的格子均只需一步就能到达它,也就是说,对于左侧的格子,有多少种到达它左侧格子的方式就有多少到达它的方式,同理,对于上方的格子,有多少种到达它上方格子的方式也就有多少到达它的方式,那么,到达某一个格子有多少种方式,其实就是到达它左侧的格子和上方的格子的方式之和。

.2- 代码编写

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> mem(m+1,vector<int>(n+1));//防越界,合理地映射下标
        return dfs(m,n,mem);
    }
    int dfs(int i,int j,vector<vector<int>>& mem)
    {
        if(mem[i][j]!=0)return mem[i][j];//查备忘录剪枝

        if(i==0 || j==0)return 0;//边界上的格子
        if(i==1 && j==1)         //紧邻边界的开始出发的格子
        {
            mem[i][j]=1;
            return 1;
        }

        mem[i][j]=dfs(i-1,j,mem)+dfs(i,j-1,mem);//填备忘录
        return mem[i][j];
    }
};

3)最长递增子序列

300. 最长递增子序列

.1- 题目解析

        我们可以以一个元素作为起点,去 DFS 它之后的元素,如果它之后的元素能够和它形成递增序列,就统计这个序列的长度。

        显然在这个 DFS 的过程中会出现重复的子问题,那么我们就可以通过记忆化搜索的方式来保存重复子问题的结果。

.2- 代码编写

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> mem(n);
        int ret=0;
        for(int i=0;i<nums.size();i++)//将每一个元素作为起点分别DFS
            ret=max(ret,dfs(i,nums,mem));
        return ret;
    }
    int dfs(int pos,vector<int>& nums,vector<int>& mem)
    {
        if(mem[pos]!=0)return mem[pos];

        int ret=1;//序列中至少有一个元素
        for(int i=pos+1;i<nums.size();i++)
        {
            if(nums[i]>nums[pos])
            {
                ret=max(ret,dfs(i,nums,mem)+1);
            }
        }
        mem[pos]=ret;
        return mem[pos];
    }
};

4)猜数字大小 II

375. 猜数字大小 II

.1- 题目解析

        1 到 n 之间的数可以构成一棵棵二叉搜索树,题目要求返回能够获胜的最小现金数,其实是要求某一数字作为根节点时,其左右子树中的最大值(返回左右子树的最大值,才能保证所有叶子节点是可选的,才不会输游戏),然后求出这些数字作为根节点时所得到的值中的最小值。

        显然在 DFS 一棵棵二叉搜索树时,存在重复的情况,我们可以用一个备忘录保存它们的结果来进行剪枝。

.2- 代码编写

class Solution {
    int memo[201][201];

public:
    int getMoneyAmount(int n) { return dfs(1, n); }
    int dfs(int left, int right) {
        if (left >= right)//递归出口
            return 0;
        if (memo[left][right] != 0)//查备忘录剪枝
            return memo[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(ret, head + max(x, y));
        }
        memo[left][right] = ret;//填备忘录
        return ret;
    }
};

5)矩阵中的最长递增路径

329. 矩阵中的最长递增路径

.1- 题目解析

        遍历所有的方格,分别以它们为起点进行 DFS,找出最长递增路径即可。

        显然在 DFS 所有路径时,存在重复的情况,我们可以用一个备忘录保存它们的结果来进行剪枝。

.2- 代码编写

class Solution {
    int m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int memo[201][201];

public:
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        int ret = 0;
        m = matrix.size(), n = matrix[0].size();
        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 (memo[i][j] != 0)
            return memo[i][j];
        int ret = 1;
        for (int k = 0; k < 4; k++) {
            int x = i + dx[k], 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);
            }
        }
        memo[i][j] = ret;
        return ret;
    }
};

标签:return,int,dfs,mem,算法,记忆,ret,搜索
From: https://blog.csdn.net/waluolandao/article/details/142449763

相关文章

  • 代码随想录算法训练营第八天| 151.翻转字符串里的单词
    151.翻转字符串里的单词文章链接:https://programmercarl.com/0151.翻转字符串里的单词.html#思路视频链接:https://www.bilibili.com/video/BV1uT41177fX/?vd_source=6cb513d59bf1f73f86d4225e9803d47b题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/classS......
  • 面试淘天集团大模型算法工程师, 开心到飞起!!!
    应聘岗位:淘天集团-大模型算法工程师面试轮数:整体面试感觉:1.自我介绍在自我介绍环节,我清晰地阐述了个人基本信息、教育背景、工作经历和技能特长,展示了自信和沟通能力。2.技术问题2.1在大模型微调过程中,评价指标是怎样设定的?在大模型微调过程中,评价指标的设定是......
  • 算法【从递归入手二维动态规划】
    尝试函数有1个可变参数可以完全决定返回值,进而可以改出1维动态规划表的实现。同理,尝试函数有2个可变参数可以完全决定返回值,那么就可以改出2维动态规划的实现。一维、二维、三维甚至多维动态规划问题,大体过程都是:1.写出尝试递归。2.记忆化搜索(从顶到底的动态规划)。3.严......
  • 代码随想录算法训练营day8|344.反转字符串 ● 541. 反转字符串II ● 卡码网:54.替换数
    学习资料:https://programmercarl.com/0344.反转字符串.html#算法公开课在python中字符串不可变,所以要增加空间lst=list(str)344.反转字符串(reverse库函数的基本代码)点击查看代码classSolution(object):defreverseString(self,s):""":types:List......
  • 代码随想录算法训练营 | 动态规划,509. 斐波那契数,70. 爬楼梯, 746. 使用最小花费爬楼梯
    动态规划:1.动态规划中每一个状态一定是由上一个状态推导出来的2.确定dp数组(dptable)以及下标的含义,确定递推公式dp,数组如何初始化,确定遍历顺序,举例推导dp数组;3.Debug:dp数组打印509.斐波那契数题目链接:509.斐波那契数文档讲解︰代码随想录(programmercarl.com)视频讲解︰斐波那......
  • 数据结构课程设计大项目————迷宫问题(邻接矩阵,prim生成算法,DFS寻路,BFS寻路,路径回溯
    一.前言迷宫问题是数据结构中最值得实践的大项目之一,本文主要讲解思路,提供的代码大部分都有注释(没有的就是太多了懒得写了QAQ)。为了更好的表现效果,该程序使用了easyx可视化,easyx简单易学(大概一天到两天就可以学会),上手简单。该程序由c语言实现,本人水平有限程序可优化空间很大。......
  • KNN算法
    KNN算法一KNN算法介绍二KNN算法API2.1KNeighborsClassifier分类算法2.2KNeighborsRegressor回归算法三两个经典案例3.1鸢尾花案例3.2手写数字识别案例一KNN算法介绍K-近邻算法(KNearestNeighbor,简称KNN).比如根据你的“邻居”来推断出你的类别.KNN算法......
  • React 中的 diff 算法
    Reactdiff为什么使用虚拟DOM?浏览器在处理DOM的时候会很慢,处理JavaScript会很快,页面复杂的时候,频繁操作DOM会有很大的性能开销(每次数据变化都会引起整个DOM树的重绘和重排)。为了避免频繁操作DOM,React会维护两个虚拟DOM,如果有数据更新,会借此计算出所有修改的状态......
  • Day 24 贪心算法part02| LeetCode 122.买卖股票的最佳时机II,55.跳跃游戏,45.跳跃游戏II
    122.买卖股票的最佳时机II局部最优:每天的正利润全局最优:每天的正利润之和121.买卖股票的最佳时机classSolution{publicintmaxProfit(int[]prices){intres=0;for(inti=1;i<prices.length;i++){i......
  • 代码随想录算法训练营day7|● 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ●
    学习资料:https://programmercarl.com/0015.三数之和.html#其他语言版本学习记录:454.四数相加(hash_dict,前两个数一组遍历a+b,后两个数一组遍历找0-(a+b))点击查看代码classSolution:deffourSumCount(self,nums1:List[int],nums2:List[int],nums3:List[int],nums4:......