前言
http://oyjh1986.blog.163.com/blog/static/19601607620118293262941/
面试之前,总结一下dp。具体概念,看我的之前写的有关dp的博客,这里主要总结LCS,0-1背包,数组分割,数中和最大的子数组,数组的最长递增子序列,以及字符串的相似度。
接下来分为俩部分,第一部分,引入各个算法的关键代码。第二部分,主要是表格总结,包括时间和空间复杂度,递推关系式。
关键代码分析
LCS:
#include<iostream>
#include<queue>
using namespace std;
//find the longest common string of str1 and str2.
//the Recurrence formula is c[i][j]=max(c[i][j-1],c[i-1][j]),if str1[i]=str2[j];c[i][j]=c[i-1][j-1],if str1[i]!=str2[j].
const int N=10;
int mark[N][N];
void findLCS(char* Str1,char* Str2,int str1Len,int str2Len)
{
int** c=new int*[str1Len+1];
for(int i=0;i<=str1Len;++i)
c[i]=new int[str2Len+1];
for(int i=0;i<=str1Len;++i)
c[i][0]=0;
for(int i=0;i<=str2Len;++i)
c[0][i]=0;
for(int i=1;i<=str1Len;++i)
{
for(int j=1;j<=str2Len;++j)
{
if(Str1[i-1]==Str2[j-1])
{
c[i][j]=c[i-1][j-1]+1;
mark[i][j]=0;
}
else
{
c[i][j]=max(c[i][j-1],c[i-1][j]);
if(c[i][j]==c[i][j-1])
mark[i][j]=-1;
else
mark[i][j]=1;
}
}
}
for(int i=0;i<=str1Len;++i)
delete [] c[i];
delete c;
}
void getLCS(char* Str1,char* Str2,int str1Len,int str2Len,queue<char>* pqueChar)
{
if(str1Len==0||str2Len==0)//watch out the boundry
return ;
if(mark[str1Len][str2Len]==0)
{
getLCS(Str1,Str2,str1Len-1,str2Len-1,pqueChar);
pqueChar->push(Str1[str1Len-1]);
}
else
if(mark[str1Len][str2Len]==1)
getLCS(Str1,Str2,str1Len-1,str2Len,pqueChar);
else
getLCS(Str1,Str2,str1Len,str2Len-1,pqueChar);
}
int main(void)
{
char* Str1="aefabc";
char* Str2="egtc";
queue<char> queChar;
int str1Len=0;
int str2Len=0;
//get the length of two string
while(*Str1++) str1Len++;
while(*Str2++) str2Len++;
Str1=Str1-str1Len-1;
Str2=Str2-str2Len-1;
findLCS(Str1,Str2,str1Len,str2Len);
getLCS(Str1,Str2,str1Len,str2Len,&queChar);
while(!queChar.empty())
{
cout<<queChar.front();
queChar.pop();
}
return 0;
}
自己写的代码质量很低,在之前代码的基础上改进了c[i][j],主要把c作为动态数组。时间复杂度O(M*N),空间复杂度 O(2*M*N).主要引入了一个c和mark。
0-1背包问题:
递推关系式 :c[i][j]=c[i-1][j-w[i]]+v[i],if c[i-1][j-w[i]]+v[i]>c[i-1][j] else c[i][j]=c[i-1][j]。
c[i][j]表示背包最大重量为 j时,前i个物品中取任意个放入背包的最大价值。
#include<iostream>
#include<queue>
using namespace std;
const int N=5;
void printfBack(int** c,int *w,int m,int maxW,queue<int>* pQueInt );
void backProcess(int *w,int *v,int m,int maxW,queue<int>* pQueInt)
{
int** c=new int*[m+1];
for(int i=0;i<=m;++i)
c[i]=new int[maxW+1];
//设定初始条件
for(int i=0;i<=m;++i)
c[i][0]=0;
for(int j=0;j<=maxW;++j)
c[0][j]=0;
for(int j=1;j<=maxW;++j)
{
for(int i=1;i<=m;++i)
if(w[i-1]<=j)
{
if(c[i-1][j]>c[i-1][j-w[i-1]]+ v[i-1])
c[i][j]=c[i-1][j];
else
c[i][j]=c[i-1][j-w[i-1]]+v[i-1];
}
else
c[i][j]=c[i-1][j];
}
printfBack(c,w,m,maxW,pQueInt);
for(int i=0;i<m;++i)
delete[] c[i];
delete []c;
}
void printfBack(int** c,int* w,int m,int maxW,queue<int>* pQueInt )
{
int log[20];
int j=maxW;
for(int i=m;i>=1;i--)
if(c[i][j]==c[i-1][j])
log[i]=0;
else
{
pQueInt->push(i);
log[i]=1;
j=j-w[i-1];
}
pQueInt->push(c[m][maxW]);
}
int main()
{
int w[N]={10,3,5,11,8};
int v[N]={3,5,2,1,6};
int maxWeight=14;//maxmium weight
queue<int> queInt;
backProcess(w,v,N,maxWeight, &queInt);
while(!queInt.empty())
{
cout<<queInt.front()<<endl;
queInt.pop();
}
cout<<"最后一个数表示的是背包可以装入的总价值";
return 0;
}
时间复杂度:O(M*maxW),空间复杂度:O(M*maxW)。
数组分割:分割后个数相同,值相等或是最相近。
递推公式:c[i][j]=c[i][j-data[k-1]];在前k个数里面能否取得i个数,使其和为j,能为true,否为false。时间复杂度,考虑到有一个三重循环,O(N*maxW*N);空间复杂度:O(N*maxW)关键代码如下:
for(k=1;k<=len;k++)
for(i=min(k,len/2);i>=1;--i)
for(j=1;j<=half;j++)
if(j>=data[k-1]&&dp[i-1][j-data[k-1]])
dp[i][j]=true;
数组分割:分割后个数可以不同。值相等,或是最相近
依然采用dp去做,递推公式:
for(i=1;i<=len;i++)
for(j=1;j<=half;++j)
if(j<data[i-1])
dp[i][j-1]=dp[i-1][j];
else
dp[i][j]=dp[i-1][j]||dp[i-1][j-data[i-1]];
字符串的距离
具体分析见我的博客http://blog.csdn.net/a725sasa/article/details/9404599,时间复杂度:O(N*len),空间复杂度:O(N*len)。
数组中和最大的子数组
时间复杂度:O(N),空间复杂度:O(N*2)
关键代码:
for(i=n-2;i>=0;i--)
{
Start[i]=max(A[i],A[i]+Start[i+1]);
All[i]=max(Start[i],All[i+1]);
}
最长递增子序列
依然采用dp,编程之美书上给出了时间复杂度Nlg(N),空间复杂度为O(N)的算法,没看懂,腾讯13年的校招就考了这题,所以在这才提一下,一般我会采用LCS的变形去做这题,即,先对数组排序,然后求LCS,思路很明了,当然复杂度没这么好。