【动态规划】最长上升子序列

 

 

思路 :

最长上升子序列就是在一系列数中找到一段最长的上升的序列,这个序列不一定要连续, 但得按每位数得在数列中出现的顺序排序

                当从1往前递推到i位数时,我们不知道第i-1的最长子序列是什么这里假设为ak,那么我们只需要判断  i 这个数和ak中的最大数的大小关系,如果大于ak(max)那么显然i这个数可以衔接上ak这个序列,那么就更新序列,否则就不更新序列

                这是一种通过i-1序列来判断i序列的问题,也就是通过最优子结构来递推的问题一般都是用动态规划来解决,那么动态规划就得写出状态转移方程

        状态转移方程 :

        我们设f[i] = 第i位数的最长子序列长度

        我们通过遍历序列,当遍历到第i个数时,我们通过依次比较1..i-1每个数的大小关系,

        如果ak[i]>ak[i-1]的话,这第i个数可以衔接上这个序列,但不一定是最长的序列,所以我们要不断比较找到最长的序列

        

public class 最长上升子序列 {
	public static void main(String[] args){
		Scanner sr = new Scanner(System.in);
		int n = sr.nextInt();//给定数组的长度
		int[] num = new int[n+1];//戈丁的无序整数数组
		for(int i=1;i<=n;++i){
			num[i]=sr.nextInt();
		}
		int [] dp = new int[n+1];//以a[i]为结尾的最长上升子序列
		Arrays.fill(dp, 1);
		int max =-1;
		for(int i=2;i<=n;++i){
			for(int j=i;j>=1;--j){
				if(num[i]>num[j]){
					dp[i]=Math.max(dp[j]+1, dp[i]);
					if(dp[i]>max)max=dp[i];
				}
			}
		}
		System.out.print(max);
	}
}

优化 :

        上面的做法虽然是用动态规划完成的,但是时间复杂度还是比较高为n^3,所以必须得优化,那么我们可以通过维护一个最长上升子序列来进行优化,上一个算法是通过不断比较每个元素来判断该序列是否为最长上升子序列,那么我们不妨直接开设一个res数组来记录最长的子序列,当第i个元素的值大于数组的最后一个元素时,说明该元素可以添加到上升序列的末尾,那么就添加到数组尾部,反之则找到res数组中第一个大于i的数j,并将第j个数用第i个数替换,这样保证了第1..j是最优且递增的,所以我们遵循大则添加小则替换的原则来更新数组,这里通过二分查找法找到res数组中第一个大于i的数并替换

public class 最长上升子序列_二分法 {
	static int len = 0;
	static int[] f ;
	static int[] a;
	public static void main(String[] args) {
		 /*
		  		2 5 9 4 6 7 1 7 2
		  		1、相较于之前的双重循环遍历来说,二分查找法大大降低了时间复杂度
		  		2、通过维护一个最长且最优上升数组来达到目的
		  		3、原则:
		  			大则添加:
		  				如果a[i] > b[len] 直接添加到b数组的最后并且数组最长上升子序列+1
		  			小则替换:
		  				首先明白一点,如果最长上升子序列的最后一个数越小,那么他就越有潜力增加序列
		  				那么我们拿到一个在增序列中仅比最后一个元素小的数我们应该用这个较小的数替换当前数
		  				如果a[i]小于或等于b[len]那么就替换掉第一个大于a[i]的元素,这样保证1~i这个上升子序列结尾元素更小
		  			保证让这个上升子序列的结尾元素更小
		  */
		Scanner sr = new Scanner(System.in);
		int N = sr.nextInt();
		a = new int[N+1];//无序数组
		f = new int[N+1];//有序数组
		for(int i=1;i<=N;++i) {
			a[i] = sr.nextInt();
		}
		for(int i=1;i<=N;++i) {//通过遍历a 数组 
			if(a[i] > f[len]) {//如果大于直接插入
				f[++len] = a[i];
			}else {
				int k = find(a[i]);//通过二分查找,找到第一个大于他的元素,得到这个元素的交表
				f[k] = a[i];//覆盖掉该元素
				
			}
		}
		System.out.println(len);
	}

	private static int find(int x) {
		// TODO Auto-generated method stub
		int mid = 0,L,R;//三个变量记录不同的指针
		L = 1;
		R = len;
		while(L<=R) {//L在递增,R在递减那么出循环的条件就是L>R
			mid = (L+R)/2;
			if(x>f[mid]) {//大于中值元素,说明在右边
				L = mid+1;//往右边找
			}else {//小于或等于中值元素就要往左边找
				R = mid-1;
			}// 2 4 8 9 10 11 12    //7
		}
		return L;
	}
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值