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

题目描述

给定N个数,求这N个数的最长上升子序列的长度。

样例输入

7
2 5 3 4 1 7 6

样例输出

4

思路:动态规划
一.惯性思维版(多此一举版)
1.状态与定义

状态1:面对第i个数字
状态2:上一次选择的数字位置 j
dp[ i ][ j ]: 面向第i位数字,以第j位数字结尾时的最长上升子序列长度

2.状态转移方程

对于每个数字位置i可以选择或者不选择该数字:

选择i:

处于第i位数字并以k结尾时,必然从k前面的第j位作为序列结尾使得序列最长并+1即可,在第j位选择的基础上,序列尾部再接上第k位,序列长度+1

dp[i][k] = max(dp[i][j]+1) 0<i<=n , 0<j<i , 0<k<j 且 a[ j ]>a[ k ]
不选择i:

不选择时,默认长度为1即可

dp[i][j] = 1
3.初值
dp[ i ][ j ] = 1 0<i,j<=n
代码
/***
状态冗余版:
1.状态 
	状态1:面对第i个数字
	状态2:上一次选择的数字位置
	dp[i][j]:面对第i个数字,且序列以第j位为末尾时的最长序列长度
2.状态转移方程
	选择i:
		dp[i][j] = max(dp[i][k])+1 ,0<j<i,0<k<j 且a[j]>a[k] 
		(注意:这里容易出错,很容易觉得是 dp[i][j] = max(dp[i-1][j])+1,0<j<i即在面对i-1个数字决策时最长的一个,
		实际上位置i与i-1在最长上升序列长度关系上没有任何关系
	    例如序列:3 4 3 ,i = 3 时若选择3,此时的最长长度为1即 dp[3][j] = 1,0<j<3
		而不是max(dp[i-1][j]) = max(dp[3-1][1],dp[3-1][2])=2.产生这个误解的原因是没有理解dp[i][j]的含义,
		dp[i][j]已经明确了是以j结尾的最长子序列长度,这个长度应该是max(上次序列长度)+1 
		因为本次长度只在上一个序列长度基础上+1,而不是在位置i-1 上 +1,正是因为i冗余导致了混淆)
		
	不选i: 
		dp[i][j] = 1
		
3.初值
 	dp[i][j] = 1    1<=i,j<=n  
**/
#include <bits/stdc++.h>
#define maxlen 1001

using namespace std;
int a[maxlen];
int dp[maxlen][maxlen];

int main() {
	int n,maxn = 1;
	cin>>n;
	for(int i = 1 ;i<=n; i++){
		cin>>a[i];	
		//赋予初值
		for(int k = 1 ;k<=n ;k++)
			dp[i][k] = 1;
	}
	//枚举每个数字
	for(int i = 1 ;i<= n;i++){
		//本次:枚举序列所有结尾的数字位置k
		for(int k = 1 ;k<i ;k++)
			//上一次:枚举上一次结尾数字的位置j,本次 = max(上一次)+1
			for(int j = 1 ;j<k ;j++){
					if(a[k]>a[j])
						dp[i][k] = max(dp[i][j]+1,dp[i][k]);
				maxn = max(dp[i][k],maxn);
			}
	}
		
	cout<<maxn<<endl;

   return 0;
}

二.正确的动态规划 O(n^2)
1.状态与定义

实际上,当前所在数字位置 i 对问题没有意义,因为序列的长度是在上一个选择的末尾再加一个新的数字,而不是在第i-1位数字上进行决策。(假设:上次最长序列的末尾是第j位,这次应该是在第j位的基础上新增当前的第i位数字,当然 j 可以等于i-1但不一定是i-1)
————————————————————————————————
状态1:以【第i位数字】结尾
dp[ i ]: 以第i位数字结尾时的最长上升子序列长度

2.状态转移方程

本次序列最长长度 = 上次最长序列长度+1

dp[i] = max(dp[ j ])+1 , 0<j<i 且 a[ i ]>a[ j ]
3.初值
dp[ i ]= 1 ,0<i<=n
代码
#include <bits/stdc++.h>
#define maxlen 1001

using namespace std;
/***
动态规划1
1.定义数组含义
	序列末尾数字位置 
		dp[i]:以第i个数字结尾时的最长上升子序列长度 
2.状态转移方程
	选当前:
		dp[i] = max(dp[j])+1   0<j<i 且 a[i]>a[j]
	不选当前: 
		dp[i] = 1
			 
3.初值
	dp[i] = 1
	 
**/
/****
int a[maxlen];
int dp[maxlen];

int main() {
	int n,maxn = 1;
	cin>>n;
	for(int i = 1 ;i<=n; i++){
		cin>>a[i];
		dp[i] = 1;
	}
	//   a   1 2 3 1 1 1
	//1: dp  1 1 1 1 1 1
	//2: dp  1 2
	//3: dp  1 2 3 1 1 1
	for(int i = 2;i<=n; i++){
		for(int j = 1 ;j<i ;j++){ 
			if(a[i]>a[j])
				dp[i] = max(dp[i],dp[j]+1);	
		}
		maxn = max(maxn,dp[i]);
	}
		
	cout<<maxn<<endl;

   return 0;
}
三、蜘蛛纸牌法(二分法) O(nlogn)
思路介绍:

类似蜘蛛纸牌的规则,对于每个数字,如果在堆中无法放置接龙,则新建一个堆放置。最后堆的数量就是最长子序列长度。(每个堆的放置规则为:新加入该堆的数字必须比原来的大,否则不能放置。由于堆头的数字有序,所以可以使用二分查找)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值