前置知识
lower_bound(开始位置,结束位置,查找的数)返回小于等于查找数的第一个数的地址
upper_bound(开始位置,结束位置,查找的数)返回比查找数小的第一个数的地址
最长上升子序列朴素解法O(n²)
例: arr[i]: 8 4 2 5 3 9 1 6 7
例: dp[i]: 1 1 1 2 2 3 1 3 4
对于这组数据开一个dp数组,用于存当前位置的最大上升子序列长度,先看一段代码
//arr[]为输入的数组,n为数组长度,ans为最长上升子序列长度
for(int i=0; i<n; i++) {
for(int j=i; j>=0; j--) {
if(arr[i]>=arr[j]) {
dp[i]=max(dp[i],dp[j]+1);
ans=max(ans,dp[i]);
}
}
}
对arr数组中的每一个数都要倒序向前找一次最大的dp值,dp[i]=max(dp[i],dp[j]+1)是找到0到i-1中最大dp值,因为在arr[i]>=arr[j]与从后往前两个条件下找到的序列一定是上升的序列,倒序是因为要将数加入序列一定是从后往前比较,如果从前往后比较就成了找前边有几个数比这个数大了。
最长上升子序列二分解法O(nlogn)
还是用上边的例子:arr[i]: 8 4 2 5 3 9 1 6 7
这里用到了数据结构中的栈,先创建一个空栈,再将数组的第一个数入栈,再遍历一遍数组,遍历过程中,如果当前数小于栈顶元素,则在栈中找到比当前数大的第一个数并替换掉他,如果当前数大于栈顶元素则将该元素入栈,最后栈里留下的就是最长上升子序列,如果入栈的判定条件是大于等于,则就成了最长不下降子序列,附上代码理解。
//这里采用的数组模拟栈,len初始为0,最后len存储的是最长上升子序列的长度
memset(low,0x3f,sizeof low);
low[len++]=arr[0];
for(int i=1;i<n;i++){
if(arr[i]>low[len-1])low[len++]=arr[i];
else if(arr[i]<low[len-1])low[lower_bound(low,low+len,arr[i])-low]=arr[i];
//这里改用upper_bound的话就是最长不下降子序列
}
朴素解法在一些极端情况下可能会超时,采用二分能将查找的时间复杂度由O(n)缩减到O(logn)
最长公共子序列问题
原理上这个问题是用动态规划解决的,首先看一下状态转移方程:
//str1与str2为将要进行操作的起始下标为1的两个字符串,dp[][]为初始为0的二维数组
for(int i=1;i<=str1.length();i++){
for(int j=1;j<=str2.length();j++){
if(str1[i]==str2[j])dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
//dp[str1.length()][str2.length()]就是最长公共子序列的长度
例: str1: aabcddd
例: str2: acdde
则整个过程中dp数组的信息如下表:
dp | i=1 | i=2 | i=3 | i=4 | i=5 | i=6 | i=7 |
j=1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
j=2 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
j=3 | 1 | 1 | 1 | 2 | 3 | 3 | 3 |
j=4 | 1 | 1 | 1 | 2 | 3 | 4 | 4 |
j=5 | 1 | 1 | 1 | 2 | 3 | 4 | 4 |
简而言之就是动态规划源于上一步的思想,若str1[i]与str2[j]匹配则可以直接继承str1[i-1]与str2[j-1]的状态,不匹配则在str1[i]与str2[j-1]和str1[i-1]与str2[j]的两种匹配状态中取最优解。
希望我的理解能帮到各位。