【递归、搜索与回溯】记忆化搜索:斐波那契数列,不同路径,最长递增子序列,猜数字游戏II,矩阵中最长递增路径

文章目录

记忆化搜索

什么是记忆化搜索?

以常规递归法解决斐波那契数列为例,n越大,展开的递归函数就越多,并且有大量重复计算.效率极低,并且有栈溢出的风险。如果能记录f(1),f(2)等数值,就可以直接拿来用,省去重复的计算。记忆化搜索就是带记忆的备忘录。

如何实现记忆化搜索?

  1. 实现一个备忘录
  2. 递归每次返回时都把值添加到备忘录中
  3. 每次调用递归前都检查备忘录中是否已保存。
  4. 注意: 备忘录的初始值必须是不可能为结果的值(例如解斐波那契数列初始化为-1),否则不能判断当前的值是被计算出来的还是初始的

记忆化搜索 vs 动态规划

本质都是暴力搜索,都是对暴力解法的优化,把已经计算过的值存起来。

  • 记忆化搜索主要是通过递归的形式写代码
  • 动态规划主要是通过循环的形式写代码
记忆化搜索 动态规划 语句
确认状态表示 dfs函数含义 dp[i]:第i个斐波那契数
dfs函数体 推导状态转移方程 dp[i] = dp[i-1] + dp[i-2]
dfs函数出口 初始化 dp[0] dp[1]=1
填写备忘录顺序 确认填表顺序 从左向右
主函数如何调用dfs 确认返回值 dp[n]

注意:

  1. 只有递归过程中存在大量完全相同的问题才可以通过记忆化搜索来优化。如果没有相同的问题,备忘录就是没有意义的。
  2. 带备忘录的递归,带备忘录的动态规划,记忆化搜索本质上是一回事
  3. 大多数题目都可以通过 暴搜->记忆化搜索->动态规划 来逐步优化。这只是为我们确定状态表示来提供思路,动态规划的解法不是只能通过这种方式来思考。

1. 斐波那契数列(LC509)

斐波那契数列

题目描述

代码实现

  • 记忆化搜索
java 复制代码
class Solution {
    int[] memo = new int[31];
    public int fib(int n) {
        //初始化
        Arrays.fill(memo,-1);
        return dfs(n);
    }
    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];
    }
}
  • 动态规划
java 复制代码
class Solution {
    int[] dp = new int[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. 不同路径(LC62)

不同路径

题目描述

解题思路

  1. 暴搜:
    • dfs(i,j)含义:到(i,j)有多少种不同路径
    • 对于每一个格子都可以从左或者从上到达,所以dfs(i,j) = dfs(i-1,j) + dfs(i,j-1)
    • 边界判断:当计算dfs(i,1)或dfs(1,j)就需要计算dfs(i,0) dfs(0,1)已经越界,所以这就是递归出口。if(i==0 || j ==0)return 0
    • i==1 || j==1时只有一种情况,return 1
  2. 记忆化搜索进行优化:
    1. 新建备忘录:memo[m+1][n+1]; 初始值是0即可
    2. 递归之前查找备忘录
    3. 返回前把结果存在备忘录中

代码实现

  • 记忆化搜索
java 复制代码
class Solution {
    int[][] memo ;
    public int uniquePaths(int m, int n) {
        memo = new int[m+1][n+1];
        return dfs(m,n);
    }
    int dfs(int i,int j){
        if(memo[i][j]!=0)
            return memo[i][j];

        if(i==0 || j==0)
            return 0;
        if(i==1 && j==1){
            memo[i][j] = 1;
            return 1;
        }
        
        memo[i][j] = dfs(i-1,j)+dfs(i,j-1);
        return memo[i][j];
    }
}
  • 动态规划
java 复制代码
class Solution {
    public int uniquePaths(int m, int n) {
    		//初始值都是0,不需要把i==0和j==0的情况手动置为0
        int[][] dp = new int[m+1][n+1];
        
        for(int i = 1;i<=m;i++){
            for(int j= 1;j<=n;j++){
                if(i==1 && j==1)
                    dp[1][1] = 1;
                else
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m][n];
    }
}

3.最长递增子序列(LC300)

最长递增子序列

题目描述

解题思路

  • int dfs(int pos) :传入一个起点,返回起点之后的最长子序列
  • 函数体:从pos+1到结尾开始遍历大于当前值 的元素,找到最大子序列 再 +1就是这个函数找到的最大子序列长度。

代码实现

  • 记忆化搜索
java 复制代码
class Solution {
    int[] memo;
    int[] nums;
    int n;
    public int lengthOfLIS(int[] _nums) {
        nums = _nums;
        n = nums.length;
        memo = new int[n];

        int max = 1;
        for(int i = 0;i<n;i++)
            max = Math.max(dfs(i),max);

        return max;
    }

    int dfs(int pos){
        int max = 1;
        if(memo[pos]!=0)
            return memo[pos];

        for(int i = pos+1;i<n;i++){
            if(nums[i]>nums[pos])
                max  = Math.max(dfs(i)+1,max);
        }
        memo[pos] = max;
        return max;
    }
}
  • 动态规划
java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];

        //初始化为1
        Arrays.fill(dp,1);
        int max = 1;

        //填表顺序:dp[i] 依赖后面的值,从后往前填写
        for(int i = n-1;i>=0;i--){
            for(int j = i+1;j<n;j++){
                if(nums[j]>nums[i])
                    dp[i] = Math.max( dp[j]+1,dp[i]);
            }
            max = Math.max(max,dp[i]);
        }
        return max;
    }
}

