目录
一*、斐波那契数

有些题记忆化搜索与动规等同,就像这题
解法一:递归
思路如下:
递归,O 2^n,画出递归展开树,我们发现,总会有些重复计算,比如n=5时d(3)就求了很多次,如果n越大,那么重复的计算会非常多。那么我们是不是可以用一个备忘录储存一下计算过的值,如果后边要拿这个数,那么就能直接拿到不用再递归计算了,从而将时间复杂度从指数级别降到线性级别这就是记忆化搜索。

递归代码如下:
cpp
class Solution {
public:
int fib(int n) {
return dfs(n);
}
int dfs(int n)
{
if(n==0||n==1)return n;
return dfs(n-1)+dfs(n-2);
}
};
解法二:记忆化搜索(避免重复运算,复杂度降低到线性)
记忆化搜索(备忘录递归)(自顶向下)O(n)
备忘录具体如何实现?
我们需要建立可变参数与计算值的映射关系,所以需要哈希表的思想
而我们本题创建一个数组就可以了。

记忆化搜索实现如下:
cpp
class Solution {
//刚需31,不能用={-1}初始化,全局变量这样处理,只会第一个是-1
int memo[31];
public:
int fib(int n) {
memset(memo,-1,sizeof memo);
return dfs(n);
}
int dfs(int n)
{
//往备忘录看一眼
if(memo[n]!=-1)
{
return memo[n];
}
//和递归终点初始化一样,往备忘录塞数
if(n==0||n==1)
{
memo[n]=n;
return memo[n];
}
//往备忘录塞数
memo[n]=dfs(n-1)+dfs(n-2);
return memo[n];
}
};
解法三:动态规划(自下上顶),常规的动态规划和记忆化搜搜本质差别不大。

dp实现如下:
cpp
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];
}
};
二、不同路径


思路如下:
纯递归会超时,优化为记忆化搜索
注意该题相当于从1,1的位置出发
到达某点的方法,等于到上面那个点方法+到左边那个点方法之和
代码实现如下:
解法一:记忆化搜索
cpp
class Solution {
int memo[101][101];
public:
int uniquePaths(int m, int n) {
return dfs(m, n);
}
int dfs(int m, int n) {
if (memo[m][n] != 0) {
return memo[m][n];
}
//上,左边界全部处理为0
if (m == 0 || n == 0){
return memo[m][n];
}
if (m == 1 && n == 1) {
memo[m][n] = 1;
return memo[m][n];
}
memo[m][n] = dfs(m - 1, n) + dfs(m, n - 1);
return memo[m][n];
}
};
解法二:dp,还是动规代码更好看
cpp
class Solution {
int dp[101][101];
public:
int uniquePaths(int m, int n) {
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];
}
};
三、最长递增子序列

先看看暴力如何做
思考递归每一层都在干什么
1.暴力就是从每一个元素为起点,后边遇到数字变大的就更新path,递归每层我们都要创建一个path,然后计算这个位置往后的最大的,返回这个path
搞清每次都在干什么就很好写了,不过该代码会超时,因为是n叉树,复杂度近乎于n^n,非常高
cpp
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int ret=0;
for(int i=0;i<nums.size();++i)
{
ret=max(ret,dfs(nums,i));
}
return ret;
}
int dfs(vector<int>& nums,int pos)
{
int path=1;
for(int i=pos+1;i<nums.size();++i)
{
if(nums[i]>nums[pos])
{
//不能path+=dfs(nums,i) or path=dfs(nums,i)+1
//因为这里for循环遍历在暴搜这个位置往后的可能最大值,每次需要比较
path=max(path,dfs(nums,i)+1);
}
}
return path;
}
};
2.记忆化优化,因为后边遇到更大数字要更新path,如果memo里有这个数字,那么我们只需要加上这个最大数字位置(可能会有重复)往后最大的递增子序列长度就行。创建一个备忘录,dfs函数开头先检查这个位置记录了没有,有返回,没有进入dfs内部,计算完这个位置后在备忘录记录。

cpp
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> memo(nums.size());
int ret=0;
for(int i=0;i<nums.size();++i)
{
ret=max(ret,dfs(nums,i,memo));
}
return ret;
}
int dfs(vector<int>& nums,int pos,vector<int>& memo)
{
//查找备忘录,看一下这个位置有没有记录
if(memo[pos]!=0)return memo[pos];
int path=1;
for(int i=pos+1;i<nums.size();++i)
{
if(nums[i]>nums[pos])
{
//不能path+=dfs(nums,i) or path=dfs(nums,i)+1
//因为这里for循环遍历在暴搜这个位置往后的可能最大值,每次需要比较
path=max(path,dfs(nums,i,memo)+1);
}
}
memo[pos]=path;
return path;
}
};
3.dp,动规后续会出一个专栏专门讲解
因为我求一个位置最长子序列,都要知道这个位置往后的情况,那么我们dp就得从后往前更新,因为往后才是最底的那个部分
cpp
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
//每个都得初始是1,不能是0,不能只初始最后一个位置是1
//可能有的数往后都是比它小的,那它也不能是0,而是1
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);
}
}
//得到i位置往后最大递增子串,判断
ret=max(ret,dp[i]);
}
return ret;
}
};
四、猜数字大小II




没记错的话,贪心章节也会提及这题
有了第一道猜数字,可能会考虑二分,如果答案是10,那么这个策略来猜,
花销是很大的。所以二分并不是最好的策略,因为我们要的不是最小次数,而是最小保证赢的金额,也就是往大值里找最小值。

问题的核心在于,答案的不确定性,所以为了确保你的钱够付得起所有结果,你得准备的钱一定得是你这个分支,往更大的地方猜测去准备你的本金,这样就算数是更小部分,我们准备的钱也足够我们获胜。

博弈,枚举从1~n当作头,然后往下递归处理子问题
递归每层处理,都往大的地方考虑,求下层所有递归结果的,里的最小值

记忆化搜索+dfs实现
cpp
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 head=left;head<=right;++head)
{
//x:左子树处理结果 y:右子树处理结果
int x=dfs(left,head-1);
int y=dfs(head+1,right);
//max(x,y)保证赢需要往大了考虑
ret=min(ret,head+max(x,y));
}
//更新该区间的保证赢的最少花费
memo[left][right]=ret;
return ret;
}
};
五、矩阵中的最长递增路径
329. 矩阵中的最长递增路径 - 力扣(LeetCode)



有了前面的铺垫,这道困难题就易如反掌了
cpp
class Solution {
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1},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 path=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])
{
path=max(path,dfs(matrix,x,y)+1);
}
}
memo[i][j]=path;
return path;
}
};
此专栏完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
感谢收看。