【算法学习计划】回溯 -- 记忆化搜索

目录

前言

509.斐波那契数

62.不同路径

300.最长递增子序列

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

结语


今天我们来讲一讲回溯中一个典型的专题 -- 记忆化搜索

今天我们将用 4 道题目来带各位深度了解一下这个算法

(下文中的标题都是leedcode对应题目的链接)

前言

我们的记忆化搜索,是一种相当厉害的思维,但是并不是所有的dfs都能改成记忆化搜索,我们只有在分析问题的时候,发现能转化,我们才能转

但是如果能转化的话,代码写起来是非常简单的

举个栗子:
比如我们的斐波那契数列,假设我们现在要找的是第 7 个,那么要知道第 7 个,就要知道第 6 个和第 5 个,而要知道第 6 个,就要知道第 5 个和第 4 个

从这里开始,我们就会发现,5 的情况其实重复了,再往后遍历的话,4、3、2的情况全部都会重复计算,如果要找的不是第 7 个,而是第 10000 个,或者更多呢?包超时的

所有我们可以那一个数组 memo 记录一下这些节点的值

也就是当我们第一次将 5 这个位置算出来的时候,我们就将这个位置的值放进 memo 数组里面,当我们下次再遍历到这个位置的时候,直接在 memo 数组里面找即可

或者不是这个模型,是要我们找路径,如果我们当前这个路径能有一个值记录着,并且我们在递归的时候会多次访问这个地方的值的话,我们就可以用记忆化搜索

这些知识都会在我们下面的题目中有所体现

509.斐波那契数

这道题的题解在前言中被当作例子,代码如下:

class Solution {
public:
    int mem[32];

    int dfs(int n)
    {
        if(n <= 0) return 0;
        if(mem[n] != -1) return mem[n];
        mem[n] = dfs(n-1) + dfs(n-2);
        return mem[n];
    }

    int fib(int n) 
    {
        if(n < 2) return n;
        memset(mem, -1, sizeof mem);
        mem[0] = 0, mem[1] = 1;
        return dfs(n);
    }
};

62.不同路径

这一道题目如果正常递归的话,我们要去最右下角,那么我们就应该从最右下角进行递归,因为每一个位置都是从上或者从左来的

但是我们这里,做过的路径会出现相互重叠的情况,哪怕只是一部分,所以我们也可以使用记忆化搜索

至于递归的逻辑,就是上和左:

vis[m-1][n] = dfs(m-1, n);

vis[m][n-1] = dfs(m, n-1);

代码如下:

class Solution {
public:
    int vis[101][101] = {0};
    int dfs(int m, int n)
    {
        if(vis[m][n]) return vis[m][n];
        if(m == 0 || n == 0) return 0;

        vis[m-1][n] = dfs(m-1, n);
        vis[m][n-1] = dfs(m, n-1);
        return vis[m-1][n] + vis[m][n-1];
    }
    int uniquePaths(int m, int n) 
    {
        vis[1][1] = 1;

        return dfs(m, n);    
    }
};

300.最长递增子序列

这一题的话,我们如果纯dfs,那么就是固定一个位置,然后从这个位置开始,往后递归,只有比当前的数要大才能进入下一个递归逻辑,最后返回从这个位置开始的最长递增子序列的长度

但是我们在中途的时候,我们遇到的每一个位置都能算是一个新的起点,所以从这个起点开始出发的话,那么必然是会有一个最长的长度的

这时我们就可以开始往里面加记忆化搜索的逻辑了,我们每遍历到一个位置,就将从这儿开始的最长递增子序列的长度给放进数组memo里面,当我下次再遍历到这个位置的时候,我们就可以直接从那个数组里面找

代码如下:

class Solution {
public:
    int vis[2501] = {0};
    int dfs(vector<int>& nums, int pos)
    {
        if(vis[pos]) return vis[pos];
        int ret = 1;
        for(int i = pos + 1; i < nums.size(); i++)
        {
            if(nums[i] <= nums[pos]) continue;
            ret = max(ret, dfs(nums, i) + 1);
        }    
        vis[pos] = ret;
        return ret;
    }

    int lengthOfLIS(vector<int>& nums) 
    {
        int ret = 1;
        for(int i = 0; i < nums.size(); i++)
        {
            ret = max(ret, dfs(nums, i));
        }    
        return ret;
    }
};

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

其实,说难也不难,因为这道题目的本质就是,从每一个起点开始,不断往四面八方递归,只有严格大于才能进行下一步的递归操作,然后每一次把四面八方都遍历完了,我们就更新一下最长的值即可

但是由于我们每一个位置都能视为一个起点,那么我们从每一个起点开始的最长长度也是一个定值,所以我们就能够使用记忆化搜索的逻辑,也就是,创建一个数组,然后每遍历到一个位置就将这个结果放进数组里面

如果新遍历到一个位置的话,那么我们就看看数组里面有没有这个位置的值,有的话,直接返回这个位置的值就好了

代码如下:

class Solution {
public:
    int n, m;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    bool check[201][201];
    int vis[201][201];

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

    int longestIncreasingPath(vector<vector<int>>& matrix) 
    {
        memset(vis, 0, sizeof vis);
        int ret = 1;
        n = matrix.size(), m = matrix[0].size();
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
            {
                memset(check, false, sizeof(check));
                ret = max(ret, dfs(matrix, i, j));
            }
        return ret;
    }
};

结语

这篇文章到这里就结束啦!!~( ̄▽ ̄)~*

如果觉得对你有帮助的,可以多多关注一下喔