目录
今天我们来讲一讲回溯中一个典型的专题 -- 记忆化搜索
今天我们将用 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;
}
};
结语
这篇文章到这里就结束啦!!~( ̄▽ ̄)~*
如果觉得对你有帮助的,可以多多关注一下喔