删除字符串s中的某些字符让s成为回文串,有多少种删除方案?

字符串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的那些格子,咱不需要填,看图:
图1
因此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
图2
根据上面的推导:

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也很特别,所以要搞清楚案例,今后再遇到问题就不大了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值