动态规划第三讲——序列化的动态规划问题

在第三节中,我们将讨论序列化问题中的动态规划解法。这部分多半分为单序列和双序列等问题

例一:最长上升子序列。

最长上升子序列问题,有一个正整数数列,长度n在1000之内,元素a[i]在10^5之内,求最长递增子序列的长度。

分析一:发现问题的可分性质

如果我们采用穷举法,将有2^n的时间复杂度;这里面有很多是重复的4、3、***类型的子序列,以4开头的递增子序列的长度都是1.

很明显,我可以写出递归函数

dp(int i)={

a[i]=1;

for (int j = i+1; j < n; ++j){

if(a[i]<a[j])

temp=max(a[i], dp(j));

}

return temp;

}:以a[i]开始的子序列的最长递增长度,然后从dp(i)中选择最大的即可。

这里,时间复杂度是O(n^2);在递归与分治法过后,是不是发现了问题的端倪呢?到这里,我们就可以很方便地转化成动态规划的问题了。形式有两种,读者可以自己去实现:1)dp[i]:以a[i]开始的最长递增子序列的长度;2)dp[i] 以a[i]结尾的最长递增子序列的长度

分析二:针对查找的优化

假设dp[i]:以a[i]为结尾的最长LIS的长度。很显然,我们按照i递增的顺序递增dp[i],这样,如果dp[j](j<i)长度相同,我们需要选择a[j]最小的那个来计算就可以了;而不是进行遍历。所以,我们定义新的子问题dp[i]长度为i+1的子序列中,结尾元素的最小值(不存在就是INF)。

对于每个a[j], 如果i=0或者dp[i-1]<a[j], dp[i] = min(dp[i], a[j])

for (int i = 0; i < n; ++i){

for (j = 0; j < n; ++j){

if(i==0 || dp[i-1]<a[j]) dp[i]=min(dp[i], a[j]);

}

}

这样下去,有两层循环:长度l和数组下标j;时间复杂度仍然是O(n^2);但是,有改进的地方:因为数组dp[i]是单调递增的;所以对于每次a[j],仅仅需要最多1次更新(不可能长度为i和i+1的LIS结尾的元素都是a[j]),所以我们可以采用二分查找来确定更新的dp[i];

for (i = 0; i < n; ++i){

*lower_bound(dp, dp+n, a[i]) = a[i];

}

return lower_bound(dp, dp+n, INF) - dp;

Eg2:Word Break

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given

s = "leetcode",

dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".

分析:

很显然,我们可以先将问题二分:找到以s[0]开头,在dict中的字符串s1,然后剩下的是s2;这样判断solution(s2)是否满足即可。

仔细分析不难发现,加入dict=["ab", "abab"], s="ababcd";此时我们需要判断solution("abcd"), solution("cd"); 而solution("cd")又包含在solution("abcd")之中。所以问题满足重叠子问题和最优子结构。这个时间复杂度是O(n^2)。

class Solution {
public:
         bool wordBreak(string s, unordered_set<string> &dict) {
             int n=s.size();
             if(n==0) return true;
             bool dp[n+1];
             fill(dp, dp+n+1, false);
             dp[0]=true;
             int i, j;

             for (i = 0; i < n; ++i){
                 for(j=i;j>=0;j--){
                    if(dp[j]){
                        if( dict.find(s.substr(j, i+1 - j)) != dict.end() ){
                            dp[i+1]=true;
                            break;
                        }
                    }
                 }
             }
             return dp[n];
         }
};  

进一步的分析:如果有这样的一个例子,s="aaaaaaaaaaaaaaa...a"(100个a);同样dict里面的元素就是s,这样,这个时间复杂度依然是O(n^2),而实际上,dp[0]~dp[n-1]的结果都为0.在计算dp[i]的时候,我们的时间复杂度也是O(n), 这个时间复杂度可以减小为O(dict.size()). 另一方面,我们采用了线性查找来确定d[j]为true,实际上,我们可以采用堆栈来存储dp[j]为true对应的j,这也能在一定程度上减小时间复杂度。

时间: 2024-11-06 07:39:57

动态规划第三讲——序列化的动态规划问题的相关文章

动态规划(2)——常见动态规划模型

