acwing1222. 密码脱落

题目链接

acwing1222. 密码脱落

可以使用区间 DP 来解决这个问题。

定义状态

我们可以使用 f ( i , j ) f(i,j) f(i,j) 表示区间 [ i , j ] [i,j] [i,j] 内删除的字符个数,使得该区间成为镜像字符串。

转移方程

如果 s i = s j s_i = s_j si=sj,那么 f ( i , j ) = f ( i + 1 , j − 1 ) f(i,j) = f(i+1,j-1) f(i,j)=f(i+1,j1)

如果 s i ≠ s j s_i \neq s_j si=sj,那么我们可以删除 s i s_i si 或者 s j s_j sj,然后递归处理剩下的区间。因此,我们有
f ( i , j ) = min ⁡ f ( i + 1 , j ) ,   f ( i , j − 1 ) + 1 f(i,j) = \min{f(i+1,j),\ f(i,j-1)} + 1 f(i,j)=minf(i+1,j), f(i,j1)+1

边界条件

i = j i=j i=j 时, f ( i , j ) = 0 f(i,j) = 0 f(i,j)=0

答案

答案即为 f ( 0 , n − 1 ) f(0,n-1) f(0,n1),其中 n n n 是字符串的长度。

C++ 代码实现

以下是使用区间 DP 来解决该问题的 C++ 代码实现:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    string s;
    cin >> s;

    int n = s.size();
    vector<vector<int>> f(n, vector<int>(n, 0));
    for (int len = 2; len <= n; len++) {
        for (int i = 0, j = i+len-1; j < n; i++, j++) {
            if (s[i] == s[j]) {
                f[i][j] = f[i+1][j-1];
            } else {
                f[i][j] = min(f[i+1][j], f[i][j-1]) + 1;
            }
        }
    }

    cout << f[0][n-1] << endl;

    return 0;
}

这个算法的正确性如何证明?

我们可以使用数学归纳法来证明该算法的正确性。

首先,当区间长度为 1 时,即 i = j i=j i=j,此时 f ( i , j ) = 0 f(i,j)=0 f(i,j)=0,显然满足条件。

然后,我们假设对于任意的区间长度 l e n ≤ k len \le k lenk,算法都能够正确计算出对应的删除字符个数。我们需要证明当区间长度为 l e n = k + 1 len=k+1 len=k+1 时,算法也能够正确计算出对应的删除字符个数。

对于区间 [ i , j ] [i,j] [i,j],如果 s i = s j s_i=s_j si=sj,那么我们可以将 s i s_i si s j s_j sj 分别看成字符串的开头和结尾,然后递归计算 f ( i + 1 , j − 1 ) f(i+1,j-1) f(i+1,j1) 即可。由于 f ( i + 1 , j − 1 ) f(i+1,j-1) f(i+1,j1) 也是一个区间的解,根据归纳假设,我们可以认为 f ( i + 1 , j − 1 ) f(i+1,j-1) f(i+1,j1) 已经计算正确。因此,当 s i = s j s_i=s_j si=sj 时, f ( i , j ) f(i,j) f(i,j) 肯定能够被正确计算出。

如果 s i ≠ s j s_i \neq s_j si=sj,那么我们可以删除 s i s_i si 或者 s j s_j sj,然后递归处理剩下的区间。由于 s i s_i si s j s_j sj 是两个不同的字符,因此我们可以将 s i s_i si s j s_j sj 分别看成字符串的开头和结尾,然后递归计算 f ( i + 1 , j ) f(i+1,j) f(i+1,j) f ( i , j − 1 ) f(i,j-1) f(i,j1)。由于 f ( i + 1 , j ) f(i+1,j) f(i+1,j) f ( i , j − 1 ) f(i,j-1) f(i,j1) 都是区间的解,根据归纳假设,我们可以认为它们已经被正确计算出。因此,当 s i ≠ s j s_i \neq s_j si=sj 时, f ( i , j ) f(i,j) f(i,j) 肯定能够被正确计算出。

