动态规划主题专杀-最长递增子序列-思路自我解剖

前言:做这种题再次强调一点,思路远比代码重要,思路对了,即使代码错了,错的也是细枝末节,思路错了,那就是个0.

所以本文并不想着重讲本题的算法代码,代码解法网上有得是。这里着重剖析自己的思路问题。

 

题干如下:

动态规划主题专杀-最长递增子序列-思路自我解剖

一直感觉自己对这种动态规划的状态转移方程的直觉很不好,亲自试了下这题,发现果然不好,上来就犯了两个错误。

 

第一,没有把状态转移中的状态函数定义好,后来看了答案(https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-she-ji-zui-chang-di-zeng-zi-xu-lie)发现状态转移函数人家是这样定义的:

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

而我自己起初想的是dp[i]代表从头到截止nums[i]这个元素这一段中的最长递增子序列的长度。

高下立判。

为什么答案中的更高?因为他定义的这个状态函数比我定义的范围要更明确,变数更小,物理学的角度看熵更低。为什么这么说?因为他的定义把子序列的一端给明确下来了,而我的定义,根本不包含子序列的任意一端,这就导致他的状态函数更有利于做递推,而我定义的状态函数很难做递推,果不其然,我就碰到了第二个问题:我尝试把dp(i)和dp(i-1)的递推关系找出来,结果找了半天发现根本找不到递推关系,一般这个时候,大脑就容易陷入死胡同,越想越乱。

 

当碰到第二个问题时,其实我犯了双重错误,第一重就是基于问题1而来,而第二重是我缺乏一个直觉,所谓的状态转移方程或者递推关系,根本不必拘泥于dp(i)到dp(i-1),还可以包括更多啊,比如dp(i-2),dp(i-3),其实仔细想想,fibonacci数列不就是这样的吗,算dp(i)是依赖于dp(i-1)和dp(i-2)的,由此可见,自以为理解了fibonacci数列,却仍不能灵活应用之,这就是理解的深度不够啊。

 

如果把这种向前多重依赖的思想真正内化了到自己脑子里,其实就不难发现这题的递推关系中,dp(i)实际上要依赖dp(i-1),dp(i-1)一直到dp(0)等一系列前序。

既然写到这里了,还是给一下代码吧:

public static 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++) {
        System.out.println("dp["+i+"]="+dp[i]);
        res = Math.max(res, dp[i]);
    }
    return res;
}