主要特点和应用场景包括:
经典的例子包括斐波那契数列的递归实现、图的最短路径问题中的递归搜索等。在实现记忆化搜索时,通常需要使用数据结构(如哈希表、数组等)来保存已经计算过的结果。
记忆化搜索和动态规划都是一种用于优化递归算法的技术,它们有很多相似之处,但也存在一些关键的区别。下面是它们之间的相同之处和不同之处
相同之处:
不同之处:
总的来说,记忆化搜索和动态规划都是用于优化递归算法的技术,但它们的实现方式和问题解决的思路有一些不同。在解决问题时,根据具体的情况选择使用记忆化搜索或动态规划能够更好地满足问题的需求。
题目链接:https://leetcode.cn/problems/fibonacci-number/
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
示例 1:
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
思路
这里我们使用记忆化搜索算法,存放已经计算的斐波那契数,可以使递归算法达到线性级别,同样动态规划算法也可以,只不过该算法思维略有不同,其实算法是几乎一致的。
代码
class Solution {
vector<int> memo;
public:
Solution():memo(31,-1){};
int fib(int n) {
if(memo[n]!=-1) return memo[n];
if(n==0||n==1)
{
memo[n]=n;
return n;
}
memo[n]=fib(n-1)+fib(n-2);
return memo[n];
}
};
或
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];
}
};
题目链接:https://leetcode.cn/problems/unique-paths/
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
提示:
1 <= m, n <= 100
2 * 109
思路
同样这里如果使用普通的递归深度遍历,会有大量重复计算导致时间复杂度过高,所以这里要使用记忆化搜索来辅助递归算法,达到线性的时间复杂度,我们计算每个格子的路径,相当于上面格子的路径加上左边格子的路径一直递推,完成递归的逆推,同样动态规划也是按照这个逻辑顺推过去。
代码
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> memo(m+1,vector<int>(n+1,0));
return dfs(m,n,memo);
}
int dfs(int m,int n,vector<vector<int>>& memo){
if(memo[m][n]!=0) return memo[m][n];
if(m==0||n==0) return 0;
if(m==1&&n==1){
memo[m][n]=1;
return 1;
}
memo[m][n]=dfs(m-1,n,memo)+dfs(m,n-1,memo);
return memo[m][n];
}
};
或
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
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];
}
};
题目链接:https://leetcode.cn/problems/longest-increasing-subsequence/
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
思路
深度遍历每个位置向后推最长的递增序列,加上备忘录记忆化搜索做优化,同样动态规划就是在该思想上有后往前递推即可。
代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
vector<int> memo(n,0);
int ret=0;
for(int i=0;i<n;++i)
ret=max(ret,dfs(i,nums,memo));
return ret;
}
int dfs(int pos,vector<int>& nums,vector<int>& memo){
if(memo[pos]!=0) return memo[pos];
int ret=1;
for(int i=pos+1;i<nums.size();i++){
if(nums[i]>nums[pos])
ret=max(ret,dfs(i,nums,memo)+1);
}
memo[pos]=ret;
return ret;
}
};
或
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
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);
}
ret=max(ret,dp[i]);
}
return ret;
}
};
题目链接:https://leetcode.cn/problems/guess-number-higher-or-lower-ii/
我们正在玩一个猜数游戏,游戏规则如下:
1
到 n
之间选择一个数字。x
并且猜错了的时候,你需要支付金额为 x
的现金。如果你花光了钱,就会 输掉游戏 。给你一个特定的数字 n
,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。
示例 1:
输入:n = 10
输出:16
解释:制胜策略如下:
- 数字范围是 [1,10] 。你先猜测数字为 7 。
- 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $7 。
- 如果我的数字更大,则下一步需要猜测的数字范围是 [8,10] 。你可以猜测数字为 9 。
- 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $9 。
- 如果我的数字更大,那么这个数字一定是 10 。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16 。
- 如果我的数字更小,那么这个数字一定是 8 。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16 。
- 如果我的数字更小,则下一步需要猜测的数字范围是 [1,6] 。你可以猜测数字为 3 。
- 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $3 。
- 如果我的数字更大,则下一步需要猜测的数字范围是 [4,6] 。你可以猜测数字为 5 。
- 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5 。
- 如果我的数字更大,那么这个数字一定是 6 。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
- 如果我的数字更小,那么这个数字一定是 4 。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
- 如果我的数字更小,则下一步需要猜测的数字范围是 [1,2] 。你可以猜测数字为 1 。
- 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $1 。
- 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11 。
在最糟糕的情况下,你需要支付 $16 。因此,你只需要 $16 就可以确保自己赢得游戏。
示例 2:
输入:n = 1
输出:0
解释:只有一个可能的数字,所以你可以直接猜 1 并赢得游戏,无需支付任何费用。
示例 3:
输入:n = 2
输出:1
解释:有两个可能的数字 1 和 2 。
- 你可以先猜 1 。
- 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $1 。
- 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $1 。
最糟糕的情况下,你需要支付 $1 。
思路
这里我们最简单的办法就是使用dfs暴力搜索每个猜数字的情况,在左右子树中找到较小的数累加,完成比对,加上记忆化搜索即可。
代码
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 mid=left;mid<=right;mid++){
int x=dfs(left,mid-1);
int y=dfs(mid+1,right);
ret=min(ret,mid+max(x,y));
}
memo[left][right]=ret;
return ret;
}
};
题目链接:https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/
给定一个 m x n
整数矩阵 matrix
,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
示例 1:
输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4
解释:最长递增路径为 [1, 2, 6, 9]。
示例 2:
输入:matrix = [[3,4,5],[3,2,6],[2,2,1]]
输出:4
解释:最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。
示例 3:
输入:matrix = [[1]]
输出:1
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 200
0 <= matrix[i][j] <= 231 - 1
思路
这里通过简单的深度优先遍历思路是没有问题的,但在时间上达不到要求,所以这里要加上记忆化搜索来进行优化。
代码
class Solution {
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
int 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 ret=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]){
ret=max(ret,dfs(matrix,x,y)+1);
}
}
memo[i][j]=ret;
return ret;
}
};