字符串s,随便删除某些位置的字符,让s变为回文串,有多少种删除方案?
提示:大厂面试题,本题是DP中极其难的题目,而且是变态性地难!!!
唯独你见过,并练习过,有印象,才知道怎么快速破解
本题是DP2,从L到R范围内的尝试模型
练习难题可以快速提升你的能力!
动态规划有四种尝试模型:尝试暴力递归代码,最后改为傻缓存或者动态规划DP代码;
DP1:从左往右的尝试模型【分析i从0–N如何如何】
DP2:从L–R范围上的尝试模型【分析范围上以i开头或者以i结尾如何如何】
DP3:样本位置对应模型【2个串,或2个数组这种,分析对应0–i,0–j上以i,或j结尾的子组、子串、子序列如何如何】
DP4:业务限制类模型【限定几种业务情况如何如何】
题目
一个字符串s,你可以任意删除其中某些位置的字符串,让s变成回文串,请问你删除的方案有多少?注意:“”空串不是回文串。
一、审题
在大厂面试刷题过程中,经常有出题的大佬,他就给你绕弯子,你需要转化它的定义,理解清楚题意才能更好地破解;
这里转化定义:删除某些位置==意味着你保留某些位置,怎么说?
s=abca,删除12位置的bc,意味着保留03位置的aa,即aa回文串,这俩操作时一个意思,理解?
因此本题可以采用保留某些位置的方式解决题目,更贴近于我们DP的解题模型
本题,显然是DP2,从L到R范围内的尝试,
定义dpLR为:讨论s的L–R范围内,有多少种保留方式?使得保留下来的子序列串是回文串?
最终我们要求的结果是dp[0][N - 1]:即s整个串,有多少种保留方式,使得子序列串为回文串。
注意,保留不同位置,得到的子序列串虽然相同,但是也是不同的方案哦,比如
ABACA,保留02得AA,保留04也得AA,保留24也得AA,但是他们都是不同的方案。
二、解题
DP2:从L到R范围上的尝试模型填表套路
既然是范围内的尝试模型,自然填表就是很固定的套路了。
L和R均可以取0–N-1范围内,N长度的取值,故L做行,R做列的话,需要填一个N*N的表格,每个格子代表s从L–R范围内有dpLR种保留方式,使得子序列串为回文串。然后我们需要分析填表dpLR究竟等于什么?【DP中的转移方程】
下面是第一次讲DP2:范围内的尝试模型的填表套路:
既然要求L<=R,所以表格左下角L>R的那些格子,咱不需要填,看图:
因此DP2模型我们只需要填以下几个步骤:
1)填写主对角线,本题,主对角线L==R,则显然,一个字符,就是回文串,“”空串不能做回文串,则就1种保留方式,就是保留那个字符串作为回文串,故dp[L][R] = 1;
2)填写副对角线,即L+1=R,副对角,意味着L在R左边,俩字符,那么怎么保留呢?举个例子完事:
不妨设LR俩字符为AA,那你留L的A,R的A,俩AA都保留,一共3种方式,则dp[L][R] = 3;
另外,AB呢?你可留A,可留B,就这2种方式为回文串,所以dp[L][R] = 2;
因此,填写副对角的话,需要判断L和R是否相同,再决定,反正不是2,就是3;
3)好,其余任意位置dp[L][R] 怎么填呢?【这就是本题变态难的地方】,但是,按照啥方式填表,是非常固定的套路
自然是从最后倒数第3行,往0行递减填写,行固定的情况下,列,从小到大填写,因为最后我们要dp[0][N-1];
注意,这里副对角是N-2行,所以L其实行只能是N-3,而R每次和L的关系 式固定的L+2=R,你可以看图,第一次填哪一列呢,N-1列,显然N-3+2=N-1
以上3个步骤,就是固定的L–R范围上的尝试模型的填表固定套路,任何时候都难不倒人。
本题填写dp[L][R] 的推导
任何DP都是以最基本的例子过界,然后推导转移方程的,下面我们来举例说明
既然是L–R范围,显然要讨论操作L和R的可能性,要求dp[L][R](再来理解一下,是LR范围内,有几种保留方案,让子序列串是回文串),那就要分析保留L和R的情况,这里各种保留方式种数的和,就是我们要的结果;【与那种求最大长度,是各种方式的最值不一样哦】
1)既不保留L,也不保留R,方案数为a=dp[L+1][R-1],比如A BB C ,既不保留A也不保留C,子序列串是L+1–R-1范围上的BB,它是回文串;
2)保留L,但不保留R,方案数为b,比如B BB C ,保留B,但不保留C,子序列串是L–R-1范围上的BBB,它是回文串;
3)不保留L,但保留R,方案数为c ,比如A BB B ,保留B,但不保留A,子序列串是L+1–R范围上的BBB,它是回文串;
4)保留L,也保留R,方案数为d,比如BBBB ,保留B,保留B,子序列串是L–R范围上的BBBB,它是回文串;
自然dp[L][R]=a+b+c+d
abcd怎么得???转移方程是啥???
——先看d怎么得到,分析一下:保留L,也保留R,方案数为d,比如BBBB,这是一种方式(这里的话dp[L][R]==dp[L+1][R-1],也就是之前那个小范围上有几种,现在就算几种),
但是还有另一种方式,那就是只保留L和R处的B和B,得到BB,也是回文串,所dp[L][R]还需要+1,故最后d=dp[L+1][R-1]+1
——再看怎么得到a+b+c,这里非常难看出来,但是有必要仔细分析
这里的b和c都是嵌套a的,比如:
如何求b?
当我们只单独求一个格子b=dp[L][R-1]时,你会发现,R位置字符确定不要了,而b=dp[L][R-1]咋来呢?
可能我们要L的字符(dp[L][R-1]种方案),也可能不要L的字符,(dp[L+1][R-1]),
这两种方式都能得到dp[L][R-1],故:dp[L][R-1] = dp[L][R-1] + dp[L+1][R-1],也就b=b+a;
同样地,如何求c?
当我们只单独求一个格子c=dp[L+1][R]时,你会发现,L位置字符确定不要了,而c=dp[L+1][R]咋来呢?
可能我们要R的字符(dp[L+1][R]种方案),也可能不要R的字符(dp[L+1][R-1]种方案),
这两种方式都能得到dp[L+1][R],故:dp[L+1][R] = dp[L+1][R] + dp[L+1][R-1],也就c=c+a;
以上这两点,一定要想清楚!!!本题难点,也会是关键点!!!
在图上看看他们的位置:dp[L][R]依赖于左边dp[L][R-1]=b,下边dp[L+1][R]=c,左下角dp[L+1][R-1]=a
根据上面的推导:
b=b+a,c=c+a,因此
b+c=b+a+ c+a=2a+b+c,因而
a+b+c = b+c-a = 2a+b+c-a = d
所以呢,a+b+c=b+c-a=dp[L][R-1]+dp[L+1][R]-dp[L+1][R-1]
所以:dp[L][R] = a+b+c+d 是多少呢?
由于d分2种情况:
——L和R字符相同时,d=dp[L+1][R-1]+1=a+1
也就是说dp[L][R] = a+b+c+d = dp[L][R-1]+dp[L+1][R]-dp[L+1][R-1] + dp[L+1][R-1] +1 = dp[L][R-1]+dp[L+1][R]+1
——L和R字符不相同时,d=0你不能保留任意一个字符,否则不回文了。
也就是说dp[L][R] = a+b+c+d = a+b+c = dp[L][R-1]+dp[L+1][R]-dp[L+1][R-1]
所以撕代码的时候判断一下,分开情况即可;代码如下:
在操作代码的时候,先认为L和R字符不相同,再判断LR相等的话,再把d加上即可
public static int howManyStayWays(String s){
if (s == "" || s.length() == 0) return 0;
char[] str = s.toCharArray();
int N = str.length;
int[][] dp = new int[N][N];
//L--R范围的固定套路
//主对角线
for (int i = 0; i < N; i++) {
dp[i][i] = 1;//全部都是回文的
}
//副对角线--最后一行没了
for (int i = 0; i < N - 1; i++) {
dp[i][i + 1] = str[i] == str[i + 1] ? 3 : 2;//AA就3种,AB就两种
}
//任意位置ij--N-1和N-2OK了,
for (int i = N - 3; i >= 0; i--) {
for (int j = i + 2; j < N; j++) {
//从下往上,从左往右
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];//2a+b+c-a
if (str[i] == str[j]) dp[i][j] += dp[i + 1][j - 1] + 1;//再加d==a+1
}
}
return dp[0][N - 1];//整个s有多少方案呢
}
测试代码:
public static void test(){
String s = "ABA";//5
String s2 = "XXY";//4
System.out.println(howManyStayWays(s));
System.out.println(howManyStayWays(s2));
}
public static void main(String[] args) {
test();
}
总结
提示:本题经验:
1)不要惧怕难题,多联系,见多识广,方可稳定心态!
2)熟练掌握DP范围上的尝试模型的填表风格
3)本题很难,难在转移方程abcd中bc嵌套a,d也很特别,所以要搞清楚案例,今后再遇到问题就不大了。