一、问题描述 最长递增子序列:英文名为“Longest Increasing Subsequence”,简称“LIS”,就是在序列中找到最长递增子序列,一般常见的有“求解LIS的长度,求解LIS的方案数,打印1个LIS方案”3个细分问题。
二、解题思路 2.1、求解LIS的长度 动态规划解决。 以sequence
指代原序列。 定义:
1 dp[len]=sequence[p],表示最长递增子序列长度为len时,所有len长度最长递增子序列方案中所有第len个元素构成的集合中具有最小值的元素为sequence[p]
构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int len = 1 ;dp[len] = sequence[0 ]; for (int index = 1 ; index < length; index++) { int left = 1 , right = len, mid; while (left <= right) { mid = (left + right) >> 1 ; if (dp[mid] < sequence[index]) { left = mid + 1 ; } else { right = mid - 1 ; } } dp[left] = sequence[index]; if (left > len) len = left; }
只需更新dp[maxlen+1]
为sequence[index]
的证明:
1 2 3 假设n>0,则: 1、dp[maxlen+1+n]不可能更新成sequence[index],否则dp[maxlen+n]<sequence[index],跟“找到最大maxlen”矛盾 2、令“dp[maxlen+1]=sequence[index]”对应的最长递增子序列中第“maxlen+1-n”个元素为sequence[m],则有sequence[m]<sequence[index],又根据dp定义,有dp[maxlen+1-n]<=sequence[m],则最终有dp[maxlen+1-n]<sequence[index],因此dp[maxlen+1-n]不可能更新成sequence[index]
根据上述构造过程,可知算法时间复杂度为O(n*n)
。 dp具有“有序”性质,证明如下:
1 令在dp[len]这个最长递增子序列中第“len-n”个元素为sequence[m],则有sequence[m]<dp[len],又根据dp定义,有dp[len-n]<=sequence[m],则最终有dp[len-n]<dp[len]
根据dp的“有序”性质,上述构造过程中的“找到最大maxlen”步骤显而易见可采用“二分查找法”策略进行优化,从而将算法的时间复杂度优化为O(n*logn)
。
2.2、求解LIS的方案数 定义:
1 2 tail[p]=len,表示以sequence[p]为最长递增子序列末尾元素时,该最长递增子序列的长度为len。tail在求解LIS长度时顺带构造 number[p]=num,表示以sequence[p]为最长递增子序列末尾元素时(此时该最长递增子序列的长度为tail[p]),方案数为num
构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for (int index = 0 ; index < length; index++) { int num = 0 ; for (int preIndex = 0 ; preIndex < index; preIndex++) { if (sequence[preIndex] < sequence[index]) { if (tail[preIndex] + 1 == tail[index]) { num += number[preIndex]; } } else if (sequence[preIndex] == sequence[index]) { if (tail[preIndex] == tail[index]) { number[preIndex] = 0 ; } } } if (num == 0 ) { num = 1 ; } number[index] = num; }
去掉重复方案逻辑正确性的证明:
1 经过逻辑推理可知,重复方案存在且只存在于同时满足“sequence[preIndex] == sequence[index]”和“tail[preIndex] == tail[index]”两个条件的情形中,因此只需对该种情形进行方案去重处理即可
2.3、打印1个LIS方案 获取最简单的一个LIS方案方法如下:
1 2 3 4 5 6 int [] oneSolution = new int [lenOfLIS];for (int index = lenOfLIS; index >= 1 ; index--) { oneSolution[index - 1 ] = dp[index]; } return oneSolution;
dp的“有序”性质确保了上述方法的正确性。
三、变种 变种有:
最长不递减子序列,英文名为“Longest Non-Decreasing Subsequence”,简称“LNDS”
最长递减子序列,英文名为“Longest Decreasing Subsequence”,简称“LDS”
最长不递增子序列,英文名为“Longest Non-Increasing Subsequence”,简称“LNIS”
接下来再探讨LNDS问题解题思路。而LDS和LNIS问题解题思路则分别可由LIS和LNDS问题解题思路简单推得。
3.1、LNDS 3.1.1、求解LNDS的长度 这里构造中须将dp[maxlen]<sequence[index]
改成dp[maxlen]<=sequence[index]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int len = 1 ;dp[len] = sequence[0 ]; for (int index = 1 ; index < length; index++) { int left = 1 , right = len, mid; while (left <= right) { mid = (left + right) >> 1 ; if (dp[mid] <= sequence[index]) { left = mid + 1 ; } else { right = mid - 1 ; } } dp[left] = sequence[index]; if (left > len) len = left; }
这里只需更新dp[maxlen+1]
为sequence[index]
的证明:
1 2 3 假设n>0,则: 1、dp[maxlen+1+n]不可能更新成sequence[index],否则dp[maxlen+n]<=sequence[index],跟“找到最大maxlen”矛盾 2、令“dp[maxlen+1]=sequence[index]”对应的最长不递减子序列中第“maxlen+1-n”个元素为sequence[m],则有sequence[m]<=sequence[index],又根据dp定义,有dp[maxlen+1-n]<=sequence[m],则最终有dp[maxlen+1-n]<=sequence[index],因此dp[maxlen+1-n]或者不可能更新成sequence[index](当dp[maxlen+1-n]<sequence[index]),或者没必要更新成sequence[index](当dp[maxlen+1-n]=sequence[index])
这里dp仍然具有“有序”性质,证明如下:
1 令在dp[len]这个最长不递减子序列中第“len-n”个元素为sequence[m],则有sequence[m]<=dp[len],又根据dp定义,有dp[len-n]<=sequence[m],则最终有dp[len-n]<=dp[len]
3.1.2、求解LNDS的方案数 这里构造中不再需要考虑“方案重复”情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (int index = 0 ; index < length; index++) { int num = 0 ; for (int preIndex = 0 ; preIndex < index; preIndex++) { if (sequence[preIndex] <= sequence[index]) { if (tail[preIndex] + 1 == tail[index]) { num += number[preIndex]; } } if (num == 0 ) { num = 1 ; } number[index] = num; } }
3.1.3、打印1个LNDS方案 跟LIS一致。
四、完整的源代码 完整的源代码见LIS 。
五、另外一种解题思路 5.1、求解LIS的长度 直接根据tail数组求解。 具体代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public int lengthOfLIS () { tail = new int [length]; int totalMax = -1 ; for (int indexA = 0 ; indexA < length; indexA++) { int maxtmp = 1 ; for (int indexB = 0 ; indexB < indexA; indexB++) { if (sequence[indexB] < sequence[indexA]) { if (tail[indexB] + 1 > maxtmp) { maxtmp = tail[indexB] + 1 ; } } } tail[indexA] = maxtmp; if (maxtmp > totalMax) { totalMax = maxtmp; } } lenOfLIS = totalMax; return lenOfLIS; }
上述算法的时间复杂度为O(n*n)
。
5.2、求解LIS的方案数 跟“2.2、求解LIS的方案数”一致。
5.3、打印1个LIS方案 具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 int [] oneSolution = new int [lenOfLIS];int len = lenOfLIS;for (int index = length - 1 ; index >= 0 && len >= 1 ; index--) { if (tail[index] == len) { oneSolution[len - 1 ] = sequence[index]; len--; } } return oneSolution;