代码随想录算法训练营第四十一天 | 一层for过渡到两层for

343. 整数拆分

文档讲解:代码随想录 (programmercarl.com)

视频讲解:动态规划,本题关键在于理解递推公式!| LeetCode:343. 整数拆分_哔哩哔哩_bilibili

状态:做不出来。应该算难题了。注意这题是从两层for,而之前题目都是一层for,这是题目的难点。真算是一层for到两层for过渡的经典题。

思路

先看视频后看文档,文档不够细

动规五部曲,分析如下:

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

    dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。dp[i]的定义将贯彻整个解题过程。

  2. 确定递推公式

    dp[i]是怎么得到的呢?

    令j从1遍历到 i-1,有两种渠道得到dp[i]:

    ①将i拆分成两个数字j 和i-j,那么dp[i] = j * (i - j);

    ②将i拆分成两个以及两个以上的个数相乘,那么dp[i] = j * dp[i - j],相当于是拆分(i - j)dp[i - j]表示对(i - j)拆分相乘得到的最大值

    在j从1遍历到 i-1过程中,比较 j * (i - j) 和 j * dp[i - j],取最大的,递推公式为dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

    在取最大值的时候,为什么还要比较dp[i]呢?因为在递推公式推导的过程中,每次计算dp[i],取最大的而已,比如对数字10拆分,可拆成很多种:5、5,3、3、4,…要从里面选最大的。

    那有同学问了,j怎么就不拆分呢?

    比如 i=6,dp[6] = 2 x dp[4] = 2 x 1 x 1 x 1 x1,此时j=2,那么有个问题:为什么不拆分j,即为什么不拆分2?

    这是因为之前 j=1 时,dp[6] = 1 x dp[5] = 1 x 1 x 1 x 1 x 1已经出现过把2拆分的情况了,所以是为了不重复拆分。

  3. dp的初始化

    严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。

    只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!

  4. 确定遍历顺序

    先来看看递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

    dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]

    for (int i = 3; i <= n ; i++) {		//遍历每个数i拆分后的最大乘积
        for (int j = 1; j < i; j++) {	//把i拆成两部分,第一部分为固定值j,第二部分为(i-j)或dp[i-j]
            dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
        }
    }
    

    注意 枚举j的时候,是从1开始的。从0开始的话,那么让拆分一个数拆个0,求最大乘积就没有意义了。

    至于 i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。

    更优化一步,可以这样:

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

    因为拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的。

    例如 6 拆成 3 * 3, 10 拆成 3 * 3 * 4。 100的话 也是拆成m个近似数组的子数 相乘才是最大的。

    只不过我们不知道m究竟是多少而已,但可以明确的是m一定大于等于2,既然m大于等于2,也就是 最差也应该是拆成两个相同的 可能是最大值。

    那么 j 遍历,只需要遍历到 n/2 就可以,后面就没有必要遍历了,一定不是最大值。

  5. 举例推导dp数组

代码

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1);
        dp[2] = 1;
        for(int i = 3; i <= n; i++){    //遍历每个数i拆分后的最大乘积
            for(int j = 1; j <= i/2; j++){  //把i拆成两部分,第一部分为固定值j,第二部分为(i-j)或dp[i-j]
                dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]));
            }
        }
        return dp[n];
    }
};

96.不同的二叉搜索树

文档讲解:代码随想录 (programmercarl.com)

视频讲解:动态规划找到子状态之间的关系很重要!| LeetCode:96.不同的二叉搜索树_哔哩哔哩_bilibili

状态:不会做。感觉很难。

思路

! 求多少种、最值,几乎都可以往dp靠

直接看视频吧,这题没做过很难想到递推公式。如果要找递归公式,先从n=1,2,3把图画出,看里面的规律,即利用dp[1]和dp[2]推导出dp[3]。

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1);
        dp[0] = 1;
        for(int i = 1; i <= n; i++){    //遍历各个dp[i]
            for(int j = 1; j <= i; j++){    //每个dp[i]的构建过程
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值