前言:
在之前的LDU训练赛和UPC训练赛中都出现了字串包含问题,在我前面的两篇博客中也介绍了那两个题的思路,但是这两个题有一个共同的特点,那就是他们要寻找的字串都是提前已知的,如果给出任意两个串,寻找A串中B字串的个数,之前我的思路显然不可用,因此我找到了力扣的“不同的子序列”这个问题,进行学习;
题目描述:
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
题目数据保证答案符合 32 位带符号整数范围。
来源:力扣(LeetCode)
链接:不同的子序列
提示:
0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成
思路:
这题我们用动态规划来做
1.当s.length<t.lenth 时不存在子序列
2.当s.length>=t.lenth 时
设二维数组dp[ i ][ j ] 表示 s[ :i ] 中 字串 t[ :j ] 出现的个数
s[ :i ]表示 s 串从 0 到 i 的部分
t[ :j ] 表示 t 串 从0 到 j 的部分
我们首先进行预处理:
当 j = 0 时,s串无论取任意长度,空串都是它的字串
for(int i=0;i<=lena;i++) dp[i][0]=1;
当 i = 0 时 t串无论取任意长度,s不可能含有字串
for(int i=1;i<=lenb;i++) dp[0][i]=0;
特别注意 dp[ 0 ][ 0 ] = 1;
不要在第二次赋值时把这个值覆盖掉;
1.当s[ i ] = t[ j ] 时
s[ i ] 自己可以选择自己是否与 t[ j ] 匹配
若s[ i ] 与 t[ j ]匹配 那么考虑 t[ :j -1] 做 s[ :i-1 ] 的字串
dp[ i ][ j ] =dp[ i -1][ j-1 ]
若s[ i ] 与 t[ j ]不匹配 那么考虑 t[ :j ] 做 s[ :i-1 ]的字串
dp[ i ][ j ] =dp[ i -1][ j ]
2.当s[ i ] != t[ j ] 时
s[ i ] 不可以于 t[ j ] 匹配
那么考虑 t[ :j ] 做 s[ :i-1 ]的字串
dp[ i ][ j ] =dp[ i -1][ j ]
因此转移方程:
for(int i=1;i<=lena;i++) { for(int j=1;j<=lenb;j++) { if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+dp[i-1][j]; else dp[i][j]=dp[i-1][j]; } }
最终代码:
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const ll maxx = 1e18; const int N = 1e5+100; const int p = 1e4; const double eps = 1e-8; string a,b; ull dp[1000][1000];// a[:i]子序列中 b[:j]子序列出现的次数 int lena,lenb; int main() { cin>>a; cin>>b; lena=a.size(); lenb=b.size(); if(lena<lenb) { cout<<"0"; retrun 0; } for(int i=0;i<=lena;i++) dp[i][0]=1; for(int i=1;i<=lenb;i++) dp[0][i]=0; for(int i=1;i<=lena;i++) { for(int j=1;j<=lenb;j++) { if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+dp[i-1][j]; else dp[i][j]=dp[i-1][j]; } } cout<<dp[lena][lenb]; }
数据加强版:
如果仅仅是一个动态规划没什么意思,但是如果这个题加强一下数据变成
0 <= s.length, t.length <= 2000,那么我想即使是 unsigned long long 也放不下最终的结果,这时候就需要高精度的帮助
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const ll maxx = 1e18; const int N = 1e5+100; const int p = 1e4; const double eps = 1e-8; string a,b; string dp[2000][2000];// a[:i]子序列中 b[:j]子序列出现的次数 int lena,lenb; string add(string s1,string s2) { int len1=s1.size(); int len2=s2.size(); string ss=""; int a[N],b[N]; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(int i=0;i<len1;i++) a[i+1]=s1[len1-1-i]-'0'; for(int i=0;i<len2;i++) b[i+1]=s2[len2-1-i]-'0'; int l=1,r=1,k=1,carry=0; while(l<=len1||r<=len2) { b[k]=a[l]+b[r]+carry; carry=b[k]/10; b[k]%=10; l++;r++;k++; } while(carry) { b[k]=carry%10; carry/=10; k++; } for(int i=k-1;i>=1;i--) ss+=(b[i]+'0'); return ss; } int main() { cin>>a; cin>>b; lena=a.size(); lenb=b.size(); if(lena<lenb) { cout<<"0"; retrun 0; } for(int i=0;i<=lena;i++) dp[i][0]="1"; for(int i=1;i<=lenb;i++) dp[0][i]="0"; for(int i=1;i<=lena;i++) { for(int j=1;j<=lenb;j++) { if(a[i-1]==b[j-1]) dp[i][j]=add(dp[i-1][j-1],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } } cout<<dp[lena][lenb]; }
注意:易出错的地方在于高精的时候存入数组处理后是倒序的,所以放到一个新串中要倒着处理,而且用两个数组处理高精而不用三个,节省时间空间不易爆内存
之前我也遇到过一个 dp+高精 的问题,没做出来很可惜,这次又找到了一个类似的问题,很高兴解决了,加油!