欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 代码随想录 | Day36 | 动态规划 :整数拆分不同的二叉搜索树

代码随想录 | Day36 | 动态规划 :整数拆分不同的二叉搜索树

2024/11/6 19:33:05 来源:https://blog.csdn.net/m0_74795952/article/details/143372507  浏览:    关键词:代码随想录 | Day36 | 动态规划 :整数拆分不同的二叉搜索树

代码随想录 | Day36 | 动态规划 :整数拆分&不同的二叉搜索树

动态规划应该如何学习?-CSDN博客

动态规划学习:

1.思考回溯法(深度优先遍历)怎么写

注意要画树形结构图

2.转成记忆化搜索

看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算

3.把记忆化搜索翻译成动态规划

基本就是1:1转换

343.整数拆分

343. 整数拆分 - 力扣(LeetCode)

思路分析:

9f416325c155daf990f88e305c09ee167000954510265d334d0d4ae53243dfb6

我们要把n分解,就把f(n)当做是分解后返回的乘积结果

那很明显,f(n)可以分为

 {i*f(n-i) | 1<i<n-1} f(n-1)会继续分,和图中一样

然后我们会从中选一个最大的出来,作为我们的结果返回

而这里我们会注意到

i*f(n-i)需要和i*(n-i)比大小,我们要选择那个大的,决定我们是否要继续分解下去

如果说没有分解过的后者已经比前者大了,那就没必要分解了,直接返回大的就行

举个例子:

当i是1而n是3

i*f(n-i)=1*f(2)=1*1=1
i*(n-i)=1*2=2

由此我们得到了本层逻辑的大概框架

i*f(n-i)和i*(n-i)比大小,我们挑一个大的进行返回

1.回溯 DFS

1.返回值和参数

dfs就是前面的f,我们要向上返回分解n-i后的结果f(n-i)即dfs(n-i)所以返回值为int

传入值就为n,就是要分解的那个数

int dfs(int n)

2.终止条件

dfs(2)=1,因为2只能拆成1*1

而dfs(1)和dfs(0)都没办法拆分,不需要考虑,或者都返回1也是可以的

if(n==2)return 1;

3.本层逻辑

这里就是上面思路部分说的

i*f(n-i)和i*(n-i)比大小,我们挑一个大的进行返回
max(i*(n-i),i*dfs(n-i))

而res记录的是树形结构同一层中的最大值,也是我们要向上层返回的最后结果,因为最大的乘以最大的肯定还是最大的,因此我们要再套一个max

int res=-1;
for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));

完整代码:

当然,这是超时的

class Solution {
public:int dfs(int n){if(n==2)return 1;int res=-1;for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));return res;}int integerBreak(int n) {return dfs(n);}
};
//lambda
class Solution {
public:int integerBreak(int n) {function<int(int)> dfs=[&](int n)->int{if(n==2)return 1;int res=-1;for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));return res;};return dfs(n);}
};

这是笔者第一次写的dfs,能过就是不好改成备忘录,故不做讲解,感兴趣的小伙伴看看就好

class Solution {
public:int res=INT_MIN;void dfs(int n,int sum,int index){if(n<=1)return ;for(int i=index;i<n;i++){int temp=sum*i*(n-i);res=max(res,temp);dfs(n-i,sum*i,i);}    }int integerBreak(int n) {dfs(n,1,1);return res;}
};

2.记忆化搜索

加入dp数组作为备忘录,初始化dp为-1

每次返回都给dp赋值之后再返回。加个if判断,碰到不是-1的说明被计算过了,直接用

因为每次的返回值其实都是拆分以后要往上返回的结果,就是f(n-i)

举个例子,n=10,i=3

那我们f(n-i)=f(10-3)=f(7),也就是dfs(7)的返回值就是dp[7],就是7的拆分后能达到的最大值,所以就要把这个记录下来

class Solution {
public:int dfs(int n,vector<int>& dp){if(n==2)return dp[n]=1;int res=-1;for(int i=1;i<n;i++){if(dp[n-i]!=-1)res=max(res,max(i*(n-i),i*dp[n-i]));elseres=max(res,max(i*(n-i),i*dfs(n-i,dp)));}return dp[n]=res;}int integerBreak(int n) {vector<int> dp(n+1,-1);return dfs(n,dp);}
};
//lambda
class Solution {
public:int integerBreak(int n) {vector<int> dp(n+1,-1);function<int(int)> dfs=[&](int n)->int{if(n==2)return dp[n]=1;int res=-1;for(int i=1;i<n;i++){if(dp[n-i]!=-1)res=max(res,max(i*(n-i),i*dp[n-i]));elseres=max(res,max(i*(n-i),i*dfs(n-i)));}return dp[n]=res;};return dfs(n);}
};

3.动态规划

怎么由记忆搜索改到动态规划呢?

首先dp数组和下标含义就是dp[i]就是i拆分后的最大值

而上面递归函数里面有一个for循环,那说明我们再函数中应该再加一层循环,用来循环递归函数的参数n,笔者使用j代替参数n

每次返回之前都会把返回值给记录一下,其实当时的返回值就是当时的dp[n],也就是说外层循环变量j顶替的就是n的角色,所以

dp[n]=res=dp[j]

直接把res的地方都替换成为dp[j]。这样就完成了从记忆搜索到递推的转变

1.确定dp数组以及下标的含义

dp数组和下标含义就是dp[i]就是i拆分后的最大值

2.确定递推公式

忘记了原因的请看思路分析部分

dp[j]顶替的是dp[n]即res的位置

dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));

3.dp数组如何初始化

初始化为负数就行,因为要得到最大值

dp[2]=1是2拆分后的结果已知,就是1

vector<int> dp(n+1,-1);
dp[2]=1;

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

