动态规划学习笔记

案例1、最长回文序列
一个字符串有许多子序列,比如字符串abcfgbda,它的子序列有a、bfg、bfgbd,在这些子序列中肯定有回文字符串。现在要对任意
字符串求其最长的回文子序列。注意,本文不是解决最长回文子串,回文子串是连续的,回文子序列是不连续的。字符串abcfgbda
的最长回文子序列为abcba,长度为5。
输入:包含若干行,每行有一个字符串,字符串由大小写字母构成,长度不超过100。
输出:对每个输入,输出一行,该行有一个整数,表示最长回文子序列的长度。

Example
Input:

a
abcfgbda

Output:

1
5
解题思路:
对任意字符串,如果头和尾相同,那么它的最长回文子序列一定是去头去尾之后的部分的最长回文子序列加上头和尾。如果头和尾
不同,那么它的最长回文子序列是去头的部分的最长回文子序列和去尾的部分的最长回文子序列的较长的那一个。
设字符串为str,dp(i,j)表示str[i..j]的最长回文子序列。
状态转移方程如下:
当i > j时,dp[i,j]= 0。
当i = j时,dp[i,j] = 1。
当i < j并且str[i] == str[j]时,dp[i][j] = dp[i+1][j-1]+2;
当i < j并且str[i] ≠ str[j]时,dp[i][j] = max(dp[i][j-1],dp[i+1][j]);
注意如果i+1 == j并且str[i] == str[j]时,dp[i][j] = dp[i+1][j-1]+2 = dp[j,j-1]+2 = 2,这就是“当i > j时f(i,j)=0”的好处。
由于dp[i][j]依赖i+1,所以循环计算的时候,第一维必须倒过来计算,从len-1到0。
最后,s的最长回文子序列长度为dp[0][len-1]。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 105;
char str[N];
int dp[N][N];

int main()
{
    while(~scanf("%s",str)) {
        memset(dp,0,sizeof(dp));
        int len = strlen(str);
        for(int i = len-1; i >= 0; --i) {
            dp[i][i] = 1;
            for(int j = i+1; j < len; ++j)
                if(str[i] == str[j]) {
                    dp[i][j] = dp[i+1][j-1]+2;
                } else {
                    dp[i][j] = max(dp[i][j-1],dp[i+1][j]);
                }
        }
        printf("%d\n",dp[0][len-1]);
    }
    return 0;
}

我们发现计算第i行时只用到了第i+1行,这样我们便不需要n行,只需要2行即可。
起初先在第0行计算dp[len-1],然后用第0行的结果在第1行计算dp[len-2],再用第1行的结果在第0行计算dp[len-3],以此类推。正在
计算的那行设为now,那么计算第now行时,就要用第1-now行的结果。这种方法很巧妙。
当计算完成时,如果len是奇数,则结果在第0行;如果是偶数,则结果在第1行。
此空间复杂度为O(n)。
优化后的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 105;
char str[N];
int dp[2][N];

int main()
{
    while(~scanf("%s",str)) {
        memset(dp,0,sizeof(dp));
        int len = strlen(str);
        int cur = 0;
        for(int i = len-1; i >= 0; --i) {
            dp[cur][i] = 1;
            for(int j = i+1; j < len; ++j)
                if(str[i] == str[j]) {
                    dp[cur][j] = dp[1-cur][j-1]+2;
                } else {
                    dp[cur][j] = max(dp[cur][j-1],dp[1-cur][j]);
                }
            cur = 1-cur;
        }
        if(len%2) {
            printf("%d\n",dp[0][len-1]);
        } else {
            printf("%d\n",dp[1][len-1]);
        }
    }
    return 0;
}
时间: 2024-10-12 21:33:12

动态规划学习笔记的相关文章

动态规划学习笔记(不定期更新)

最近刚开始接触动态规划(Dynamic Programming)算法,之前略有耳闻,一直觉得DP非常之高大上,看了某些题的DP解法也是云里雾里,哇擦?!这么几行代码就解决了?怎么全是数组操作?时间复杂度也很低的样子.其实不然,当我真正开始学习动态规划的时候才发现这货没那么玄乎. 把我对DP浅显的理解总结为以下几点: 1.空间换时间. 2.找到状态. 3.找到状态转移方程. 动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决.为了能够快速的不重复的计

动态规划学习笔记--对于钢条切割方案的思考

