动态规划初级题解法(JS)

296 阅读3分钟

定义

动态规划,简称dp,即将问题分解为互相重叠的字问题,通过反复求解子问题来解决原问题。

求解动态规划的核心问题是穷举,这类问题因为存在重叠子问题,穷举效率会极其低下。

动态规划问题会存在最优子问题,可以通过子问题的最值来得到原问题的最值,然后推导出状态转移方程(状态转移方程,是动态规划中本阶段的状态。往往是上一阶段状态和上一阶段决策的结果。如果给定了第K阶段的状态Sk以及决策uk(Sk),则第K+1阶段的状态Sk+1也就完全确定。)

动态规划的解题方法

  1. 递归 + 记忆化(自顶向下)
  1. 动态规划(自底向上)

解题流程

  1. 根据重叠子问题定义状态
  1. 寻找最优子结构推导状态转移方程
  1. 确定dp初始状态
  1. 确定输出值

leetcode

解法:递归 + 记忆化

  1. Leetcode 70 爬楼梯

解题思路:

爬上 第 n 阶楼梯的方法数量 = 爬上 n-1 阶 楼梯的数量 + 爬上 n-2 阶楼梯的数量。

因为每次最多爬 1 阶 或 2阶。

倒推过去,爬上 n-1 阶 楼梯再爬一阶可以到第 n 阶楼梯,爬上 n-2 阶 楼梯再爬 2阶可以到 第 n 阶楼梯。两者相加正好是 爬到第 n 阶 楼梯的方法数量。这两部分再继续倒推就可以得到自己的数量。

这样就得到了 状态转移方程 dp[n] = dp[n-1] + dp[n-2];

var climbStairs = function (n) {
    const memo = {} ; // 缓存结果
    const helper = (x) => {
        if (memo[x]) return memo[x];
        if (x === 1) return 1;
        if (x === 2) return 2;
        memo[x] = helper(x-1) + helper(x-2);
        return memo[x];
    }
    return helper(n);
}

解法:动态规划

  1. Leetcode 121 买股票的最佳时机

解题思路

每天的状态是前一台的状态 + 今天的状态结合而成的,由于每天有两种状态,用一个矩阵来表示。

第i-1天不持有然后不操作 和 第i-1天持有然后卖出

dp[i][0] 今天不持有 = Math.max( dp[i-1][0] 昨日持有今日不操作, dp[i - 1][0] + prices[i] 昨日不持有今日买入)

第i-1天持有然后不操作 和 第i-1天不持有然后买入

dp[i][1] 今日持有 = Math.max(dp[i-1][1] 昨日持有今日不操作, -prices[i] 昨日持有今日卖出)

const maxProfit = function (prices) {
    let n = prices.length;
    let dp = Array.from(new Array(n), () => new Array(2));
    dp[0][0] = 0; //第0天不持有
    dp[0][1] = -prices[0]; //第0天持有
    for (let i = 1; i < n; i++) {
        // 第i-1天不持有然后不操作 和 第i-1天持有然后卖出 两种情况的最大值转移过来
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
        // 第i-1天持有然后不操作 和 第i-1天不持有然后买入 两种情况的最大值转移过来
        dp[i][1] = Math.max(dp[i - 1][1],  -prices[i]); 
    }
    return dp[n - 1][0];
};
  1. leetcode 53 最大子数组和

解题思路

如果以第i-1个数结尾的子数组最大和小于0,就重新开始计算新的子数组。

得到状态转移公式:

当前最大子数组之和 dp[i] = Math.max( 前一项子数组之和 dp[i-1] + 当前项 nums[i] ,当前项nums[i] )

var maxSubArray = function(nums) {
    let pre = 0, maxAns = nums[0]; // 以前一项结尾的数组的最大子数组之和, 最大值
    nums.forEach((x) => {
        pre = Math.max(pre + x, x); // 当前项是否作为新的子数组的第一个元素。
        maxAns = Math.max(maxAns, pre); // 取最大值
    });
    return maxAns;
};
  1. 198 打家劫舍

解题思路

因为不能去两家相邻的房屋,所以。

dp[0] = nums [0];

dp[1] = Max.max(nums[0], nums[1]);

dp[2] = Math.max(dp[1], dp[0] + dp[2]);

得到状态转移公式

dp[n] = Math.max(dp[n-1], dp[n-2]+nums[n]);

var rob = function(nums) {
    const len = nums.length;
    const dp = [nums[0], Math.max(nums[0], nums[1])]; 
    for (let i=2; i< len; i++) {
        dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i]);
    }
    return dp[len-1]
};