题目链接

小红的字符串修改

题目描述

小红有两个由小写字母构成的字符串 。她可以对字符串 进行一种操作:将其中任意一个字母替换成其在字母表中的相邻字母(例如 'c' 可以变成 'b' 或 'd')。每次替换算作一次操作。

现在,小红想知道,最少需要多少次操作,才能使得字符串 变成字符串 的一个子串。

解题思路

这道题的目标是找到一个最优的对齐方式,使得字符串 与字符串 的某个子串匹配时,所需的总操作次数最少。

首先,我们要理解“替换成相邻字母”这个操作的成本。将一个字母 char1 替换成 char2,最少需要的操作次数等于它们在字母表中的距离,也就是它们 ASCII 码值之差的绝对值,即 abs(char1 - char2)

既然要让 成为 的子串,那么修改后的 长度必须和原 相同。设 的长度为 的长度为 。我们可以将 中所有长度为 的子串进行比较。

我们可以使用一个“滑动窗口”的思路:

  1. 创建一个长度为 的窗口,在字符串 上从左到右滑动。
  2. 窗口每滑动到一个新的位置,就得到了 的一个子串。设这个子串为 sub
  3. 我们计算将 变换成 sub 所需的总成本。这个成本是 sub 中对应位置上所有字符的变换成本之和。 即:
  4. 我们遍历所有可能的窗口位置(即 的所有长度为 的子串),计算出每一个位置的成本,并记录下这些成本中的最小值。
  5. 遍历结束后,记录的这个最小值就是最终的答案。

例如,s = "abc", t = "abbc"

  • 窗口在 的位置0,子串是 "abb"。成本 = abs('a'-'a') + abs('b'-'b') + abs('c'-'b') =
  • 窗口在 的位置1,子串是 "bbc"。成本 = abs('a'-'b') + abs('b'-'b') + abs('c'-'c') = 。 所有可能成本为 ,最小值为

代码

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

using namespace std;

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

    int len_s = s.length();
    int len_t = t.length();
    
    long long min_total_cost = numeric_limits<long long>::max();

    // 滑动窗口遍历t中所有长度为len_s的子串
    for (int i = 0; i <= len_t - len_s; ++i) {
        long long current_total_cost = 0;
        // 计算s和当前t的子串的转换成本
        for (int j = 0; j < len_s; ++j) {
            int diff = abs(s[j] - t[i + j]);
            // 考虑循环字母表的最短距离
            current_total_cost += min(diff, 26 - diff);
        }
        min_total_cost = min(min_total_cost, current_total_cost);
    }

    cout << min_total_cost << '\n';

    return 0;
}
import java.util.Scanner;
import java.lang.Math;

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

        int lenS = s.length();
        int lenT = t.length();

        long minTotalCost = Long.MAX_VALUE;

        // 滑动窗口遍历t中所有长度为lenS的子串
        for (int i = 0; i <= lenT - lenS; i++) {
            long currentTotalCost = 0;
            // 计算s和当前t的子串的转换成本
            for (int j = 0; j < lenS; j++) {
                int diff = Math.abs(s.charAt(j) - t.charAt(i + j));
                // 考虑循环字母表的最短距离
                currentTotalCost += Math.min(diff, 26 - diff);
            }
            minTotalCost = Math.min(minTotalCost, currentTotalCost);
        }

        System.out.println(minTotalCost);
    }
}
# 读取输入
s = input()
t = input()

len_s = len(s)
len_t = len(t)

min_total_cost = float('inf')

# 滑动窗口遍历t中所有长度为len_s的子串
for i in range(len_t - len_s + 1):
    current_total_cost = 0
    # 提取t的子串
    sub_t = t[i : i + len_s]
    # 计算s和当前t的子串的转换成本
    for j in range(len_s):
        diff = abs(ord(s[j]) - ord(sub_t[j]))
        # 考虑循环字母表的最短距离
        current_total_cost += min(diff, 26 - diff)
    
    min_total_cost = min(min_total_cost, current_total_cost)

print(min_total_cost)

算法及复杂度

  • 算法:滑动窗口 / 暴力匹配。
  • 时间复杂度:外层循环遍历 的所有可能的起始位置,共 次。内层循环遍历字符串 的所有字符,共 次。因此,总的时间复杂度为 ,可以近似看作
  • 空间复杂度:我们只使用了少数几个变量来存储长度、成本和循环计数器,没有使用与输入规模成正比的额外空间,因此空间复杂度为