前言
在之前 DFS 遍历过程中,通过标记某个位置是否遍历过,从而避免每个位置进行多次 DFS 遍历;但是有些情况下,还需要获取从该位置进行 DFS 遍历的结果。
而记忆化搜索,简单来说就是记录这些 DFS 遍历的结果,在下次从某个位置进行 DFS 遍历时,如果该位置已经进行过 DFS 遍历,直接获取结果即可。
一、斐波那契数
题目解析

给定一个数 n,求第 n 个斐波那契数。
算法思路
在之前解决这道题的时,都是使用循环,计算到第 n 个斐波那契数;或者说动态规划解决。
之前不使用递归解决的原因:递归调用函数栈帧太多。
例如:要求 f(10),就要先求 f(9) 和 f(8),而求 f(9),也要去求 f(8)

通过观察不难发现,在递归遍历的过程当中,某个位置都是递归遍历了很多次的。
记忆化搜索:在遍历完某个位置时,记录一下从当前位置遍历的结果;在下次遍历的该位置时,直接使用获取结果即可。
对于这道题,就是在首次遍历完 i 位置时,记录 第i个斐波那契数,在下次遍历到 i 位置值,直接获取第 i 个斐波那契数。
这里记忆化搜索思路就有点类似于简单的动态规划问题
状态表示 :
dp[i]表示第 n 个斐波那契数状态转移方程 :
dp[i] = dp[i-1] + dp[i-2];
代码实现
cpp
class Solution {
int dp[33];
public:
int f(int n) {
if (dp[n] >= 0)
return dp[n];
dp[n] = f(n - 1) + f(n - 2);
return dp[n];
}
int fib(int n) {
// 初始化
for (int i = 0; i <= n; i++)
dp[i] = -1;
dp[0] = 0;
dp[1] = 1;
return f(n);
}
};
二、不同路径
题目解析

给定一个 m*n 的网格,只能向下、向右移动一步。
从[0,0]位置出发,到达网格的右下角,求一共有多少路径。
算法思路
从[0,0]位置出发,每次向下、向右移动一步,BFS 遍历展开图:

