题目:让我们讨论最长增加子序列(LIS)问题作为一个可以使用动态规划解决的例子问题。最长增加子序列(LIS)问题是查找给定序列的最长子序列的长度,使得子序列的所有元素按升序排列。例如,{10,22,9,33,21,50,41,60,80}的LIS长度是6,LIS是{10,22,33,50,60,80}。
这句话听起来是不是很绕? 简单来说,就是对一个数组进行大小比较,如果后面那个值大于当前值,则加1,如果后面那个值小于当前的话,则进行下一个的比较,LIS不变,最后作为LIS的长度并返回
例如:
首先我们在主方法里面创建一个数组:
然后我们再创建一个方法函数:lis(int[] arr,int n)
其中: arr是我们已经创建好的数组,n是数组的长度
接下来写我们函数里面的代码,我们在lis函数里面创建一个lisArr数组 记忆我们用来比较数值的数量
定义循环变量i,j 以及最大值MAX, 其中i,j用于循环,也就是用于arr数组索引对数组里面的值进行比较的索引变量,MAX是得到算好的LIS数组里面的最大数值,在写代码前我们必须要了解两点,第一:重叠子问题,第二,最优子结构
1)重叠子问题:
考虑到上面的实现,以下是大小为4的数组的递归树。lis(n)给出了arr []的LIS长度。
LIS(4)
/ |
lis(3)lis(2)lis(1)
/ /
lis(2)lis(1)lis(1)
/
LIS(1)
我们可以看到,有很多问题一次又一次得到解决。因此,此问题具有重叠子结构属性,可以通过使用Memoization或Tabulation来避免相同子问题的重新计算。以下是LIS问题的一个列表实现。
2)最优子结构:给定问题具有最优子结构性质,如果给定问题的最优解可以通过使用子问题的最优解得到。
例如,最短路径问题具有以下最佳子结构属性:
如果节点x位于从源节点u到目的节点v的最短路径,则从u到v的最短路径是从u到x的最短路径和最短路径从x到v的路径。像Floyd-Warshall和Bellman-Ford这样的标准的所有对最短路径算法都是动态规划的典型例子。
另一方面,最长路径问题没有最佳子结构属性。这里的“最长路径”是指两个节点之间最长的简单路径(没有周期的路径)。考虑CLRS书中给出的以下未加权图。从q到t有两条最长的路径:q→r→t和q→s→t。与最短路径不同,这些最长的路径不具有最佳的子结构属性。例如,最长路径q→r→t不是从q到r的最长路径和从r到t的最长路径的组合,因为从q到r的最长路径是q→s→t→r,最长路径从r到t是r→q→s→t。
详细代码如下
static int lis(int arr[],int n) {
int[] lis = new int[n];
int i,j,MAX = 0;
for (i = 0; i < lis.length; i++) {
lis[i] = 1;
}
for (i = 1; i < n; i++) {
for (j = 0; j < i; j++) {
if(arr[i] > arr[j] && lis[i] < lis[j] + 1) {
lis[i] = lis[j] + 1;
}
}
}
//找到LIS最大值返回
for (i = 0; i < lis.length; i++) {
if(MAX < lis[i]) {
MAX = lis[i];
}
}
return MAX;
}
简单对这代码讲解一下
for (i = 1; i < n; i++) {
for (j = 0; j < i; j++) {
if(arr[i] > arr[j] && lis[i] < lis[j] + 1) {
lis[i] = lis[j] + 1;
}
}
}
Arr[i] > arr[j] = 22 > 10 后面那个数大于前面那个数 我们则在Lis数组里面给下标为i的变量加1赋值,1+1=2 以此类推下去,发现大于当前的数,则继续+1操作,当作最长子序列的计数 22 > 10 ? yes 则 Lis[i] = Lis[j]+1 否则i继续循环下一个做比较,最后就得出Lis的最大值了。
输出结果: