1. 最长上升子序列(LIS)
题意:给定一个序列,求出最长的上升(递增)子序列,注意:不一定要连续
解法:
动态规划:
创建一个dp数组,dp[i]表示以i
结尾的最长上升子序列的长度,开两层循环求dp数组即可,具体思路看代码理解。
代码:
int LIS(int a[],n)
{
int dp[MAX];
for(int i = 0; i < n; i++)
{
for(int j = i; j < n; j++)
{
if(a[j] > a[i])
{
dp[j] = max(dp[i]+1,dp[j]);
}
}
}
int maxValue = 0;
for(int i = 0; i < n; i++)
{
maxValue = max(maxValue,dp[i]);
}
return maxValue;
}
2. 最大递增子序列的和
题意:跟上一题差不多,就只是求个和。
代码:
int Max_LIS_Sum(int a[],int n)
{
int dp[MAX];
int maxn = a[0];
for(int i = 0; i < n; i++)
{
dp[i] = a[i];
}
for(int i = 1; i < n; i++)
{
for(int j = 0; j < i; j++)
{
if(a[i] > a[j])
{
dp[i] = max(dp[i],dp[j] + a[i]);
if(dp[i] > maxn)
{
maxn = dp[i];
}
}
}
}
return maxn;
}
3.最大连续和
题意:给定一个数组,求出最大的连续和,注意:这里指定是连续和
解法:
1. 枚举起点和终点 - O(n3)
2. 求出前缀和,然后再枚举起点和终点 - O(n2)
3. 分治法 - O(nlogn):
最大连续和一定是左边最大连续和 or 右边最大连续和 or 左边右边各取一部分的连续和 的最大值,然后具体到左边和右边,又可以分成这三种情况求最大值。
4. 动态规划 - O(n):
最大连续和一定是以i结尾的,所以只要求出每个以i结尾的最大连续和即可,从dp数组里面找出最大值就是序列的最大连续和。
5. 对方法2的优化 - O(n):
若最大连续和以m为起点,n为终点,即sum[n] - sum[m],sum[m]必然是sum[1]…sum[n-1]中的最小值**(不理解)**,所以只需记录下之前的最小值即可。
代码:
// 1. 枚举起点和终点
int GetMaxSum_n3(int * a,int n)
{
int maxn = -INF;
for(int i = 0; i < n; i++)
{
for(int j = i; j < n; j++)
{
int sum = 0;
for(int k = i; k <= j; k++)
{
sum = sum + a[k];
}
if(sum > maxn)
{
maxn = sum;
}
}
}
return maxn;
}
// 2. 求出前缀和,然后再枚举起点和终点
int GetMaxSum_n2(int * a,int n)
{
int sum[MAX];
memset(sum,0,sizeof(sum));
sum[0] = 0;
for(int i = 1; i <= n; i++)
{
sum[i] = a[i - 1] + sum[i - 1];
}
int maxn = -INF;
for(int i = 1; i <= n; i++)
{
for(int j = i; j <= n; j++)
{
if(sum[j] - sum[i - 1] > maxn)
{
maxn = sum[j] - sum[i - 1];
}
}
}
return maxn;
}
//3. 分治法
int Max(int x,int y,int z)
{
if(x > y)
{
if(x > z)
{
return x;
}
else
{
return z;
}
}
else
{
if(y > z)
{
return y;
}
else
{
return z;
}
}
}
int GetMaxSum_nlogn(int * a,int left,int right)
{
if(left == right)
{
return a[left];
}
int mid = left + (right - left) / 2;
int leftMax = GetMaxSum_nlogn(a,left,mid); /** 左边最大值 */
int rigthMax = GetMaxSum_nlogn(a,mid+1,right); /** 右边最大值 */
int sum = 0;
int leftmaxn = a[mid];
for(int i = mid; i >= left; i--)
{
sum = sum + a[i];
if(sum > leftmaxn)
{
leftmaxn = sum;
}
}
sum = 0;
int rightmaxn = a[mid + 1];
for(int i = mid + 1; i <= right; i++)
{
sum = sum + a[i];
if(sum > rightmaxn)
{
rightmaxn = sum;
}
}
return Max(leftMax,rigthMax,leftmaxn + rightmaxn);
}
// 4. 动态规划
int dp[MAX];
int GetMaxSum_n_1(int * a,int n)
{
/** 其实这里不用dp数组也可以,只要用原始的a数组记录下就可以了,以达到空间的最大优化 */
memset(dp,0,sizeof(dp));
for(int i = 0; i < n; i++)
{
dp[i] = a[i];
}
int maxn = -INF;
for(int i = 1; i < n; i++)
{
dp[i] = max(0,dp[i - 1]) + dp[i];
if(dp[i] > maxn)
{
maxn = dp[i];
}
}
return maxn;
}
// 5. 对方法2的优化
int GetMaxSum_n_2(int * a,int n)
{
int sum[MAX];
memset(sum,0,sizeof(sum));
int maxn = a[0];
int minn = 0;
sum[0] = 0;
for(int i = 1; i <= n; i++)
{
sum[i] = a[i - 1] + sum[i - 1];
if(sum[i] - minn > maxn)
{
maxn = sum[i] - minn;
}
if(sum[i] < minn)
{
minn = sum[i];
}
}
return maxn;
}
4. 最长公共子序列(LCS)
题意:给定两个数组,求出最长公共子序列的长度(有的题目可能还要直接求出具体的序列)。
解法:
1. 递归解法
从两个数组的末尾开始比较,一步步地向前递减,每次做的判断:
如果两个数相等,则都下标都-1,对应的+1
如果两个数不相等,则返回较大的值,具体看代码吧
2. 动态规划解法
思路类似,具体看下面的代码
代码:
/** 递归求解最长公共子序列的长度(LCS) */
int RecursiveForLCSLength(char * x,char * y,int i,int j)
{
if(i == 0 || j == 0)
{
return 0;
}
if(i > 0 && j > 0 && x[i] == y[j])
{
return RecursiveForLCSLength(x,y,i - 1,j - 1) + 1;
}
if(i > 0 && j > 0 && x[i] != y[j])
{
return max(RecursiveForLCSLength(x,y,i - 1,j),RecursiveForLCSLength(x,y,i,j - 1));
}
}
/** 动态规划求解最长公共子序列的长度(LCS) */
int dp[MAX][MAX];
int DpForLCSLength(char * x,char * y,int len_x,int len_y)
{
memset(dp,0,sizeof(dp));
/** 赋初值 */
for(int i = 0; i <= len_x; i++)
{
dp[i][0] = 0;
}
for(int j = 0; j <= len_y; j++)
{
dp[0][j] = 0;
}
/** 根据动态规划的状态转移方程来填表 */
for(int i = 1; i <= len_x; i++)
{
for(int j = 1; j <= len_y; j++)
{
if(x[i - 1] == y[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
}
}
}
/** 由表得出最后的结果LCS */
return dp[len_x][len_y];
}
如果要求出具体的最长公共子序列,可以用下面的方法:
/** 求出最长公共子序列(利用动态规划的解法 + 借助一个临时数组记录动态规划过程)
* 创建一个辅助数组,在动态规划填表的时候记录下解的由来:
* 三种情况:
* 1、x[i] == y[j] ---> 从左上角得解(dp[i - 1][j - 1]) ---> 2
* 2、x[i] != y[j] && dp[i - 1][j] > dp[i][j - 1] ---> 从上面得解(dp[i - 1][j]) ---> 3
* 3、x[i] != y[j] && dp[i - 1][j] < dp[i][j - 1] ---> 从左边得解(dp[i][j - 1]) ---> 1
*/
void DpForLCS(char * x,char * y,int len_x,int len_y)
{
/** 其他过程和直接求长度相同,都要填表,只是这里要记录填表的过程*/
memset(dp,0,sizeof(dp));
memset(t,0,sizeof(t));
for(int i = 0; i <= len_x; i++)
{
dp[i][0] = 0;
}
for(int j = 0; j <= len_y; j++)
{
dp[0][j] = 0;
}
/** 根据动态规划的状态转移方程来填表 */
for(int i = 1; i <= len_x; i++)
{
for(int j = 1; j <= len_y; j++)
{
if(x[i - 1] == y[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
t[i][j] = 2;/** 左上角得解 ---> 2 */
}
else
{
if(dp[i - 1][j] >= dp[i][j - 1])
{
dp[i][j] = dp[i - 1][j];
t[i][j] = 3;/** 上面得解 ---> 3 */
}
else if(dp[i - 1][j] < dp[i][j - 1])
{
dp[i][j] = dp[i][j - 1];
t[i][j] = 1;/** 左边得解 ---> 1 */
}
}
}
}
}
5. 最长递增公共子序列
题意: 两个序列a、b,找出两个序列中所有的既是递增序列又是公共序列,而且找出其中最大长度的。
解法:
【待理解】
代码:
int DP(int m,int n)
{
memset(dp,0,sizeof(dp));
for(int i = 0; i < m; i++)
{
int maxn = -INF;
for(int j = 0; j < n; j++)
{
dp[i][j] = dp[i - 1][j];
if(a[i] > b[j] && maxn < dp[i - 1][j])
{
maxn = dp[i - 1][j];
}
else if(a[i] == b[i])
{
dp[i][j] = maxn + 1;
}
}
}
int maxn = -INF;
for(int k = 0; k < n; k++)
{
if(dp[m][k] > maxn)
{
maxn = dp[m][k];
}
}
return maxn;
}