【算法51天:Day51】第九章动态规划 最长递增子序列(300)

88 阅读1分钟

题目三:

image.png

思路

最长上升子序列是动规的经典题目,这里dp[i]是可以根据dp[j] (j < i)推导出来的,那么依然用动规五部曲来分析详细一波:

  1. dp[i]的定义

dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度

  1. 状态转移方程

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

所以:

if (nums[i] > nums[j]) {
    dp[i] = Math.max(dp[i], dp[j] + 1);
}

注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值

  1. dp[i]的初始化

每一个i,对应的dp[i](即最长上升子序列)起始大小至少都是1.

  1. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环在外层,遍历j则在内层,代码如下:

for (let i = 1; i < nums.length; i++) {
    for (let j = 0; j < i; j++) {
        if (nums[j] < nums[i]) {
            dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
    if (dp[i] > result) {
         result = dp[i]; // 取长的子序列
    }
}
  1. 举例推导dp数组

输入:[0,1,0,3,2],dp数组的变化如下:

300.最长上升子序列

如果代码写出来,但一直AC不了,那么就把dp数组打印出来,看看对不对!

以上五部分析完毕,JS代码如下:

const lengthOfLIS = (nums) => {
    let dp = Array(nums.length).fill(1);
    let result = 1;

    for(let i = 1; i < nums.length; i++) {
        for(let j = 0; j < i; j++) {
            if(nums[j] < nums[i]) {
            // 位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
                dp[i] = Math.max(dp[i], dp[j]+1);
            }
        }
        result = Math.max(result, dp[i]);
    }
    return result;
};

#总结

本题最关键的是要想到dp[i]由哪些状态可以推出来,并取最大值,那么很自然就能想到递推公式:dp[i] = max(dp[i], dp[j] + 1);

子序列问题是动态规划的一个重要系列,本题算是入门题目,好戏刚刚开始!