【LeetCode 每日一题】2976. 转换字符串的最小成本 I

Problem: 2976. 转换字符串的最小成本 I

文章目录

  • 整体思路
      • [1. 核心问题](#1. 核心问题)
      • [2. 算法与逻辑步骤](#2. 算法与逻辑步骤)
  • 完整代码
  • 时空复杂度
      • [1. 时间复杂度: O ( N + M + C 3 ) O(N + M + C^3) O(N+M+C3)](#1. 时间复杂度: O ( N + M + C 3 ) O(N + M + C^3) O(N+M+C3))
      • [2. 空间复杂度: O ( C 2 ) O(C^2) O(C2)](#2. 空间复杂度: O ( C 2 ) O(C^2) O(C2))

整体思路

1. 核心问题

我们需要将 source 字符串转换为 target 字符串。转换规则由 original, changed, cost 数组给出,表示将字符 original[i] 变为 changed[i] 需要花费 cost[i]

转换具有传递性(例如 a -> b -> c),我们需要求出总的最小成本。如果无法转换,返回 -1。

2. 算法与逻辑步骤

这是一个典型的多源最短路径问题

  1. 图的建模

    • 将 26 个小写英文字母看作图中的 26 个节点('a' 对应 0,...,'z' 对应 25)。
    • 输入的转换规则即为图中的有向边,权重为转换成本。
    • 我们需要计算任意两个字符之间的最小转换代价。
  2. Floyd-Warshall 算法

    • 由于节点数固定且非常少(仅 26 个),Floyd-Warshall 算法是解决此问题的最佳选择。它可以一次性计算出所有节点对之间的最短路径。
    • 初始化 :创建一个 26 × 26 26 \times 26 26×26 的矩阵 minCosts
      • 对角线(自己转自己)设为 0。
      • 其他位置初始化为无穷大(这里用 Integer.MAX_VALUE / 2 防止加法溢出)。
      • 根据输入的边 (original, changed, cost) 更新矩阵。注意可能有重边(多条规则描述同一种转换),取最小值。
    • 三重循环松弛 :通过中间节点 k,尝试优化从 ij 的路径:minCosts[i][j] = min(minCosts[i][j], minCosts[i][k] + minCosts[k][j])
  3. 计算总成本

    • 预处理完成后,我们拥有了一个查找表 minCosts,可以在 O ( 1 ) O(1) O(1) 时间内查询任意字符转换的最优代价。
    • 遍历 sourcetarget 字符串,逐个字符查询转换成本并累加。
    • 如果在任意位置查询到的成本为无穷大(UNREACHABLE),说明不可达,立即返回 -1。

完整代码

java 复制代码
import java.util.Arrays;

class Solution {
    public long minimumCost(String source, String target, char[] original, char[] changed, int[] cost) {
        // 定义一个足够大的数表示不可达,但不能是 Integer.MAX_VALUE,否则相加会溢出
        final int UNREACHABLE = Integer.MAX_VALUE / 2;
        // 字母表大小,固定为 26
        final int ALPHABET_SIZE = 26;
        
        // minCosts[i][j] 存储从字符 i 转换到字符 j 的最小成本
        int[][] minCosts = new int[ALPHABET_SIZE][ALPHABET_SIZE];
        
        // 1. 初始化距离矩阵
        for (int i = 0; i < ALPHABET_SIZE; i++) {
            // 默认所有转换都不可达
            Arrays.fill(minCosts[i], UNREACHABLE);
            // 字符转换为自身的成本为 0
            minCosts[i][i] = 0;
        }
        
        // 2. 根据输入构建初始图
        for (int i = 0; i < cost.length; i++) {
            int fromIndex = original[i] - 'a';
            int toIndex = changed[i] - 'a';
            // 注意:输入中可能存在多条相同起止点的边,必须取最小值
            minCosts[fromIndex][toIndex] = Math.min(minCosts[fromIndex][toIndex], cost[i]);
        }
        
        // 3. 使用 Floyd-Warshall 算法计算所有节点对的最短路径
        // k 是中间节点
        for (int k = 0; k < ALPHABET_SIZE; k++) {
            // i 是起始节点
            for (int i = 0; i < ALPHABET_SIZE; i++) {
                // 剪枝:如果起点到中间点不可达,就没必要继续看了
                if (minCosts[i][k] == UNREACHABLE) {
                    continue; 
                }
                // j 是终点
                for (int j = 0; j < ALPHABET_SIZE; j++) {
                    // 状态转移:比较 "直接从 i 到 j" 和 "经由 k 从 i 到 j" 的成本
                    if (minCosts[k][j] != UNREACHABLE) {
                        minCosts[i][j] = Math.min(minCosts[i][j], minCosts[i][k] + minCosts[k][j]);
                    }
                }
            }
        }

        // 4. 计算 source 转换到 target 的总成本
        long totalCost = 0; // 使用 long 防止总成本溢出 int 范围
        int len = source.length();
        
        for (int i = 0; i < len; i++) {
            int sourceCharIdx = source.charAt(i) - 'a';
            int targetCharIdx = target.charAt(i) - 'a';
            
            // 查表获取最小转换成本
            int currentPairCost = minCosts[sourceCharIdx][targetCharIdx];
            
            // 如果某个字符无法转换,说明整个任务失败
            if (currentPairCost == UNREACHABLE) {
                return -1;
            }
            totalCost += currentPairCost;
        }
        
        return totalCost;
    }
}

时空复杂度

假设 source 的长度为 N N N,转换规则数组(边)的长度为 M M M,字符集大小为 C C C(本题中 C = 26 C=26 C=26)。

1. 时间复杂度: O ( N + M + C 3 ) O(N + M + C^3) O(N+M+C3)

  • 初始化图 :遍历 M M M 条边,耗时 O ( M ) O(M) O(M)。
  • Floyd-Warshall 算法 :包含三重循环,每重循环次数为 C C C,耗时 O ( C 3 ) O(C^3) O(C3)。由于 C = 26 C=26 C=26,这是一个非常小的常数( 26 3 ≈ 1.7 × 10 4 26^3 \approx 1.7 \times 10^4 263≈1.7×104)。
  • 计算总成本 :遍历长度为 N N N 的字符串,每次查表 O ( 1 ) O(1) O(1),耗时 O ( N ) O(N) O(N)。
  • 总计 : O ( N + M + C 3 ) O(N + M + C^3) O(N+M+C3)。在实际应用中, N N N 通常是主导项。

2. 空间复杂度: O ( C 2 ) O(C^2) O(C2)

  • 距离矩阵 :我们需要一个 C × C C \times C C×C 的二维数组 minCosts 来存储最短路径。
  • 结论 : O ( C 2 ) O(C^2) O(C2),对于 C = 26 C=26 C=26,空间消耗极小且固定。
相关推荐
蒟蒻的贤2 小时前
滑动窗口策略
算法
闪电麦坤953 小时前
Leecode热题100:矩阵置零(矩阵)
线性代数·算法·矩阵
浅念-3 小时前
C语言——双向链表
c语言·数据结构·c++·笔记·学习·算法·链表
Wh-Constelltion3 小时前
【PQ分解法潮流计算(matlab版)】
算法·matlab
只是懒得想了3 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
码农水水4 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
m0_736919104 小时前
模板编译期图算法
开发语言·c++·算法
dyyx1114 小时前
基于C++的操作系统开发
开发语言·c++·算法
m0_736919104 小时前
C++安全编程指南
开发语言·c++·算法