动态规划的核心是状态和状态转移方程
DAG(Directed Acyclic Graph)
DAG:有向无环图
很多问题都可以转化为DAG上的最长路,最短路,路径计数问题
嵌套矩形
描述:有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a
抽象为求DAG中不固定起点的最长路径:
- 构造图(邻接矩阵):如果矩形A可以包含矩形B,则A有一条到B的路径:A->B;
- 定义状态dp[i]:表示从i出发的最长路长度
- 状态转移方程:dp[i] = max(d[j]+1 | (i,j)属于边集))
凑硬币
有面值为V1,V2,...,Vn的n种硬币若干枚,如何用最少/最多的硬币凑够S元?
抽象为DAG中固定终点的最长路和最短路(下面以最长路叙述)
1.把每种面值看做一个点,表示“还需要凑足的面值”,起点(初始状态)S,终点(目标状态)0.
2.定义状态d(i):表示从节点 i 出发到 0 的 最长路。//即凑足 i 需要的最多硬币。
3.状态转移方程:d(i) = max{ d(i - Vj) + 1} ,i >= Vj。
背包问题
- 0-1背包问题
n种物品,每种只有一件,物品i质量为w[i],价格为c[i],有一个容量为V的背包,如何挑选使背包里的物品总价值最大?
定义状态:d[i][v] : 表示前i个物品填满v的最大总价值。
状态转移方程:d[i][v] = max(d[i-1][v], d[i-1][v-w[i]]+c[i]);
边界:d[0][v]=0;
- 完全背包问题
n种物品若干件,每种每件质量为w[i],价格为c[i],有一个容量为V的背包,如何挑选使背包里的物品总价值最大?
和凑硬币很像,可以看成固定起点的带权的DAG的最长路
定义状态:d(i):i表示容量,d(i)为容量为i的最大价值。
状态转移方程:d(i)=max( d(i-w(j)) + c(j) ); i>=w(j) j=0,1,...,n-1
边界:d(0)=0;
最大连续子序列和
描述:给定序列A比如[-1,2,3,4,-5,2],找出连续的和最大的子序列a[2,3,4];
(比如求最大连续积啊;或者不连续的最大和啊都是相同的思路)
定义状态描述
dp[i]: 表示以A[i]结尾的最大连续子序列的和
状态转移方程:
dp[i]=max(dp[i-1]+A[i],A[i]);
边界
dp[0]=A[0];
最长不下降子序列LIS
给定一个序列A,给定一个排序的标准(比如是降序),找出A的一个最长的满足这个排序的子序列。
例:A=[3,3,4,5,1,2,9]=>最长子序列a=[3,3,4,5,9]
= = = =
状态:
dp[i];//代表结尾是A[i]的最大子序列长度
状态转移方程:
dp[i] = max(1,dp[j]+1); for j=0,...,i-1
时间复杂度O(n^2)
可以提高到O(nlogn)
dp和二分查找(from这里)而且这个方法还构造出了这个最长子序列(最有潜力递增的),用dp数组就只是返回最长子序列的长度。
~ 构建数组buildingList,返回buildlist.size()即所求最长子序列长度。
buildingList[i]存的是长度为i的子序列的最后一个元素a,这个元素a一定是小的,也就是如果出现比a更小的,就要更新a(用二分查找,因为buildinglist一定是严格单调递增的)
比如给一个序列[6,2,4,3]
1.buildingList[0]=6;//6
2. **2<6**->buildingList[0]=2;//2
3. **4>2**->buildingList[1]=4;//2 4
4. **3>2;3<4**->buildingList[1]=3;//2 3----二分法返回返回第一个大于等于3的位置i
->return buildingList.size()=2;//最长升序子序列长度为2
题目:
- longest-increasing-subsequence
最大公共子序列LCS
描述:给定两个字符串A,B,求这两个字符串公共部分的最长长度(不用连续)
~例 “ate” and "delete" -> "te"为公共子串,长度为2~
LCS描述:
string1,string2长度分别为m,n
构建二维数组dp[m+1][n+1];
定义状态dp[i][j]: 表示string1'(下标为0,...,i-1)和string2'(下标为0,...,j-1)的最长公共子串的长度。
状态转移方程:
int LCS(string word1,string word2){
int m=word1.length();
int n=word2.length();
//int dp[m+1][n+1];
vector > dp(m+1,vector(n+1));
//初始化一下dp二维数组(边界)
for(int i=0;i<=m;i++){
dp[i][0]=0;
}
for(int i=0;i<=n;i++){
dp[0][i]=0;
}
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
if(i == 0 || j==0)
continue;
//状态转移方程
if(word1[i-1] == word2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
题目
delete-operation-for-two-strings
最长回文子串
描述:给一个字符串A,比如“ababb”,那么最长回文子串有“aba”,"bab";
动态规划解决方法(from here) :
定义状态的指标函数:p[i][j]:
字符子串A[i]到A[j]是否是回文;是则为1,否则为0;
状态转移方程:
边界
复杂度
时间复杂度为O(n^2);
空间复杂度为O(n^2);// 空间复杂度可降低到O(1)
提高:
二分法+Hash->Time Complexity:O(nlogn);
Manacher's Algorithm->O(n)
class Solution {
public:
string longestPalindrome(string s) {
int len=s.length();
int max=1;
int start[len]={-1};//储存回文起点坐标,长度为max
int dp[len][len]={0};//dp[i][j]表示i,j之间是否是回文,回文是1,否则是0
//初始化边界
for(int i=0;ii
题目
longest-palindromic-substring
练习
from PAT
/*1045 Favorite Color Stripe (30)(30 分)*/
/*最长不下降子序列 */
#include
#include
#include
#include
using namespace std;
#define maxL 10000
int HashTable[maxL];
int Lcolor(vector &stripe);
int main()
{
int colorsN, loveC, stripeC;
cin >> colorsN >> loveC;
int x;//暂存输入的数
memset(HashTable, -1, sizeof(HashTable));
for (int i = 0; i> x;
HashTable[x] = i;
}
cin >> stripeC;
vector stripe;
for (int i = 0; i> x;
if (HashTable[x] != -1) {
stripe.push_back(x);//不是喜欢的颜色就不放进stripe里了
}
}
cout << Lcolor(stripe);
return 0;
}
int Lcolor(vector &stripe)
{
int n = stripe.size();
int *dp = new int[n];//用来存以strip[i]结尾的LIS值
int re = 0;
for (int i = 0; idp[i]=dp[j]+1;
int tmpj = HashTable[stripe[j]];
if (tmpj <= tmpi && dp[i]