在一个乱序的数组中找到最长的递增子序列

这个题是牛客上左程云讲其他俄国沙皇问题是提及到的一个算法原型。代码中给出了两个思路。时间复杂度分别为 N^2 和 NlogN,源代码中没有给出注释,自己照着思路又重新捋了遍程序,稍微加了点注释,方便理解。

原题目的讲解在牛客网公开课http://www.nowcoder.com/live/11/1/1

以下是代码

public class Problem_05_LIS {

	public static int[] lis1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return null;
		}
		int[] dp = getdp1(arr);
		return generateLIS(arr, dp);
	}
//dp中放的是,对应数组中每个元素为输出序列的最后一个值时的最大长度
//方法一:O(n^2)复杂度,具体思路是:两层循环,外层为i时,内从从0到i,依次判断,当加入 arr[i]的值后,更新dp中最长子序列的长度,最长
//子序列更新,依赖于i前面的dp中的数组值,若是arr[i]比i之前的某个位置上的的数要大,则通过max(dp[i], dp[j] + 1);来
//更新第i位上的值。
	public static int[] getdp1(int[] arr) {
		int[] dp = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			dp[i] = 1;
			for (int j = 0; j < i; j++) {
				if (arr[i] > arr[j]) {
					dp[i] = Math.max(dp[i], dp[j] + 1);
				}
			}
		}
		return dp;
	}

	//根据 dp 数组的值生成要打印的数组,作为最后结果的输出,
	public static int[] generateLIS(int[] arr, int[] dp) {
		int len = 0;
		int index = 0;
		for (int i = 0; i < dp.length; i++) {
			if (dp[i] > len) {
				len = dp[i];
				index = i;
			}
		}
		int[] lis = new int[len];
		lis[--len] = arr[index];
		for (int i = index; i >= 0; i--) {
			if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
				lis[--len] = arr[i];
				index = i;
			}
		}
		return lis;
	}

	public static int[] lis2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return null;
		}
		int[] dp = getdp2(arr);
		return generateLIS(arr, dp);
	}

//方法二,时间复杂度为O(N*logN),这个时间复杂度具体体现在一个for循环里套了一个二分查找
//dp中放的是,对应数组中每个元素为输出序列的最后一个值时的最大长度
//ends数组作为一个辅助数组,存放的是有效区的数值,ends[i]的含义是:
//当遍历到当前的数时,ends[i]表示的是,遍历到当前时刻,长度为i+1的子序列中的末尾元素值
//每次更新的是长度为i+1子序列的末尾元素的值,那么,可以通过二分法查找每个当前遍历到的元素在ends中应该所处的位置
//然后更新对应的位置上的元素值,然后根据i+1,就可以知道,当前元素作为子序列的末尾元素时,前面有几个数了(i)
//程序中的right值记录的是,有效区数组ends的长度,l为左边界,r为右边界,m为中间值
	public static int[] getdp2(int[] arr) {
		int[] dp = new int[arr.length];
		int[] ends = new int[arr.length];
		ends[0] = arr[0];
		dp[0] = 1;
		int right = 0;
		int l = 0;
		int r = 0;
		int m = 0;
		for (int i = 1; i < arr.length; i++) {
			l = 0;
			r = right;
			while (l <= r) {
				m = (l + r) / 2;
				if (arr[i] > ends[m]) {
					l = m + 1;
				} else {
					r = m - 1;
				}
			}
			right = Math.max(right, l);  //更新有效区数组的边界值,若是所有的元素都小于当前的元素,则数组值往外扩充一个
			ends[l] = arr[i];
			dp[i] = l + 1;
		}
		return dp;
	}

	// for test
	public static void printArray(int[] arr) {
		for (int i = 0; i != arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] arr = { 2, 1, 5, 3, 6, 4, 8, 9, 7 };
		printArray(arr);
		printArray(lis1(arr));
		printArray(lis2(arr));

	}
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值