摘要:本系列最后一篇训练线性动规的基本套路的随笔,后面进阶更高级的动规类型入门(树形动规、区间动规、背包动规等).
1.正文:以下主要通过几道典型的例题再训练一下线性动规的做法。
复习一下:
(1)题意分析;
(2)基于分析数学建模;
(3)判定是否可以符合使用动规的两大前置条件(最优子结构和无后效性),是则下一步,否则终止(非动规可以解决的问题,另寻他法);
(4)动规基本三步曲:
1)结合题意根据模型选择计算出比较合适的状态转移方程,归约初始的状态值,推导出终止(最终收敛)条件;
2)迭代验证;
3)选择合适的迭代次序实现状态转移方程的迭代和收敛;
(5)编程实现。
2.题目:
上一篇是“子数组类”的例题给出“子数组”的定义:对于给定数组a[0..n],其中a[i..j](0<=i<=j<=n)为该数组的子数组;
本篇类似地,有“子序列类”的例题给出“子序列”的定义:对于给定数组a[0..n],其中(a[i],..,a[j])(0<=i,..,j<=n且a[i]≠..≠a[j])为数组a的一组子序列;
显然,子序列与子数组共同点在于都是有顺序要求(按数组元素排列顺序),区别在于子序列允许元素间不相邻,子数组必须相邻(连续性)。
3.输入输出示例:
1.最长公共子序列
输入:
[2, 1, 4, 3, 5, -1, 8]
输出:
8
2.最长递增子序列:
输入:
[2, 1, 4, 3, 5, -1, 8]
输出: 4
4.例程:
package com.algorithm;
/**
* 子序列类问题
* 1.最长公共子序列;
* 2.最长递增子序列;
* 3.最长路径问题(明天会在评论附上)
*/
public class DynamicProgrammingSolution {
/**
* 1.最长公共子序列
* a.建模?不需要
* b.定义max[i][j]为a[0..i]b[0..j]的最长公共子序列长度,显然状态转移方程有:
* max[i][j] = max{max[i - 1][j], max[i][j - 1], max[i -1][j -1] + (a[i] == b[j] ? 1 : 0)}
* max = max{max[i][j]}
* 初始值为:max[0][j] = a[0] == b[j] ? 1:0 , 终止条件是遍历完成。
* @param a
* @param b
* @return
*/
private static int maxLenCommonSubSequence(int[] a, int[] b) {
if (null == a || null == b) {
return -1;
}
int[][] max = new int[a.length][b.length];
int maxLen = 0;
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
if (a[i] == b[j]) {
max[i][j] = 1;
}
if (i > 0 && j > 0) {
max[i][j] += max[i - 1][j - 1];
}
if (i > 0) {
max[i][j] = Math.max(max[i][j], max[i - 1][j]);
}
if (j > 0) {
max[i][j] = Math.max(max[i][j], max[i][j - 1]);
}
maxLen = Math.max(maxLen, max[i][j]);
}
}
return maxLen;
}
/**
* 2.最长递增子序列
* a.建模?不需要
* b.max[i]定义为a[i]为末尾元素的子序列中最长的递增子序列,故有方程:
* max[i] = max{max[k]{a[k] < a[i]}}{k:0..i-1};
* max = max{max[i]}
* 初始值为max[i] = 1, 终止条件是正向迭代计算完成。
* @param a
* @return
*/
private static int maxIncrementSubSequence(int[] a) {
if (null == a) {
return -1;
}
int maxLen = 0;
int[] max = new int[a.length];
for (int i = 0; i < a.length; i++) {
//初始时a[i]本身就是一个递增序列
max[i] = 1;
//计算a[i]之前符合a[k]比a[i]小的最大的递增序列
int leftMax = 0;
for (int k = 0; k < i; k++) {
if (a[k] < a[i]) {
leftMax = Math.max(max[k], leftMax);
}
}
//得到最终的迭代值
max[i] += leftMax;
//比较大小获取最大值
maxLen = Math.max(maxLen, max[i]);
}
return maxLen;
}
public static void main(String[] strings) {
int[] ints = {2, 1, 4, 3, 5, -1, 8};
int[] int2 = {2, 1, 4, 3, 5, 2, 8};
System.out.println(maxIncrementSubSequence(ints));
System.out.println(maxLenCommonSubSequence(ints, int2));
}
}
与上篇是兄弟篇,同样上述题目都已经建好模了,都是经典例题,比较简单,不再赘述,不懂的地方请认真看看注释能否释疑,有问题欢迎留言。
5.总结:
事实上,“子数组”、“子序列”这两个概念在数组类的题目有着挺高的曝光率,很多经典的题型都有它们的身影,建议初学者一定要区分好这两个概念。另外,这是近期最后一篇线性动规的归档,后面将进入树形动规的入门,希望有问题可以多多交流。