1.问题描述 对一个长为n的钢条,给出不同长度钢条对应的单价,求出如何切割能使得该钢条的收益最大化. 2.问题解析 (1)暴力法 找出所有切割方案(共2^(n-1)种),计算出每种切割方案的收益,求最大值. 时间复杂度:O(2^(n-1)) (2)动态规划 这一问题是<算法导论>中,讲解动态规划的例题. 为什么这题能够用动态规划解决呢? 动态规划是用来解决具有以下性质的问题的: 1.最优子结构 2.重复子问题 这两个性质,显然前者更为重要,而后者没有也可以用动态规划解决(我现在这么认为). 动

由LCS到编辑距离—动态规划入门—算法学习笔记

一切计算机问题,解决方法可以归结为两类:分治和封装.分治是减层,封装是加层. 动态规划问题同样可以用这种思路,分治. 它可以划分为多个子问题解决,那这样是不是用简单的递归就完成了?也许是的,但是这样会涉及太多的不便的操作.因为子问题有重叠! 针对这种子问题有重叠的情况的解决,就是提高效率的关键. 所以动态规划问题可以总结为:最优子结构和重叠子问题. 解决这个子问题的方式的关键就是:memoization,备忘录. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方

NLTK学习笔记(四):自然语言处理的一些算法研究

自然语言处理中算法设计有两大部分:分而治之 和 转化 思想.一个是将大问题简化为小问题,另一个是将问题抽象化,向向已知转化.前者的例子:归并排序:后者的例子:判断相邻元素是否相同(与排序). 这次总结的自然语言中常用的一些基本算法,算是入个门了. 递归 使用递归速度上会受影响,但是便于理解算法深层嵌套对象.而一些函数式编程语言会将尾递归优化为迭代. 如果要计算n个词有多少种组合方式?按照阶乘定义:n! = n*(n-1)*...*1 def func(wordlist): length = le

Android学习笔记-回顾计划

人最怕的是,没有方向! 1.楔子: 本人接触Andrjoid开发也有一年多了,期间在一家外包公司独立开发了五六个项目.虽谈不上大牛,但自认小有所成.平时没什么爱好,就喜欢看看技术博客,试验各种开源代码,写写学习笔记. 最近感觉有点陷入瓶颈了,进步甚慢,却又不知该如何进一步提升自己.对于开发中遇到的很多问题,虽有所领悟,然不够系统,一些小知识点,也常有遗漏.觉得是时候系统的反思一下自己的知识体系了,于是决定制定一个回顾计划,综合自己看的博客.书籍,以及自己的开发实践,对一些常用的知识点进行整理.

算法学习笔记 最短路

图论中一个经典问题就是求最短路,最为基础和最为经典的算法莫过于 Dijkstra 和 Floyd 算法,一个是贪心算法,一个是动态规划,这也是算法中的两大经典代表.用一个简单图在纸上一步一步演算,也是很好理解的,理解透自己多默写几次即可记住,机试时主要的工作往往就是快速构造邻接矩阵了. 对于平时的练习,一个很厉害的 ACMer  @BenLin_BLY 说:"刷水题可以加快我们编程的速度,做经典则可以让我们触类旁通,初期如果遇见很多编不出,不妨就写伪代码,理思路,在纸上进行整体分析和一步步的演算

数字语音信号处理学习笔记——绪论(2)

1.2.2 语音编码 语音编码的目的是在保证一定语音质量的前提下,尽可能降低编码比特率,以节省频率资源. 语音编码技术的鼻祖: 研究开始于1939年军事保密通信的需要,贝尔电话实验室的Homer Dudley提出并实现了在低频带宽电话电报电缆上传输语音信号的通道声码器. 20世纪70年代:国际电联(ITU-T,原CCITT)64kbit/s脉冲编码调制(PCM)语音编码算法的G.711建议,它被广泛应用于数字通信.数字交换机等领域,从而占据统治地位. 1980年:美国政府公布了一种2.4kbit

vector 学习笔记

vector 使用练习: /**************************************** * File Name: vector.cpp * Author: sky0917 * Created Time: 2014年04月27日 11:07:33 ****************************************/ #include <iostream> #include <vector> using namespace std; int main

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则 用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeplex.com/ 文档也写的很详细,自己在看它的文档和代码时写了一些demo和笔记,还有它实现的原理记录一下 学习Caliburn.Micro要有MEF和MVVM的基础 先说一下他的命名规则和引导类 以后我会把Caliburn.Micro的 Actions IResult,IHandle ICondu