最长递增子序列

一个整型数组,求其中最长递增子序列的长度。

简单的想法:从前到后遍历数组,对于每一个元素,从该元素开始往前判断是不是大于前面的元素,如果大于,则根据是否大于当前位置的最长子序列长度,相应的更新当前这个子序列的长度。

代码如下:

package a;

public class CopyOfTest1 {

	public static void main(String ss[]) {
		System.out.println(get(new int[] { 1, -1, 2, -3, 4, -5, 6, -7 }));
	}

	public static int get(int[] data) {
		int[] len = new int[data.length];// 记录最长信息
		for (int i = 0; i < len.length; i++) {
			len[i] = 1;
		}
		for (int i = 0; i < data.length; i++) {
			for (int j = i - 1; j >= 0; j--) {
				if (data[i] > data[j] && len[i] < len[j] + 1) {
					len[i] = len[j] + 1;
				}
			}
		}
		int max = -1;
		for (int i = 0; i < len.length; i++) {
			if (max < len[i]) {
				max = len[i];
			}
		}
		return max;
	}
}

上面的这个方法的时间复杂度为O(n^2)。这是一个基本的算法。

-----------------------------------------------------------------------------

另外一个想法是,对于一个未排序的数组,可以用LCS算法来求最长递增子序列。那么求的过程是这样的:

将原数组A排序。排序之后的数组记为B,求A和B的最长公共子序列,就是这里要的结果了。但是这样的时间复杂度和空间复杂度就非常高了。

----------------------------------------------------------------------------

下面的 部分参考例子这里:http://www.ahathinking.com/archives/117.htmlhttp://blog.youkuaiyun.com/sunmenggmail/article/details/7599744

目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。

方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。

代码如下:

package a;

public class CopyOfTest1 {

	public static void main(String ss[]) {
		// int[] data = new int[] { 1, 2, 3, 4, 5, 6 };
		// int[] data = new int[] { 1, 3, 5, 2, 4, 6, 7, 8 };
		int[] data = new int[] { 1, 5, 8, 3, 6, 7 };
		// int[] data = new int[] { 1, -1, 2, -3, 4, -5, 6, -7, 8, 9, 10 };
		System.out.println(get2(data));
	}

	public static int binsearch(int[] data, int low, int high, int key) {
		int mid = -1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (data[mid] <= key) {
				low = mid + 1;
			} else {
				high = mid - 1;
			}
		}
		return low;
	}

	public static int get2(int[] data) {
		int[] Max = new int[data.length];
		int len = 1;
		Max[0] = data[0];
		int i;
		for (i = 1; i < data.length; i++) {
			if (data[i] > Max[len - 1]) {
				Max[len++] = data[i];
			} else {
				int pos = binsearch(Max, 0, len - 1, data[i]);
				Max[pos] = data[i];
			}
		}
		return len;
	}

	public static int min(int a, int b) {
		return a < b ? a : b;
	}
}

上面的代码这样描述会更好理解一点:

开一个数组max,下标为0的位置设置为data[0],对于从1开始的每一位元素,

如果大于max数组最后一个位置的元素,则max数组长度增1,设置为当前的这个元素

如果不大于max数组最后一个位置的元素,则从max的最后一个元素开始往前找,替换第一个大于当前元素的那个值。

这样,最终max数组的长度就是最长递增子序列的长度。

算法的时间复杂度是O(n*log(len)),其中len是递增子序列的最大长度。

--------------------------------

上面的代码输出了最长递增子序列的长度。

但是如果想输出最长递增子序列本身呢?(要求输出一个子序列即可)

其实在上面的代码中也是很容易做到的。

代码中,使用了Max数组记录长度为len的时候的最大值。只要此时把这个最大值在数组中的位置记录下来(使用maxindex数组),那么在代码运行到最后的时候,maxindex数组中记录的就是长度为i的时候的最大递增子序列的最大元素在数组中所在的位置。去数组中根据maxindex数组中的位置输出相应的数字,就是一耳光最大递增子序列了。

看下面代码和输出:

package comz;

public class TT {

	public static void main(String ss[]) {
		// int[] data = new int[] { 1, 2, 3, 4, 5, 6 };
		// int[] data = new int[] { 1, 3, 5, 2, 4, 6, 7, 8 };
		int[] data = new int[] { 1, 5, 8, 3, 6, 7, 2, 9 };
		// int[] data = new int[] { 1, -1, 2, -3, 4, -5, 6, -7, 8, 9, 10 };
		System.out.println("最大长度  "+get2(data));
	}

	public static int binsearch(int[] data, int low, int high, int key) {
		int mid = -1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (data[mid] <= key) {
				low = mid + 1;
			} else {
				high = mid - 1;
			}
		}
		return low;
	}

	public static int get2(int[] data) {
		int maxindex[] = new int[data.length];
		int[] Max = new int[data.length];
		int len = 1;
		Max[0] = data[0];
		int i;
		for (i = 1; i < data.length; i++) {
			if (data[i] > Max[len - 1]) {
				Max[len] = data[i];
				maxindex[len] = i;
				len++;
			} else {
				int pos = binsearch(Max, 0, len - 1, data[i]);
				Max[pos] = data[i];
			}
		}
		// 输出最长递增子序列
		for (int j = 0; j < len; j++) {
			System.out.println(data[maxindex[j]]);
		}
		return len;
	}

	public static int min(int a, int b) {
		return a < b ? a : b;
	}
}

看输出:

1
5
8
7
9
最大长度  5

可以看到输出了一个最长递增子序列。 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值