动态规划设计——最长递增子序列

题目

在这里插入图片描述
注意「子序列」和「子串」这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。下面先来一步一步设计动态规划算法解决这个问题。

为什么可以用动态规划解决

每个数为结尾可组成的最大子序列 = 它前一个最大的小于它的数组成的最大子序列 + 1

动态规划解法

动态规划的核心设计思想是数学归纳法。

相信大家对数学归纳法都不陌生,高中就学过,而且思路很简单。比如我们想证明一个数学结论,那么我们先假设这个结论在 k < n k<n k<n 时成立,然后想办法证明 k = n k=n k=n 的时候此结论也成立。如果能够证明出来,那么就说明这个结论对于 k 等于任何数都成立。

类似的,我们设计动态规划算法,不是需要一个dp数组吗?我们可以假设 d p [ 0... i − 1 ] dp[0...i-1] dp[0...i1] 都已经被算出来了,然后问自己:怎么通过这些结果算出dp[i]

直接拿最长递增子序列这个问题举例你就明白了。不过,首先要定义清楚dp数组的含义,即dp[i]的值到底代表着什么?

我们的定义是这样的:dp[i]表示以nums[i]这个数结尾的最长递增子序列的长度。
举两个例子:

在这里插入图片描述
算法演进的过程是这样的,:
在这里插入图片描述
根据这个定义,我们的最终结果(子序列的最大长度)应该是 dp 数组中的最大值。

int res = 0;
for (int i = 0; i < dp.size(); i++) {
    res = Math.max(res, dp[i]);
}
return res;

读者也许会问,刚才这个过程中每个 dp[i] 的结果是我们肉眼看出来的,我们应该怎么设计算法逻辑来正确计算每个 dp[i] 呢?

这就是动态规划的重头戏了,要思考如何进行状态转移,这里就可以使用数学归纳的思想:

我们已经知道了 d p [ 0...4 ] dp[0...4] dp[0...4] 的所有结果,我们如何通过这些已知结果推出 d p [ 5 ] dp[5] dp[5] 呢?
在这里插入图片描述
根据刚才我们对 dp 数组的定义,现在想求 dp[5] 的值,也就是想求以 nums[5] 为结尾的最长递增子序列。

nums[5] = 3,既然是递增子序列,我们只要找到前面那些结尾比 3 小的子序列,然后把 3 接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一。

当然,可能形成很多种新的子序列,但是我们只要最长的,把最长子序列的长度作为 dp[5] 的值即可。
在这里插入图片描述

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

这段代码的逻辑就可以算出 dp[5]。

算出dp数组其他的值:

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

还有一个细节问题,dp 数组应该全部初始化为 1,因为子序列最少也要包含自己,所以长度最小为 1。下面我们看一下完整代码:

public int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    // dp 数组全都初始化为 1
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) 
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }

    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

至此,这道题就解决了,时间复杂度 O(N^2)。总结一下动态规划的设计流程:

首先明确 dp 数组所存数据的含义。这步很重要,如果不得当或者不够清晰,会阻碍之后的步骤。
然后根据 dp 数组的定义,运用数学归纳法的思想,假设 d p [ 0... i − 1 ] dp[0...i-1] dp[0...i1] 都已知,想办法求出 d p [ i ] dp[i] dp[i],一旦这一步完成,整个题目基本就解决了。

但如果无法完成这一步,很可能就是 dp 数组的定义不够恰当,需要重新定义 dp 数组的含义;或者可能是 dp 数组存储的信息还不够,不足以推出下一步的答案,需要把 dp 数组扩大成二维数组甚至三维数组。

最后想一想问题的 base case 是什么,以此来初始化 dp 数组,以保证算法正确运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值