可以看出,对于同一个位置 [i,j] 是进行了多次 遍历的
记忆化搜索 :首次遍历到 [i,j]位置时,遍历完后记录结果(从[i,j]走到 网格右下角的路经个数 );在之后遍历到[i,j]位置时,直接获取从`[i,j] 走到 网格右下角的路径个数即可。
代码实现
cpp
class Solution {
int dp[110][110];
public:
int dfs(int x, int y, int m, int n) {
if (x > m || y > n)
return 0;
if (dp[x][y] > 0)
return dp[x][y];
dp[x][y] = dfs(x + 1, y, m, n) + dfs(x, y + 1, m, n);
return dp[x][y];
}
int uniquePaths(int m, int n) {
dp[m][n] = 1;
return dfs(1, 1, m, n);
}
};
三、最长递增子序列
题目解析

给定一个整数数组 nums,找出其中最长严格递增子序列的长度。
严格递增子序列:从小到大递增(不能相等),子序列(可以不连续)
算法思路
对于这道题,整体来说就是,从0 ~ n-1任意一个位置开始,递归遍历寻找最长严格递增子序列。
在遍历到 i 位置时,就要遍历寻找 从 i+1 ~ n-1开始,最长严格递增子序列。
递归遍历展开图:

虽然省略了很多内容,但还是可以看出在 递归遍历寻找最长递增子序列时,对于 i 位置是遍历了很多次的。
记忆化搜索:记录从 i 位置开始的最长递增子序列的长度;
在遍历到 i 位置时,如果 i 位置还没有遍历过,就进行一次 BFS 递归遍历;如果 i 位置已经遍历过,则直接获取结果即可。
代码实现
cpp
class Solution {
public:
int dfs(vector<int>& nums, vector<int>& dp, int pos) {
int n = nums.size();
if (pos == n)
return 0;
if (dp[pos] > 0)
return dp[pos];
int ret = 1;
for (int i = pos + 1; i < n; i++) {
if (nums[i] > nums[pos])
ret = max(ret, dfs(nums, dp, i) + 1);
}
dp[pos] = ret;
return dp[pos];
}
int lengthOfLIS(vector<int>& nums) {
int ret = 0, n = nums.size();
vector<int> dp(n, 0);
for (int i = 0; i < n; i++) {
ret = max(ret, dfs(nums, dp, i));
}
return ret;
}
};
四、猜数字大小 II
题目解析

这道题,猜数字游戏:
在 1~n 中选一个数字,猜我选的数字,猜对了游戏胜利。
猜错了,告诉你我选的数字是比猜的数字 更大或者更小 (当猜了数字 x 并且猜错后,就需要支付 x 的现金费用),然后继续猜知道猜对为止。
给定一个数字 n ,求:无论我选择哪一个数字,都确保你能获胜的最小现金数。
算法思路
对于猜数字的整体流程,还是比较简单的:首先在[1,n]中猜数字,然后根据大小关系,再去左侧区间/右侧区间去猜数字,直到猜对为止。
这里要确保我们可以获胜,就 DFS 遍历,去求在区间
[begin,end]内 任选一个数字,都能获取胜利的最小现金数。对于区间
[begin,end],猜数字i,要确保能够获得胜利,就要分别求 区间[begin,i-1]、[i+1,end]内数字能获得胜利的最小现金数,然后取最大值(对于任选数字,都要保证能获取胜利)
对于区间 [begin, end],计算该区间内必胜的最小现金数 dfs(begin, end) :
- 边界条件 :若
begin >= end(区间无数字 / 只有 1 个数字),无需花钱,直接返回0; - 枚举猜测点:遍历区间内每个数字
i分析猜i的代价:- 若答案比
i小:DFS 计算左区间[begin, i-1]的必胜最小代价dfs(begin, i-1); - 若答案比
i大:DFS 计算右区间[i+1, end]的必胜最小代价dfs(i+1, end); - 「最坏情况」:猜
i时,需准备足够的钱,取左右区间中代价更高的那个(否则钱不够会输),即max(left, right); - 「猜
i的总成本」:最坏情况代价 + 猜i本身的成本i,即max(left, right) + i;
- 若答案比
- 选最优策略 :枚举所有猜测点
i后,取所有i对应的总成本的最小值(区间[begin,end]的最小必胜现金数)。
记忆化搜索 :在DFS遍历过程中,区间 [i,j]就会被 DFS 遍历多次,这里就采用二维数组来记录 区间[i,j]最小必胜现金数。
代码实现
cpp
class Solution {
int dp[210][210];
public:
int dfs(int begin, int end) // [begin,end] 能猜中数字,最小现金数
{
if (begin >= end)
return 0;
if (dp[begin][end] > 0)
return dp[begin][end];
int ret = 0x3f3f3f;
for (int i = begin; i <= end; i++) {
int left = dfs(begin, i - 1);
int right = dfs(i + 1, end);
ret = min(ret, max(left, right) + i);
}
dp[begin][end] = ret;
return dp[begin][end];
}
int getMoneyAmount(int n) { return dfs(1, n); }
};
五、矩阵中的最长递增路径
题目解析

给定一个 m*n 的矩阵,可以向上、下、左、右四个方向移动(不能沿对角线移动,不能移动到边界外、也不允许环绕)
要找出矩阵当中,最长的递增路径。
算法思路
最长递增路径,可以以任意位置开始,所以就要从二维矩阵的每一个位置都进行 BFS遍历,寻找最长递增路径。
在 BFS 递归遍历寻找最长路径时,对于[i,j],也会遍历很多次的。
记忆化搜索 :记录以 [i, j]位置为起点的最长递增子路径的长度(首次遍历时记录,后续遍历直接获取结果)
代码实现
cpp
class Solution {
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
public:
int dfs(vector<vector<int>>& matrix, vector<vector<int>>& dp, int x,
int y) {
if (dp[x][y] > 0)
return dp[x][y];
int m = matrix.size();
int n = matrix[0].size();
int ret = 1;
for (int i = 0; i < 4; i++) {
int posx = x + dx[i];
int posy = y + dy[i];
if (posx >= 0 && posx < m && posy >= 0 && posy < n &&
matrix[posx][posy] > matrix[x][y])
ret = max(ret, dfs(matrix, dp, posx, posy) + 1);
}
dp[x][y] = ret;
return dp[x][y];
}
int longestIncreasingPath(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
int ret = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
ret = max(ret, dfs(matrix, dp, i, j));
}
}
return ret;
}
};
本篇文章到这里就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws