最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现
关于最长递增子序列时间复杂度O(n^2)的实现方法在博客http://blog.csdn.net/iniegang/article/details/47379873(最长递增子序列 Java实现)中已经做了实现,但是这种方法时间复杂度太高,查阅相关资料后我发现有人提出的算法可以将时间复杂度降低为O(nlogn),这种算法的核心思想就是替换(二分法替换),以下为我对这中算法的理解:
假设随机生成的一个具有10个元素的数组arrayIn[1-10]如[2, 3, 3, 4, 7, 3, 1, 6, 6, 4],求这个数组的最长递增子序列。
首先定义一个数组arrayOut[1-10]来逐个寻找arrayIn[1-10]中以第i个元素结尾的最长递增子序列的长度。
定义len来计算相应的长度
(1)将arrayIn[1]放入arrayOut,此时arrayOut[1]=arrayIn[1]=2,此时len=1;
(2)将arrayIn[2]放入arrayOut,此时要先寻找arrayIn[2]应该放入的位置,由于arrayIn[2]=3>arrayOut[1]=2,那么arrayIn[2]应该放入的位置为arrayOut[2],这时arrayOut[2]=arrayIn[2]=3,此时len=2;
(3)将arrayIn[3]放入arrayOut,此时要先寻找arrayIn[3]应该放入的位置,由于arrayIn[3]=3=arrayOut[2]=3,那么arrayIn[3]应该放入的位置为arrayOut[2],这时arrayOut[2]=arrayIn[3]=3,即此时进来的arrayIn[3]替换掉了arrayOut[2],此时len仍然为2;
(4)对数组arrayIn的后续元素执行以上类似的操作即
如果arrayIn要放入的元素比arrayOut最后一个元素大的话就放在其后;
否则寻找一个替换的位置
这样以来arrayIn元素放入的位置即为len的值,然后判断这次得到的len值与上次的len值的大小,向大的方向更新即可。
使用二分法来查找arrayIn元素应该放入的位置即可将时间复杂度降为O(nlogn)。
以下为具体的实现代码(java)
import java.util.Arrays;
import java.util.Random;
public class LISUpdate {
public static void main(String[] args){
System.out.println("Generating a random array...");
LISUpdate lisUpdate=new LISUpdate();
int[] oldArray=new int[10];
oldArray=lisUpdate.randomArray();
System.out.println(Arrays.toString(oldArray)); //输出生成的随机数组
System.out.println("each LIS array:"); //输出每次计算时arrayOut数组的内容,便于观察
System.out.println("LIS length nlogn is:"+lisUpdate.getLength(oldArray)); //输出最长递增子序列的长度
}
public int[] randomArray(){ //生成一个10以内的数组,长度为10
Random random=new Random();
int[] randomArray=new int[10];
for (int i = 0; i < 10; i++) {
randomArray[i]=random.nextInt(10);
}
return randomArray;
}
public int BinarySearchPosition(int arrayOut[],int left,int right,int key){ //二分查找要替换的位置
int mid;
if (arrayOut[right]<key) {
return right+1;
}else {
while(left<right){
mid=(left+right)/2;
if (arrayOut[mid]<key) {
left=mid+1;
}else {
right=mid;
}
}
return left;
}
}
public int getLength(int[] arrayIn){ //获取最长递增子序列的长度
int position;
int len=1;
int[] arrayOut=new int[arrayIn.length+1];//arrayOut[0]没有存放数据
arrayOut[1]=arrayIn[0]; //初始化,长度为1的LIS末尾为arrayIn[0]
for (int i = 1; i < arrayIn.length; i++) {
position=BinarySearchPosition(arrayOut, 1, len, arrayIn[i]);
arrayOut[position]=arrayIn[i];
System.out.println(Arrays.toString(arrayOut));
if (len<position) {
len=position;
}
}
return len;
}
需要注意的是:上面代码中输出的arrayOut数组并不是最长递增子序列,我这里选择将其输出只是为了验证算法的执行过程。
对于求最长递减子序列,则可以直接将原数组进行“反转”操作,然后求出反转之后的数组的最长递增子序列的长度即为最长递减子序列的长度。