Hard

题目描述

给你两个长度为 n 的 0 索引字符串 sourcetarget,两者都由小写英文字母组成。还给你两个 0 索引字符串数组 originalchanged,以及一个整数数组 cost,其中 cost[i] 表示将字符串 original[i] 转换为字符串 changed[i] 的成本。

你从字符串 source 开始。在一次操作中,你可以从字符串中选择一个子字符串 x,并以成本 z 将其改为 y,如果存在任意索引 j 使得 cost[j] == zoriginal[j] == xchanged[j] == y。你可以进行任意次数的操作,但任意一对操作必须满足以下两个条件之一:

  • 操作中选择的子字符串是 source[a..b]source[c..d],其中 b < cd < a。换句话说,两次操作中选择的索引是不相交的。
  • 操作中选择的子字符串是 source[a..b]source[c..d],其中 a == cb == d。换句话说,两次操作中选择的索引是相同的。

使用任意次数的操作,返回将字符串 source 转换为字符串 target 的最小成本。如果无法将 source 转换为 target,则返回 -1

示例 1:

输入:source = "abcd", target = "acbe", original = ["a","b","c","c","e","d"], changed = ["b","c","b","e","b","e"], cost = [2,5,5,1,2,20]
输出:28

示例 2:

输入:source = "abcdefgh", target = "acdeeghh", original = ["bcd","fgh","thh"], changed = ["cde","thh","ghh"], cost = [1,3,5]
输出:9

示例 3:

输入:source = "abcdefgh", target = "addddddd", original = ["bcd","defgh"], changed = ["ddd","ddddd"], cost = [100,1578]
输出:-1

提示:

  • 1 <= source.length == target.length <= 1000
  • source, target 只包含小写英文字母
  • 1 <= cost.length == original.length == changed.length <= 100
  • 1 <= original[i].length == changed[i].length <= source.length
  • original[i], changed[i] 只包含小写英文字母
  • original[i] != changed[i]
  • 1 <= cost[i] <= 10^6

解题思路

这是一个复杂的字符串转换问题,需要结合动态规划、图论和字符串匹配技术。

主要思路:

  1. 字符串映射与最短路径预处理:首先为所有出现在 originalchanged 中的唯一字符串分配ID,然后使用 Floyd-Warshall 算法计算任意两个字符串之间的最小转换成本。

  2. 动态规划状态设计:定义 dp[i] 表示将 source 的前 i 个字符转换为 target 对应前缀的最小成本。

  3. 状态转移:对于每个位置 i,考虑两种情况:

    • 如果 source[i-1] == target[i-1],则 dp[i] = dp[i-1]
    • 枚举所有可能的子字符串 source[j..i-1] 和对应的 target[j..i-1],如果存在转换路径,则更新 dp[i] = min(dp[i], dp[j-1] + cost)
  4. 字符串匹配优化:使用前缀树(Trie)或哈希表来快速检查某个子字符串是否可以进行转换。

  5. 算法流程

    • 构建字符串到ID的映射
    • 用Floyd算法预计算所有字符串对之间的最短转换成本
    • 使用动态规划计算最终答案

时间复杂度:O(m³ + n²),其中m是唯一字符串数量,n是源字符串长度。 空间复杂度:O(m² + n)。

代码实现

