题目链接

小红的字符串

题目描述

给定一个目标字符串 。我们可以从一个空字符串开始,通过以下两种操作来构建它:

  1. 在当前字符串末尾添加任意一个字符。
  2. 复制当前整个字符串,并粘贴到末尾(此操作最多只能使用一次)。

求解构建出目标字符串 所需的最少操作次数。

思路分析

这是一个典型的动态规划或枚举优化问题。解决问题的关键是决策是否使用以及何时使用那唯一的一次“复制粘贴”操作。我们可以分两种情况来讨论。

设目标字符串 的长度为

情况一:不使用“复制粘贴”操作

如果完全不使用复制操作,那么要得到长度为 的字符串 ,我们必须执行 次“添加字符”操作。因此,这种情况下的成本固定为 。我们可以将其作为最终答案的一个基准值。

情况二:使用一次“复制粘贴”操作

“复制粘贴”操作的效果是将当前字符串 变为 。这意味着,如果我们决定使用此操作,那么目标字符串 的某个前缀必须呈现出 的结构。

假设我们选择在构建了 的前 个字符(记为 )后执行复制操作。要使得这个操作有意义且正确,复制后得到的字符串 必须是 的一个前缀。这要求 的前 个字符与紧接着的后 个字符完全相同,即

如果这个条件满足,我们来计算总操作数:

  1. 构建前缀 : 需要 次“添加字符”操作。
  2. 复制粘贴: 需要 次操作。此时,我们得到了长度为 的前缀
  3. 构建剩余部分: 目标字符串 总长为 ,还剩下 个字符需要添加。这需要 次“添加字符”操作。

因此,对于一个可行的复制点 ,总操作数为

最终策略

我们的目标是找到最小的操作次数。所以,最终答案就是 情况一的成本所有可行的 情况二的成本 中的最小值。

算法步骤如下:

  1. 初始化最小操作数 min_ops = n
  2. 遍历所有可能的前缀(复制点)长度 ,范围从
  3. 对于每个 ,判断 的前 个字符是否等于其后 个字符。
  4. 如果相等,则计算成本 ,并用它来更新 min_opsmin_ops = min(min_ops, n - i + 1)
  5. 遍历结束后,min_ops 即为最终答案。

代码

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    string s;
    cin >> s;

    int n = s.length();
    int min_ops = n; // 基准情况:不使用复制操作

    // 遍历所有可能的前缀长度 i
    for (int i = 1; i <= n / 2; ++i) {
        // 检查 S[0...i-1] 是否等于 S[i...2i-1]
        if (s.substr(0, i) == s.substr(i, i)) {
            // 如果可以复制,计算当前操作数
            // i 次添加 + 1 次复制 + (n - 2*i) 次添加
            int current_ops = (i + 1) + (n - 2 * i);
            min_ops = min(min_ops, current_ops);
        }
    }

    cout << min_ops << endl;

    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s = sc.next();

        int n = s.length();
        int minOps = n; // 基准情况:不使用复制操作

        // 遍历所有可能的前缀长度 i
        for (int i = 1; i <= n / 2; i++) {
            String prefix = s.substring(0, i);
            String nextPart = s.substring(i, 2 * i);

            if (prefix.equals(nextPart)) {
                // 如果可以复制,计算当前操作数
                // i 次添加 + 1 次复制 + (n - 2*i) 次添加
                int currentOps = (i + 1) + (n - 2 * i);
                minOps = Math.min(minOps, currentOps);
            }
        }

        System.out.println(minOps);
    }
}
def solve():
    s = input()
    n = len(s)
    min_ops = n  # 基准情况:不使用复制操作

    # 遍历所有可能的前缀长度 i
    for i in range(1, n // 2 + 1):
        # 检查 s[0:i] 是否等于 s[i:2*i]
        if s[:i] == s[i:2*i]:
            # 如果可以复制,计算当前操作数
            # i 次添加 + 1 次复制 + (n - 2*i) 次添加
            current_ops = (i + 1) + (n - 2 * i)
            min_ops = min(min_ops, current_ops)
    
    print(min_ops)

solve()

算法及复杂度

  • 算法:枚举
  • 时间复杂度。外层循环遍历前缀长度 ,最多 次。内层的字符串切片和比较操作,在最坏情况下需要 的时间。因此总体复杂度为
  • 空间复杂度,用于存储目标字符串以及切片时产生的临时字符串。