两道子序列经典题目
力扣1143:最长公共子序列
思路:这是一道经典题,dp[i][j]表示第一个字符串[0,i-1]和第二个字符串[0,j-1]之间公共的子序列个数,每次多比较一个,dp[i][j]可以由dp[i-1][j-1]、dp[i][j-1]、dp[i-1][j]得到。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Solution { public int longestCommonSubsequence(String s1, String s2) { int len1=s1.length(); int len2=s2.length(); int[][] dp=new int[len1+1][len2+1]; //i和j从1开始,防止-1时越界 for(int i=1;i<=len1;i++) { for(int j=1;j<=len2;j++) { if(s1.charAt(i-1)==s2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]); } } return dp[len1][len2]; } }
题意:求最短子串长度,要求包含"puleyaknoi"子序列。
思路:子序列的字符用一个前驱数组维护,指定子序列的上一个字符是什么,这里10个字符没有重复,一维就够了。对子序列的字符每次同步前驱字符的位置,找到子序列的起点。不可以直接记录子序列起点的位置,可能会被最近更新的起点位置覆盖。
#include<stdio.h> #include<iostream> #include<algorithm> #include<cstring> #include<math.h> #include<string> #include<map> #include<queue> #include<stack> #include<set> #include<sys/types.h> #include<unistd.h> #define ll long long #define inf 0x3f3f3f3f using namespace std; char s[100005]; int num[30];///模式串 int dp[30];/// char a[]={"puleyaknoi"}; int pre[30];///模式串字符的前驱字符 int main() { int lena=strlen(a); for(int i=0;i<lena;i++) { num[ a[i]-'a' ]=1; if( i!=0 )///第一个字符没有前驱 pre[ a[i]-'a' ]=a[i-1]-'a'; } int t; scanf("%d",&t); while(t--) { memset(dp,-1,sizeof(dp));///防止起点是0相冲,dp[i]记录字符i作为子序列的起点的位置 scanf("%s",s); int lens=strlen(s); int ans=inf; for(int i=0;i<lens;i++) { int x=s[i]-'a'; if( num[x]==0 )///不是喜欢的字符 continue; if( s[i]=='p' )///第一个字符,做一下标记,持续更新,后面遇到的字符也会更新 { dp['p'-'a']=i; } else { ///当前字符x的前驱存在,才可以往后走 if(dp[ pre[x] ]!=-1 ) dp[ x ]=dp[ pre[x] ];///和前驱同一个起点,根据前驱找到所在子序列的起点,而不是直接指向起点,可能会被最近的新的起点覆盖 } if( s[i]=='i' && dp[ x ]!=-1 )///10个够了,并且i字符有前驱,一直连到p ans=min(ans,i-dp[x]+1); } if(ans==inf) printf("-1\n"); else printf("%d\n",ans); } return 0; }
思路扩展:子序列可以出现多次相同字符,则dp数组多开一维,dp[x][j]=dp[pre[x][j].c][pre[x][j].d],dp[x][j]表示当前字母x与模式串中第j个x字母匹配时,最近的起始位置。pre[x][j].c表示模式串中第j个x字母的前一个字母,pre[x][j].d表示模式串中第j个x字母的前一个字母是第几次出现。