【递归、回溯、搜索】专题六:记忆化搜索

目录

一*、斐波那契数

二、不同路径

三、最长递增子序列

四、猜数字大小II

五、矩阵中的最长递增路径


一*、斐波那契数

509. 斐波那契数 - 力扣(LeetCode)

有些题记忆化搜索与动规等同,就像这题

解法一:递归

思路如下:

递归,O 2^n,画出递归展开树,我们发现,总会有些重复计算,比如n=5时d(3)就求了很多次,如果n越大,那么重复的计算会非常多。那么我们是不是可以用一个备忘录储存一下计算过的值,如果后边要拿这个数,那么就能直接拿到不用再递归计算了,从而将时间复杂度从指数级别降到线性级别这就是记忆化搜索。

递归代码如下:

cpp 复制代码
class Solution {
public:
    int fib(int n) {
        return dfs(n);
    }
    int dfs(int n)
    {
        if(n==0||n==1)return n;
        return dfs(n-1)+dfs(n-2);
    }
};

解法二:记忆化搜索(避免重复运算,复杂度降低到线性)

记忆化搜索(备忘录递归)(自顶向下)O(n)

备忘录具体如何实现?

我们需要建立可变参数与计算值的映射关系,所以需要哈希表的思想

而我们本题创建一个数组就可以了。

记忆化搜索实现如下:

cpp 复制代码
class Solution {
    //刚需31,不能用={-1}初始化,全局变量这样处理,只会第一个是-1
    int memo[31];
public:
    int fib(int n) {
        memset(memo,-1,sizeof memo);
        return dfs(n);
    }
    int dfs(int n)
    {
        //往备忘录看一眼
        if(memo[n]!=-1)
        {
            return memo[n];
        }

        //和递归终点初始化一样,往备忘录塞数
        if(n==0||n==1)
        {
            memo[n]=n;
            return memo[n];
        }

        //往备忘录塞数
        memo[n]=dfs(n-1)+dfs(n-2);
        return memo[n];
    }
};

解法三:动态规划(自下上顶),常规的动态规划和记忆化搜搜本质差别不大。

dp实现如下:

cpp 复制代码
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];
    }
};

二、不同路径

62. 不同路径 - 力扣(LeetCode)

思路如下:

纯递归会超时,优化为记忆化搜索

注意该题相当于从1,1的位置出发

到达某点的方法,等于到上面那个点方法+到左边那个点方法之和

代码实现如下:

解法一:记忆化搜索

cpp 复制代码
class Solution {
    int memo[101][101];
public:
    int uniquePaths(int m, int n) {
        return dfs(m, n);
    }
    int dfs(int m, int n) {
        if (memo[m][n] != 0) {
            return memo[m][n];
        }
        //上,左边界全部处理为0
        if (m == 0 || n == 0){
            return memo[m][n];
        }
        if (m == 1 && n == 1) {
            memo[m][n] = 1;
            return memo[m][n];
        }
        memo[m][n] = dfs(m - 1, n) + dfs(m, n - 1);
        return memo[m][n];
    }
};

解法二:dp,还是动规代码更好看

cpp 复制代码
class Solution {
    int dp[101][101];
public:
    int uniquePaths(int m, int n) {
        dp[1][1]=1;
        for(int i=1;i<=m;++i)
        {
            for(int j=1;j<=n;++j)
            {
                if(i==1&&j==1)continue;
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m][n];
    }
};

三、最长递增子序列

300. 最长递增子序列 - 力扣(LeetCode)

先看看暴力如何做

思考递归每一层都在干什么

1.暴力就是从每一个元素为起点,后边遇到数字变大的就更新path,递归每层我们都要创建一个path,然后计算这个位置往后的最大的,返回这个path

搞清每次都在干什么就很好写了,不过该代码会超时,因为是n叉树,复杂度近乎于n^n,非常高

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int ret=0;
        for(int i=0;i<nums.size();++i)
        {
            ret=max(ret,dfs(nums,i));
        }
        return ret;
    }
    int dfs(vector<int>& nums,int pos)
    {
        int path=1;
        for(int i=pos+1;i<nums.size();++i)
        {
            if(nums[i]>nums[pos])
            {
                //不能path+=dfs(nums,i) or path=dfs(nums,i)+1
                //因为这里for循环遍历在暴搜这个位置往后的可能最大值,每次需要比较
                path=max(path,dfs(nums,i)+1);
            }
        }
        return path;
    }
};