\(1.\)数字三角形 每次可以往右下或者左下走一格,求路径的最大权值. \(d(i,j)=max(d(i+1,j),d(i+1,j+1))+a(i,j).\)边界是\(d(n+1,j)=0\),从下往上推(因为要保证\(i+1\)行在第\(i\)行之前更新) for(int i=1;i<=n+1;++i) d[n+1][i]=0; for(int i=n;i>=1;--i) { for(int j=1;j<=i;++j) { d[i][j]=max(d[i+1][j],d[i+1][j

动态规划-矩阵连乘(附备忘录法)

题目:矩阵连乘, 求解计算量最小的加括号方法. 输入: 按顺序输入各个矩阵的 行和列. 求解: 最少数乘次数和加括号方案. (详细情况可自行百度) 题解: 用一个数组p[] 来存储参数, (但要进行处理一下, 如: 矩阵A 为 10*5 , B 为 5*15.P[]只记录 10, 5, 15) 用m[i][j] 来表示 从 i->j 的数乘最小值.s[][]记录过程中的最优步骤. const int maxn = 100; int p[maxn], m[maxn][maxn], s[maxn][

动态规划之最长公共子序列(LCS)

tips : 本文内容是参考了很多著名博客和自己的思考得出的,如有不当欢迎拍砖. 先简单说一下动态规划 通俗地说:动态规划就是将一个可以划分为子问题的问题进行递归求解,不过动态规划将大量的中间结果保存起来, 不管它们是否会用得到,从而在后面的递归求解过程中可以快速求解.由此可以看得出来动态规划是一个以牺牲空间 为代价换取时间的算法. 对于最长公共子序列的题目不用多说,现在来分析一下LCS的动态规划解决思路: 一.首先先观察问题是否符合动态规划最明显的两个特征:最优子结构和重叠子问题 方便起见,以

动态规划分析总结——如何设计和实现动态规划算法

进行算法设计的时候,时常有这样的体会:如果已经知道一道题目可以用动态规划求解,那么很容易找到相应的动态规划算法并实现:动态规划算法的难度不在于实现,而在于分析和设计-- 首先你得知道这道题目需要用动态规划来求解.本文,我们主要在分析动态规划在算法分析设计和实现中的应用,讲解动态规划的原理.设计和实现.在很多情况下,可能我们能直观地想到动态规划的算法:但是有些情况下动态规划算法却比较隐蔽,难以发现.本文,主要为你解答这个最大的疑惑:什么类型的问题可以使用动态规划算法?应该如何设计动态规划算法? 动

动态规划题目

动态规划算法,在T大某位老师的书中说就是递推+重复子问题. 动态规划算法的效率主要与重复子问题的处理有关. 典型的题目有 陪审团,最大公共子串问题 1,最大公共子串问题 这个是动态规划的基础题目.动态规划就是递推和重复子结构. 确定了递推关系后.找到一个能极大地减少重复运算的子结构至关重要.选的好了,时间效率会很好. 这个问题,不妨设第一个串为a,长度为n,第二个串为b,长度m.那么最长的子序列长度为f(n,m) 当a[n]=a[m]时 f(n,m)=1+f(n-1,m-1) 否则f(n,m)=

动态规划分析总结——怎样设计和实现动态规划算法

进行算法设计的时候,时常有这种体会:假设已经知道一道题目能够用动态规划求解,那么非常easy找到对应的动态规划算法并实现:动态规划算法的难度不在于实现,而在于分析和设计-- 首先你得知道这道题目须要用动态规划来求解. 本文,我们主要在分析动态规划在算法分析设计和实现中的应用,解说动态规划的原理.设计和实现.在非常多情况下,可能我们能直观地想到动态规划的算法.可是有些情况下动态规划算法却比較隐蔽.难以发现. 本文.主要为你解答这个最大的疑惑:什么类型的问题能够使用动态规划算法?应该怎样设计动态规划

动态规划求解最长公共子序列

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的.若用分治法来解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间.然而,不同子问题的数目常常只有多项式量级.在用分治法求解时,有些子问题被重复计算了许多次.如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式

动态规划专题 01背包问题详解【转】

对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本文力求通俗易懂,无异性,不让读者感到迷惑,引导读者去思考,所以如果你在阅读中发现有不通顺的地方,让你产生错误理解的地方,让你难得读懂的地方,请跟贴指出,谢谢! 初识动态规划 经典的01背包问题是这样的: 有一个包和n个物品,包的容量为m,每个物品都有各自的体积和价值,问当从这n个物品中选择多个物品放

动态规划之收集苹果

路径经过的最大值(最小值):原题:平面上有N*M个格子,每个格子中放着一定数量的苹果.从左上角的格子开始, 每一步只能向下走或是向右走,每次走到一个格子就把格子里的苹果收集起来, 这样一直走到右下角,问最多能收集到多少个苹果. 不妨用一个表格来表示: {5, 8, 5, 7, 1, 8},    {1, 3, 2, 8, 7, 9},    {7, 8, 6, 6, 8, 7},    {9, 9, 8, 1, 6, 3},    {2, 4,10, 2, 6, 2},    {5, 5, 2,