class Solution {
public:
    long long minimumCost(string source, string target, vector<string>& original, vector<string>& changed, vector<int>& cost) {
        unordered_map<string, int> stringToId;
        vector<string> idToString;
        int nextId = 0;
        
        // 分配字符串ID
        auto getId = [&](const string& s) {
            if (stringToId.find(s) == stringToId.end()) {
                stringToId[s] = nextId++;
                idToString.push_back(s);
            }
            return stringToId[s];
        };
        
        // 建图
        for (int i = 0; i < original.size(); i++) {
            getId(original[i]);
            getId(changed[i]);
        }
        
        int m = nextId;
        vector<vector<long long>> dist(m, vector<long long>(m, LLONG_MAX));
        
        // 初始化距离
        for (int i = 0; i < m; i++) {
            dist[i][i] = 0;
        }
        
        for (int i = 0; i < original.size(); i++) {
            int u = getId(original[i]);
            int v = getId(changed[i]);
            dist[u][v] = min(dist[u][v], (long long)cost[i]);
        }
        
        // Floyd算法
        for (int k = 0; k < m; k++) {
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < m; j++) {
                    if (dist[i][k] != LLONG_MAX && dist[k][j] != LLONG_MAX) {
                        dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                    }
                }
            }
        }
        
        int n = source.length();
        vector<long long> dp(n + 1, LLONG_MAX);
        dp[0] = 0;
        
        for (int i = 1; i <= n; i++) {
            // 如果字符相同,无需转换
            if (source[i-1] == target[i-1]) {
                dp[i] = dp[i-1];
            }
            
            // 尝试所有可能的子字符串转换
            for (int j = 0; j < i; j++) {
                if (dp[j] == LLONG_MAX) continue;
                
                string srcSub = source.substr(j, i - j);
                string tgtSub = target.substr(j, i - j);
                
                if (srcSub == tgtSub) {
                    dp[i] = min(dp[i], dp[j]);
                } else if (stringToId.count(srcSub) && stringToId.count(tgtSub)) {
                    int srcId = stringToId[srcSub];
                    int tgtId = stringToId[tgtSub];
                    if (dist[srcId][tgtId] != LLONG_MAX) {
                        dp[i] = min(dp[i], dp[j] + dist[srcId][tgtId]);
                    }
                }
            }
        }
        
        return dp[n] == LLONG_MAX ? -1 : dp[n];
    }
};
class Solution:
    def minimumCost(self, source: str, target: str, original: List[str], changed: List[str], cost: List[int]) -> int:
        from collections import defaultdict
        
        # 分配字符串ID
        string_to_id = {}
        id_to_string = []
        next_id = 0
        
        def get_id(s):
            nonlocal next_id
            if s not in string_to_id:
                string_to_id[s] = next_id
                id_to_string.append(s)
                next_id += 1
            return string_to_id[s]
        
        # 建图
        for i in range(len(original)):
            get_id(original[i])
            get_id(changed[i])
        
        m = next_id
        dist = [[float('inf')] * m for _ in range(m)]
        
        # 初始化距离
        for i in range(m):
            dist[i][i] = 0
        
        for i in range(len(original)):
            u = get_id(original[i])
            v = get_id(changed[i])
            dist[u][v] = min(dist[u][v], cost[i])
        
        # Floyd算法
        for k in range(m):
            for i in range(m):
                for j in range(m):
                    if dist[i][k] != float('inf') and dist[k][j] != float('inf'):
                        dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
        
        n = len(source)
        dp = [float('inf')] * (n + 1)
        dp[0] = 0
        
        for i in range(1, n + 1):
            # 如果字符相同,无需转换
            if source[i-1] == target[i-1]:
                dp[i] = dp[i-1]
            
            # 尝试所有可能的子字符串转换
            for j in range(i):
                if dp[j] == float('inf'):
                    continue
                
                src_sub = source[j:i]
                tgt_sub = target[j:i]
                
                if src_sub == tgt_sub:
                    dp[i] = min(dp[i], dp[j])
                elif src_sub in string_to_id and tgt_sub in string_to_id:
                    src_id = string_to_id[src_sub]
                    tgt_id = string_to_id[tgt_sub]
                    if dist[src_id][tgt_id] != float('inf'):
                        dp[i] = min(dp[i], dp[j] + dist[src_id][tgt_id])
        
        return -1 if dp[n] == float('inf') else dp[n]
public class Solution {
    public long MinimumCost(string source, string target, string[] original, string[] changed, int[] cost) {
        var stringToId = new Dictionary<string, int>();
        var idToString = new List<string>();
        int nextId = 0;
        
        int GetId(string s) {
            if (!stringToId.ContainsKey(s)) {
                stringToId[s] = nextId++;
                idToString.Add(s);
            }
            return stringToId[s];
        }
        
        // 建图
        for (int i = 0; i < original.Length; i++) {
            GetId(original[i]);
            GetId(changed[i]);
        }
        
        int m = nextId;
        long[,] dist = new long[m, m];
        
        // 初始化距离
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < m; j++) {
                dist[i, j] = i == j ? 0 : long.MaxValue;
            }
        }
        
        for (int i = 0; i < original.Length; i++) {
            int u = GetId(original[i]);
            int v = GetId(changed[i]);
            dist[u, v] = Math.Min(dist[u, v], cost[i]);
        }
        
        // Floyd算法
        for (int k = 0; k < m; k++) {
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < m; j++) {
                    if (dist[i, k] != long.MaxValue && dist[k, j] != long.MaxValue) {
                        dist[i, j] = Math.Min(dist[i, j], dist[i, k] + dist[k, j]);
                    }
                }
            }
        }
        
        int n = source.Length;
        long[] dp = new long[n + 1];
        for (int i = 0; i <= n; i++) {
            dp[i] = long.MaxValue;
        }
        dp[0] = 0;
        
        for (int i = 1; i <= n; i++) {
            // 如果字符相同,无需转换
            if (source[i-1] == target[i-1]) {
                dp[i] = dp[i-1];
            }
            
            // 尝试所有可能的子字符串转换
            for (int j = 0; j < i; j++) {
                if (dp[j] == long.MaxValue) continue;
                
                string srcSub = source.Substring(j, i - j);
                string tgtSub = target.Substring(j, i - j);
                
                if (srcSub == tgtSub) {
                    dp[i] = Math.Min(dp[i], dp[j]);
                } else if (stringToId.ContainsKey(srcSub) && stringToId.ContainsKey(tgtSub)) {
                    int srcId = stringToId[srcSub];
                    int tgtId = stringToId[tgtSub];
                    if (dist[srcId, tgtId] != long.MaxValue) {
                        dp[i] = Math.Min(dp[i], dp[j] + dist[srcId, tgtId]);
                    }
                }
            }
        }
        
        return dp[n] == long.MaxValue ? -1 : dp[n];
    }
}
var minimumCost = function(source, target, original, changed, cost) {
    const stringToId = new Map();
    const idToString = [];
    let nextId = 0;
    
    const getId = (s) => {
        if (!stringToId.has(s)) {
            stringToId.set(s, nextId++);
            idToString.push(s);
        }
        return stringToId.get(s);
    };
    
    // 建图
    for (let i = 0; i < original.length; i++) {
        getId(original[i]);
        getId(changed[i]);
    }

复杂度分析

指标复杂度
时间-
空间-

相关题目