动态规划系列之力扣中等题(零钱兑换,乘积最大子数组,最长递增子序列,带详细注释)

289 阅读3分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

前两天(其实是七天前),我们讲了初始动态规划,通过爬楼梯认识了动态规划,传送门-># 用爬楼梯来认识动态规划。本章我们来用三道力扣的中等难度提来巩固下我们的学习~话不多说,我们这就开始吧

剑指 Offer II 103. 最少的硬币数目

image.png

image.png 提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

题解:

确定递归状态,推导递归方程: dp(x) = y。y是所求值,最少硬币个数,个数与金额有关,所以x表示金额。x可以拆解dp(i) + 1,dp(i)表示小于x的某个金额,再加上一张coins中的某个硬币。然后题目要求最少硬币个数,所以dp(i)+1可以转换成min(dp(i1) + coins(j1), dp(i2) + conins(j2),...,dp(in) + coins(jn))。

代码:

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function (coins, amount) {
    const n = amount + 1;
    const dp = new Array(n).fill(-1);

    //边界条件
    dp[0] = 0;

    for (let i = 1; i < n; i++) {
        for (let k of coins) {
            let j = i - k;
            // 金额小于当前硬币金额
            if (i < k) continue;
            // j金额没有有效性
            if (dp[j] === -1) continue;
            // 之前值是无效值或者之前值比现在j金额+1数量大,就替换成当前的较小值
            if (dp[i] === -1 || dp[i] > dp[j] + 1) dp[i] = dp[j] + 1;
        }
    }
    return dp[amount]
};

152. 乘积最大子数组

image.png

题解:

正常来说,dp(x) = y,y表示到x位置的乘积的最大值。dp(x) = min(dp(x-1) * val(x), val(x))。但是这题中有负数,如果负数 * dp(x-1),会变成最小值,而负数 * x-1之前的最小值,会变成x的最大值。所以这题我们需要定义两个数,到i位置的最大值iMax,最小值iMin。然后根据当前所求x位置的值是正是负来判断用最大还是最小值相乘。

代码如下:

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxProduct = function (nums) {
    // 初始化结果为最小值,iMax = iMin = 1,这样dp[0]就等于nums[0]
    let ans = -Infinity, iMax = 1, iMin = 1;

    for (let i = 0; i < nums.length; i++) {
        // 负数就将最大最小值对调
        if (nums[i] < 0) {
            const temp = iMax;
            iMax = iMin;
            iMin = temp;
        }
        // 最大值
        iMax = Math.max(nums[i] * iMax, nums[i]);
        // 最小值
        iMin = Math.min(nums[i] * iMin, nums[i]);
        
        ans = Math.max(iMax, ans);
    }
    return ans;
};

300. 最长递增子序列

image.png

image.png

题解:

严格递增子序列表示数组中从小到大的子序列。

我们找递归状态,dp(x) = y,y表示到i位置的最长递增子序列长度,他与最后位置i和之前一个比val(i)小的值的位置的最长递增子序列 + 1得到。

因此我们得到状态转移方程为: dp(i) = dp(j) + 1 && j < i && val(j) < val(i)

代码如下:

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
    const n = nums.length;
    // 每个i位置的递增子序列最小值肯定为1,初始化后dp[0] = 1
    const dp = new Array(n).fill(1);
    let ans = 1;
    for (let i = 1; i < n; i++) {
        // 遍历i位置之前的元素,找到合法的递增子序列
        for (let j = 0; j < i; j++) {
            // 必须要j位置的值小于i位置的值才是递增
            if (nums[j] < nums[i]) {
                // dp[i]等于所有j位置的递增子序列长度+1中的最大值。
                dp[i] = Math.max(dp[j] + 1, dp[i])
            }
        }
        // ans等于所有以i为结尾的递增子序列长度中的最大值
        ans = Math.max(dp[i], ans)
    }
    return ans;
};

这道题还有个进阶,因为上面的方法的时间复杂度是O(n2),进阶要我们降低到O(nlogn),大家有什么想法欢迎留言讨论~下一篇递归文章我们来给出答案。

觉得对您有帮助麻烦点个赞再走~

本文的代码已收录Github,仓库中包括之前的文章链接和代码收录,后续代码也会陆续更新,欢迎大家不吝赐教。