2.记忆化优化,因为后边遇到更大数字要更新path,如果memo里有这个数字,那么我们只需要加上这个最大数字位置(可能会有重复)往后最大的递增子序列长度就行。创建一个备忘录,dfs函数开头先检查这个位置记录了没有,有返回,没有进入dfs内部,计算完这个位置后在备忘录记录。

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> memo(nums.size());
        int ret=0;
        for(int i=0;i<nums.size();++i)
        {
            ret=max(ret,dfs(nums,i,memo));
        }
        return ret;
    }
    int dfs(vector<int>& nums,int pos,vector<int>& memo)
    {
        //查找备忘录,看一下这个位置有没有记录
        if(memo[pos]!=0)return memo[pos];
        int path=1;
        for(int i=pos+1;i<nums.size();++i)
        {
            if(nums[i]>nums[pos])
            {
                //不能path+=dfs(nums,i) or path=dfs(nums,i)+1
                //因为这里for循环遍历在暴搜这个位置往后的可能最大值,每次需要比较
                path=max(path,dfs(nums,i,memo)+1);
            }
        }
        memo[pos]=path;
        return path;
    }
};

3.dp,动规后续会出一个专栏专门讲解

因为我求一个位置最长子序列,都要知道这个位置往后的情况,那么我们dp就得从后往前更新,因为往后才是最底的那个部分

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //每个都得初始是1,不能是0,不能只初始最后一个位置是1
        //可能有的数往后都是比它小的,那它也不能是0,而是1
        int n=nums.size();
        vector<int> dp(n,1);
        int ret=0;
        for(int i=n-1;i>=0;--i)
        {
            for(int j=i+1;j<n;++j)
            {
                if(nums[j]>nums[i])
                {
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
            //得到i位置往后最大递增子串,判断
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};

四、猜数字大小II

375. 猜数字大小 II - 力扣(LeetCode)

没记错的话,贪心章节也会提及这题

有了第一道猜数字,可能会考虑二分,如果答案是10,那么这个策略来猜,

花销是很大的。所以二分并不是最好的策略,因为我们要的不是最小次数,而是最小保证赢的金额,也就是往大值里找最小值。

问题的核心在于,答案的不确定性,所以为了确保你的钱够付得起所有结果,你得准备的钱一定得是你这个分支,往更大的地方猜测去准备你的本金,这样就算数是更小部分,我们准备的钱也足够我们获胜。

博弈,枚举从1~n当作头,然后往下递归处理子问题

递归每层处理,都往大的地方考虑,求下层所有递归结果的,里的最小值

记忆化搜索+dfs实现

cpp 复制代码
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)
        {
            //x:左子树处理结果 y:右子树处理结果
            int x=dfs(left,head-1);
            int y=dfs(head+1,right);
            //max(x,y)保证赢需要往大了考虑
            ret=min(ret,head+max(x,y));
        }
        //更新该区间的保证赢的最少花费
        memo[left][right]=ret;
        return ret;
    }
};

五、矩阵中的最长递增路径

329. 矩阵中的最长递增路径 - 力扣(LeetCode)

有了前面的铺垫,这道困难题就易如反掌了

cpp 复制代码
class Solution {
    int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1},m,n;
    int memo[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(memo[i][j]!=0)return memo[i][j];
        int path=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])
            {
                path=max(path,dfs(matrix,x,y)+1);
            }
        }
        memo[i][j]=path;
        return path;
    }
};

此专栏完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

感谢收看。

相关推荐
努力学算法的蒟蒻9 小时前
day03(11.1)——leetcode面试经典150
java·算法·leetcode
yugi9878389 小时前
C语言多进程创建和回收
linux·c语言·算法
极客数模9 小时前
【浅析赛题,一等奖水平】思路模型数据相关资料!2025 年“大湾区杯”粤港澳金融数学建模竞赛B 题 稳定币的综合评价与发展分析~
大数据·算法·数学建模·金融·数据挖掘·图论·1024程序员节
深入理解GEE云计算9 小时前
遥感生态指数(RSEI):理论发展、方法论争与实践进展
javascript·人工智能·算法·机器学习
列逍9 小时前
list的模拟实现
数据结构·c++·list
superior tigre9 小时前
(huawei)最小栈
c++·华为·面试
BreezeJuvenile9 小时前
外设模块学习(13)——HW-493激光发射模块(STM32实现)
stm32·单片机·学习·hw-493激光发射模块
云外天ノ☼9 小时前
一、Node.js入门实战指南:从零搭建你的第一个后端
前端·javascript·笔记·node.js
渡我白衣10 小时前
C++:链接的两难 —— ODR中的强与弱符号机制
开发语言·c++·人工智能·深度学习·网络协议·算法·机器学习