综上所述,使用数学归纳法可以证明该算法的正确性。

具体讲讲 f [ i ] [ j ] = m i n ( f [ i + 1 ] [ j ] , f [ i ] [ j − 1 ] ) + 1 f[i][j] = min(f[i + 1][j], f[i][j - 1]) + 1 f[i][j]=min(f[i+1][j],f[i][j1])+1;

对于状态 f ( i , j ) f(i,j) f(i,j),如果 s i = s j s_i=s_j si=sj,那么 f ( i , j ) f(i,j) f(i,j) 的值与 f ( i + 1 , j − 1 ) f(i+1,j-1) f(i+1,j1) 的值相同。因此,我们只需要考虑 s i ≠ s j s_i \neq s_j si=sj 的情况。

由于 s i ≠ s j s_i \neq s_j si=sj,我们只能删除 s i s_i si 或者 s j s_j sj 中的一个字符,然后递归计算 f ( i + 1 , j ) f(i+1,j) f(i+1,j) 或者 f ( i , j − 1 ) f(i,j-1) f(i,j1)。那么,我们需要选择删除哪一个字符呢?

我们可以考虑两种情况:

如果删除 s i s_i si,那么 s i + 1 s_{i+1} si+1 s j s_j sj 可能成为镜像字符串的开头和结尾,因此我们需要计算 f ( i + 1 , j ) f(i+1,j) f(i+1,j)
如果删除 s j s_j sj,那么 s i s_i si s j − 1 s_{j-1} sj1 可能成为镜像字符串的开头和结尾,因此我们需要计算 f ( i , j − 1 ) f(i,j-1) f(i,j1)
由于我们希望计算出的是最少的删除字符个数,因此我们需要取上述两种情况的最小值,即 f ( i , j ) = min ⁡ f ( i + 1 , j ) ,   f ( i , j − 1 ) + 1 f(i,j) = \min{f(i+1,j),\ f(i,j-1)} + 1 f(i,j)=minf(i+1,j), f(i,j1)+1

该转移方程的含义是,我们选择删除 s i s_i si 或者 s j s_j sj 中的一个字符,然后递归计算剩下的区间的删除字符个数,再加上删除的这个字符。我们需要选择删除字符个数最少的那种情况。

因此,状态转移方程 f ( i , j ) = min ⁡ f ( i + 1 , j ) ,   f ( i , j − 1 ) + 1 f(i,j) = \min{f(i+1,j),\ f(i,j-1)} + 1 f(i,j)=minf(i+1,j), f(i,j1)+1 是合理的。


len为啥要从2开始

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    string s;
    cin >> s;

    int n = s.size();
    vector<vector<int>> f(n, vector<int>(n, 0));
    for (int len = 2; len <= n; len++)
    {
        for (int i = 0, j = i + len - 1; j < n; i++, j++)
        {
            if (s[i] == s[j])
            {
                f[i][j] = f[i + 1][j - 1];//j>=1 -> i+len-1>=1,i>=0 ->  len-1>=1 -> len>=2  
            }
            else
            {
                f[i][j] = min(f[i + 1][j], f[i][j - 1]) + 1;
            }
        }
    }

    cout << f[0][n - 1] << endl;

    return 0;
}

该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),其中 n n n 是字符串的长度。由于第一个 for 循环的循环次数为 n − 1 n-1 n1,第二个 for 循环的循环次数为 n − l e n + 1 n-len+1 nlen+1,因此总的循环次数为:

∑ l e n = 2 n ( n − l e n + 1 ) = n ( n − 1 ) 2 \sum_{len=2}^{n} (n-len+1) = \frac{n(n-1)}{2} len=2n(nlen+1)=2n(n1)

因此,该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

踏过山河,踏过海

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

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

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

打赏作者

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

抵扣说明:

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

余额充值