动态规划算法题解

145 阅读2分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

动态规划算法题解

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。

剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]k[1]...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

题解:
对于正整数 n,当 n≥2 时,可以拆分成至少两个正整数的和。令 i 是拆分出的第一个正整数,则剩下的部分是 n−i,n−i 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。(关键在于把大的问题,拆分成小的问题) 第一步:定义dp数组,dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积。
第二步:当 i≥2 时,假设对正整数 i 拆分出的第一个正整数是 j(1≤j<i),则有以下两种方案:

  • 将 i 拆分成 j 和 i−j,且 i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j);
  • 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]。 所以,状态转移方程为 dp[i]=Math.max(j*(i−j),j*dp[i−j])
var cuttingRope = function(n) {
    if(n <= 1) return 0;
    const dp = new Array(n+1).fill(0);

    for (let i = 2; i <= n; i++) {
        for (let j = 1; j < i; j++) {
            dp[i] = Math.max(dp[i], Math.max(j*(i-j), j * dp[i - j]));
        }        
    }
    return dp[n]
};

统计字典序元音字符串的数目

给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。

字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。

输入:n = 1
输出:5
解释:仅由元音组成的 5 个字典序字符串为 ["a","e","i","o","u"]

输入:n = 2
输出:15
解释:仅由元音组成的 15 个字典序字符串为["aa","ae","ai","ao","au","ee","ei","eo","eu","ii","io","iu","oo","ou","uu"]
题解:  

找规律:可知如果设 dp[i] 为以字母[i]为结尾的个数,那么a可以作为dp[0]的结尾,e可以作为dp[0]dp[1]的结尾,... ,u可以作为dp[0]、dp[1]、dp[2]、dp[3]、dp[4]的结尾。而dp[i]的数量是当n=0 到 n=n-1 时的dp[i]相加,所以思路如下:

var countVowelStrings = function(n) {
    const dp = new Array(5).fill(1); // n=0时都为1
    for (let i = 1; i < n; i++) {
        for (let j = 4; j >= 0; j--) { // 字母j(从u开始)
            for (let k = j-1; k >= 0; k--) {  // 字母 j 前面的字母都可以用 j 当作结尾
                dp[j] = dp[j] + dp[k]
            }
        }
    }
    return dp[0]+dp[1]+dp[2]+dp[3]+dp[4]
};