注意一个小细节是这里的j可以取到n,dfs的参数也可以取到n,只是咱们在主函数第一次传入的就是n,大家可能没有注意这一点少写了等号导致错误。

for(int j=3;j<=n;j++)for(int i=1;i<j;i++)dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));

完整代码

class Solution {
public:int integerBreak(int n) {vector<int> dp(n+1,-1);dp[2]=1;for(int j=3;j<=n;j++)for(int i=1;i<j;i++)dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));return dp[n];}
};

96.不同的二叉搜索树

96. 不同的二叉搜索树 - 力扣(LeetCode)

思路分析:

我们可以分别以 1,2,3,……,n 为根结点,对于一棵树,我们可以递归的构造树的左右子树,而本题求解的是二叉搜索树的种数,那么假设左子树有 x 种,右子树有 y 种,可能的二叉搜索树就有 x×y 种,举个例子,要求结点数量为3的二叉搜索树的种数:

总数=以1为根结点的二叉搜索树数量+以2为根结点的二叉搜索树数量+以3为根结点的二叉搜索树数量

以1为根结点的二叉搜索树数量=左子树搜索树数量(0个节点)+右子树搜索树数量(2个节点)

以2为根结点的二叉搜索树数量=左子树搜索树数量(1个节点)+右子树搜索树数量(1个节点)

以3为根结点的二叉搜索树数量=左子树搜索树数量(2个节点)+右子树搜索树数量(0个节点)

如果是n的话那就是

总数=以1为根结点的二叉搜索树数量+以2为根结点的二叉搜索树数量+以3为根结点的二叉搜索树数量+…+以n为根结点的二叉搜索树数量

所以我们肯定需要一个for循环来遍历1~~n

1.回溯 DFS

1.返回值和参数

很明显我们传入的n表示我们算上n结点一共有几个结点

返回值就返回n个结点有多少种可能的二叉搜索树

int dfs(int n)

2.终止条件

当我们传的节点只有1个或者0个,那就返回1,这种情况只会有一种搜索树

if(n<=1)return 1;

3.本层逻辑

for循环遍历从1到我们传入的n

而dfs是得出子树的搜索树数量

左子树l有i个节点,右子树r就是n-i-1个节点

那么对于以i为根结点的二叉搜索树一共有l*r种搜索树

而sum是把以1~~n为根结点的各种情况下的i累加起来得到一个总数

最后返回我们得到的总数sum

int sum=0;
for(int i=0;i<n;i++)
{int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);
}
return sum;

完整代码:

class Solution {
public:int dfs(int n){if(n<=1)return 1;int sum=0;for(int i=0;i<n;i++){int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);}return sum;}int numTrees(int n) {return dfs(n);}
};
class Solution {
public:int numTrees(int n) {function<int(int)> dfs=[&](int n)->int{if(n<=1)return 1;int sum=0;for(int i=0;i<n;i++){int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);}return sum;};return dfs(n);}
};

2.记忆化搜索

其实写dfs的时候就想顺手把记忆数组给加上了。

加入dp数组作为备忘录,初始化dp为-1。

每次返回都给dp赋值之后再返回。加个if判断,碰到不是-1的说明被计算过了,直接用。

class Solution {
public:int dfs(int n,vector<int> &dp){if(n<=1)return dp[n]=1;if(dp[n]!=-1)return dp[n];int sum=0;for(int i=0;i<n;i++)sum+=dfs(i,dp)*dfs(n-i-1,dp);return dp[n]=sum;}int numTrees(int n) {vector<int> dp(n+1,-1);return dfs(n,dp);}
};
//lambda
class Solution {
public:int numTrees(int n) {vector<int> dp(n+1,-1);function<int(int)> dfs=[&](int n)->int{if(n<=1)return dp[n]=1;if(dp[n]!=-1)return dp[n];int sum=0;for(int i=0;i<n;i++)sum+=dfs(i)*dfs(n-i-1);return dp[n]=sum;};return dfs(n);}
};

3.动态规划

怎么由记忆搜索改到动态规划呢?

首先dp数组和下标含义是dp[i]就是有i个结点的话能有多少种搜索树的数量

和上一题一样,上面递归函数里面有一个for循环,那说明我们再函数中应该再加一层循环,用来循环递归函数的参数n,笔者使用j代替参数n

每次返回之前都会把返回值给记录一下,其实当时的返回值就是当时的dp[n],也就是说外层循环变量j顶替的就是n的角色,所以

dp[n]=sum=dp[j]

直接把sum的地方都替换成为dp[j]。这样就完成了从记忆搜索到递推的转变

1.确定dp数组以及下标的含义

首先dp数组和下标含义是dp[i]就是有i个结点的话能有多少种搜索树的数量

2.确定递推公式

忘记了原因的请看思路分析部分

dp[j]顶替的是dp[n]即sum的位置

dp顶替dfs的位置

sum+=dfs(i)*dfs(n-i-1);
dp[j]+=dp[i]*dp[j-i-1];

3.dp数组如何初始化

初始化为0,因为我们要进行累加求搜索树类型的总数

如果只有0个或者1个节点那就是1种搜索树类型,初始化为1

(0的话说明,n个全在右子树)

vector<int> dp(n+1,0);
dp[0]=1;
dp[1]=1;

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

for(int j=2;j<=n;j++)for(int i=0;i<j;i++)dp[j]+=dp[i]*dp[j-i-1];

完整代码

class Solution {
public:int numTrees(int n) {vector<int> dp(n+1,0);dp[0]=1;dp[1]=1;for(int j=2;j<=n;j++)for(int i=0;i<j;i++)dp[j]+=dp[i]*dp[j-i-1];return dp[n];}
};

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com