4. 猜数字大小II(LC375)

猜数字游戏II

题目描述

解题思路

假设选择i,对两个区间展开,花费就是两个子树的最大值加上i在所有i中要找花费最少的情况。可以借助备忘录记录对于某个区间最少的金额。因为dfs需要传入两个参数表示区间,memo也需要创建二维数组表示两个变量。

代码实现

java 复制代码
class Solution {
    int[][] memo;

    public int getMoneyAmount(int n) {
        memo = new int[n+1][n+1];
        return dfs(1,n);
    }
    int dfs(int l,int r){
        //区间不存在直接返回 区间内只有一个元素说明猜对了,不需要花钱
        if(l>=r)
            return 0;
        if(memo[l][r]!=0)
            return memo[l][r];

        int ret = Integer.MAX_VALUE;
        for(int i = l;i<=r;i++){
            int left = dfs(l,i-1);
            int right = dfs(i+1,r);
            ret = Math.min(Math.max(left,right)+i,ret);
        }
        memo[l][r] = ret;
        return ret;
    }
}

5. 矩阵中最长递增路径(LC329)

矩阵中最长递增路径

题目描述

解题思路

dfs负责找到当前元素为起点的最长递增路径,备忘录记录下来。在二维数组中依次进行深度优先遍历。

代码实现

java 复制代码
class Solution {
    int[][] memo ;
    int[][] matrix;
    int m,n;
    int[] dx = {1,-1,0,0};
    int[] dy = {0,0,1,-1};

    public int longestIncreasingPath(int[][] _matrix) {
        matrix = _matrix;
        m = matrix.length;
        n = matrix[0].length;
        memo = new int[m][n];
        int ret = 1;
        for(int i=0;i<m;i++){
            for(int j = 0;j<n;j++){
                ret = Math.max(ret,dfs(i,j));
            }
        }
        return ret;
    }

    int dfs(int i,int j){
        int ret = 1;
        if(memo[i][j] != 0)
            return memo[i][j];
        for(int k = 0;k<4;k++){
            int x = dx[k]+i;
            int y = dy[k]+j;
            if(x>=0 && x<m && y>=0 && y<n && matrix[x][y]>matrix[i][j])
                ret = Math.max(ret,dfs(x,y)+1);
        }
        memo[i][j] = ret;
        return ret;
    }
}
相关推荐
干啥啥不行,秃头第一名3 小时前
C++与机器学习框架
开发语言·c++·算法
爱吃涮毛肚的肥肥(暂时吃不了版)3 小时前
Leetcode——181.超过经理收入的员工
算法·leetcode·职场和发展
Charlie_lll3 小时前
力扣解题-接雨水
算法·leetcode
仰泳的熊猫3 小时前
题目2580:蓝桥杯2020年第十一届省赛真题-分类计数
数据结构·c++·算法·蓝桥杯
qyzm3 小时前
牛客周赛 Round 136
数据结构·python·算法
用户637818131193 小时前
优先队列的使用
算法
qq_334903153 小时前
C++与人工智能框架
开发语言·c++·算法
夕珩3 小时前
Java 排序算法详解:冒泡排序、选择排序、堆排序
java·算法·排序算法
Magic--3 小时前
从入门到精通:快速排序的核心原理、实现与优化
数据结构·算法·排序算法