动态规划小题整理(数字三角形,最长上升子序列,最长公共子序列)

我分析做动规的题目有两种方式:

1、记忆化递归性(递归+备忘录)

2、递推型

其中,记忆化递归性用递归的方式做,并且要设置备忘录数组,以免重复调用计算,浪费时间;递推型动规要确定出状态,并列出状态转移方程,这是比较难的。

下面三个小题都是有关动态规划的题目:

目录

一、数字三角形

二、最长上升子序列(百练2757)

三、最长公共子序列(poj1458)


一、数字三角形

Description
下图给出了一个数字三角形,请编写一个程序,计算从顶至底的某处的一条路径,使该路径所经过的数字和最大
Input
有很多个测试案例,对于每一个测试案例, 通过键盘逐行输入,第1行是输入整数(如果该整数是0,就表示结束,不需要再处理),表示三角形行数n,然后是n行数
Output
输出最大值
Example Input

 5 

3 8 
8 1 0 
2 7 4 4 
4 5 2 6 5

Example Output

30 

代码块 

(1) 记忆递归性型动规程序

///数字三角形记忆递归性型动规程序
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 101
int D[maxn][maxn];
int n;
int maxsum[maxn][maxn];//备忘录
int Maxsum(int i,int j)//Maxsum函数表示D[i][j]点到底边的最大值
{
    if(maxsum[i][j]!=-1)
        return maxsum[i][j];
    if(i==n)
        maxsum[i][j]=D[i][j];
    else
    {
        int x=Maxsum(i+1,j);
        int y=Maxsum(i+1,j+1);
        maxsum[i][j]=max(x,y)+D[i][j];
    }
    return maxsum[i][j];
}
int main()
{
    int i,j;
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
        {
            cin>>D[i][j];
            maxsum[i][j]=-1;//设置备忘录
        }
    cout<<Maxsum(1,1)<<endl;
    return 0;
}

(2)递推型

///数字三角形递推形式
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 101
int D[maxn][maxn];
int n;
int maxSum[maxn][maxn];
int main()
{
    int i,j;
    cin >> n;
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
            cin >> D[i][j];
    for( int i = 1;i <= n;i++)
        maxSum[n][i] = D[n][i];
    for( int i = n-1; i>= 1;i--)
        for( int j = 1; j <= i; ++j )
        {
            maxSum[i][j] = max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j];
        }

    cout << maxSum[1][1] << endl;
}

(3)递推型的空间优化

直接用D的 第n行替代maxSum即可。 节省空间,时间复杂度不变

///数字三角形空间优化
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 101
int D[maxn][maxn];
int n;
int *maxSum;
int main()
{
    int i,j;
    cin >> n;
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
            cin >> D[i][j];
    maxSum=D[n];//指针指向D数组第n行的首地址
    for( int i = n-1; i>= 1;i--)
        for( int j = 1; j <= i; ++j )
        {
            maxSum[j] = max(maxSum[j],maxSum[j+1])+D[i][j];
        }

    cout << maxSum[1] << endl;
}

二、最长上升子序列(百练2757)

描述

一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1a2, ..., aN),我们可以得到一些上升的子序列(ai1ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。

输入

输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。

输出

最长上升子序列的长度。

样例输入 

7
1 7 3 5 9 4 8

样例输出 

 分析:

1.找子问题
“求以ak(k=1, 2, 3…N)为终点的最长上升子序列的 长度” 一个上升子序列中最右边的那个数,称为该子序列的 “终点”。 虽然这个子问题和原问题形式上并不完全一样,但 是只要这N个子问题都解决了,那么这N个子问题的解中, 最大的那个就是整个问题的解。

2、确定状态

子问题只和一个变量-- 数字的位置相关。因此序列中数的 位置k 就是“状态”,而状态 k 对应的“值”,就是以ak做为 “终点”的最长上升子序列的长度。 状态一共有N个。

3. 找出状态转移方程:
maxLen (k)表示以ak做为“终点”的 最长上升子序列的长度那么:
初始状态:maxLen (1) = 1

 maxLen (k) = max { maxLen (i):1<=i < k 且 ai < ak且 k≠1 } + 1 若找不到这样的i,则maxLen(k) = 1

maxLen(k)的值,就是在ak左边,“终点”数值小于ak ,且长度 最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小 于ak的子序列,加上ak后就能形成一个更长的上升子序列。

///最长上升子序列(“人人为我”递推型动归程序)
///时间复杂度O(N2)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<math.h>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=1010;
int a[maxn];
int maxlen[maxn];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        maxlen[i]=1;
    }
    for(int i=2;i<=n;i++)//每次求以第i个数为终点的最长上升子序列的长度
        for(int j=1;j<i;j++)
        {
            if(a[i]>a[j])//察看以第j个数为终点的最长上升子序列 
                maxlen[i]=max(maxlen[i],maxlen[j]+1);
        }
    cout<<*max_element(maxlen+1,maxlen+n+1)<<endl;//STL函数,在maxlen中找到最大值
    return 0;
}

三、最长公共子序列(poj1458)

题意: 

给出两个字符串,求出这样的一 个最长的公共子序列的长度:子序列 中的每个字符都能在两个原串中找到, 而且每个字符的先后顺序和原串中的 先后顺序一致。

Sample Input

abcfbc abfcab

programming    

contest abcd mnp

Sample Output

4 2 0   

分析:

输入两个串s1,s2, 设MaxLen(i,j)表示:  s1的左边i个字符形成的子串,与s2左边的j个 字符形成的子串的最长公共子序列的长度(i,j从0 开始算) MaxLen(i,j) 就是本题的“状态”。

#include <iostream>
#include <cstring>
using namespace std;
char sz1[1000];
char sz2[1000];
int maxLen[1000][1000];
int main()
{
    while( cin >> sz1 >> sz2 )
    {
        int length1 = strlen( sz1);
        int length2 = strlen( sz2);
        int nTmp;
        int i,j;
        for( i = 0;i <= length1; i ++ )
            maxLen[i][0] = 0;
        for( j = 0;j <= length2; j ++ )
            maxLen[0][j] = 0;
        for( i = 1;i <= length1;i ++ )
        {
            for( j = 1; j <= length2; j ++ )
            {
                if( sz1[i-1] == sz2[j-1] )
                    maxLen[i][j] = maxLen[i-1][j-1] + 1;
                else
                    maxLen[i][j] = max(maxLen[i][j-1], maxLen[i-1][j]);

            }
        }
        cout <<  maxLen[length1][length2] << endl;
    }
    return 0;
}

北大郭炜老师的算法视频讲解:https://www.icourse163.org/learn/PKU-1001894005?tid=1002783037#/learn/announce

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值