Minimum Cost to Convert String II

hard graph shortest path dynamic programming trie

Problem

You are given equal-length strings source and target, plus rules original[i] → changed[i] each with a cost[i]. A single operation replaces a substring equal to some original[i] with the matching changed[i], paying its cost; rules may be chained. Two operations must act on disjoint or identical index ranges. Return the minimum total cost to turn source into target, or -1 if impossible.

Inputsource = "abcd", target = "acbe", original = ["a","b","c","c","e","d"], changed = ["b","c","b","e","b","e"], cost = [2,5,5,1,2,20]
Output28
b→c costs 5; c→e→b costs 1+2=3; d→e costs 20. Total 5 + 3 + 20 = 28.

def minimumCost(source, target, original, changed, cost):
    INF = float('inf')
    ids = {}                                   # unique string -> id
    def gid(s):
        if s not in ids: ids[s] = len(ids)
        return ids[s]
    m = len(original)
    for i in range(m):                         # register every endpoint
        gid(original[i]); gid(changed[i])
    k = len(ids)
    dist = [[INF] * k for _ in range(k)]       # all-pairs cheapest convert
    for i in range(k): dist[i][i] = 0
    for i in range(m):                         # seed direct edges (keep min)
        u, v = ids[original[i]], ids[changed[i]]
        dist[u][v] = min(dist[u][v], cost[i])
    for w in range(k):                         # Floyd-Warshall
        for i in range(k):
            if dist[i][w] == INF: continue
            for j in range(k):
                if dist[i][w] + dist[w][j] < dist[i][j]:
                    dist[i][j] = dist[i][w] + dist[w][j]
    lens = sorted({len(s) for s in original})  # rule lengths to try
    n = len(source)
    dp = [INF] * (n + 1)                        # dp[i]=cost for prefix len i
    dp[0] = 0
    for i in range(1, n + 1):
        if source[i - 1] == target[i - 1] and dp[i - 1] < INF:
            dp[i] = dp[i - 1]                  # chars match: free
        for L in lens:
            j = i - L
            if j < 0 or dp[j] == INF: continue
            a, b = source[j:i], target[j:i]    # substring conversion
            if a in ids and b in ids:
                c = dist[ids[a]][ids[b]]
                if c < INF: dp[i] = min(dp[i], dp[j] + c)
    return dp[n] if dp[n] < INF else -1
function minimumCost(source, target, original, changed, cost) {
  const INF = Infinity, ids = new Map();        // unique string -> id
  const gid = s => { if (!ids.has(s)) ids.set(s, ids.size); return ids.get(s); };
  const m = original.length;
  for (let i = 0; i < m; i++) { gid(original[i]); gid(changed[i]); }
  const k = ids.size;
  const dist = Array.from({ length: k }, () => new Array(k).fill(INF));
  for (let i = 0; i < k; i++) dist[i][i] = 0;
  for (let i = 0; i < m; i++) {                  // seed direct edges
    const u = ids.get(original[i]), v = ids.get(changed[i]);
    dist[u][v] = Math.min(dist[u][v], cost[i]);
  }
  for (let w = 0; w < k; w++)                    // Floyd-Warshall
    for (let i = 0; i < k; i++) {
      if (dist[i][w] === INF) continue;
      for (let j = 0; j < k; j++)
        if (dist[i][w] + dist[w][j] < dist[i][j])
          dist[i][j] = dist[i][w] + dist[w][j];
    }
  const lens = [...new Set(original.map(s => s.length))].sort((a, b) => a - b);
  const n = source.length, dp = new Array(n + 1).fill(INF);
  dp[0] = 0;                                     // dp[i]=cost for prefix i
  for (let i = 1; i <= n; i++) {
    if (source[i - 1] === target[i - 1] && dp[i - 1] < INF)
      dp[i] = dp[i - 1];                         // chars match: free
    for (const L of lens) {
      const j = i - L;
      if (j < 0 || dp[j] === INF) continue;
      const a = source.slice(j, i), b = target.slice(j, i);
      if (ids.has(a) && ids.has(b)) {            // substring conversion
        const c = dist[ids.get(a)][ids.get(b)];
        if (c < INF) dp[i] = Math.min(dp[i], dp[j] + c);
      }
    }
  }
  return dp[n] < INF ? dp[n] : -1;
}
long minimumCost(String source, String target, String[] original,
                String[] changed, int[] cost) {
    final long INF = Long.MAX_VALUE / 4;
    Map<String, Integer> ids = new HashMap<>();   // unique string -> id
    for (String s : original) ids.putIfAbsent(s, ids.size());
    for (String s : changed)  ids.putIfAbsent(s, ids.size());
    int k = ids.size(), m = original.length;
    long[][] dist = new long[k][k];
    for (long[] row : dist) Arrays.fill(row, INF);
    for (int i = 0; i < k; i++) dist[i][i] = 0;
    for (int i = 0; i < m; i++) {                 // seed direct edges
        int u = ids.get(original[i]), v = ids.get(changed[i]);
        dist[u][v] = Math.min(dist[u][v], cost[i]);
    }
    for (int w = 0; w < k; w++)                   // Floyd-Warshall
        for (int i = 0; i < k; i++) {
            if (dist[i][w] == INF) continue;
            for (int j = 0; j < k; j++)
                if (dist[i][w] + dist[w][j] < dist[i][j])
                    dist[i][j] = dist[i][w] + dist[w][j];
        }
    TreeSet<Integer> lens = new TreeSet<>();
    for (String s : original) lens.add(s.length());
    int n = source.length();
    long[] dp = new long[n + 1];
    Arrays.fill(dp, INF); dp[0] = 0;             // dp[i]=cost for prefix i
    for (int i = 1; i <= n; i++) {
        if (source.charAt(i - 1) == target.charAt(i - 1) && dp[i - 1] < INF)
            dp[i] = dp[i - 1];                    // chars match: free
        for (int L : lens) {
            int j = i - L;
            if (j < 0 || dp[j] == INF) continue;
            String a = source.substring(j, i), b = target.substring(j, i);
            if (ids.containsKey(a) && ids.containsKey(b)) {
                long c = dist[ids.get(a)][ids.get(b)];
                if (c < INF) dp[i] = Math.min(dp[i], dp[j] + c);
            }
        }
    }
    return dp[n] < INF ? dp[n] : -1;
}
long long minimumCost(string source, string target, vector<string>& original,
                      vector<string>& changed, vector<int>& cost) {
    const long long INF = LLONG_MAX / 4;
    unordered_map<string, int> ids;               // unique string -> id
    auto gid = [&](const string& s) {
        if (!ids.count(s)) ids[s] = ids.size();
        return ids[s];
    };
    int m = original.size();
    for (int i = 0; i < m; i++) { gid(original[i]); gid(changed[i]); }
    int k = ids.size();
    vector<vector<long long>> dist(k, vector<long long>(k, INF));
    for (int i = 0; i < k; i++) dist[i][i] = 0;
    for (int i = 0; i < m; i++) {                 // seed direct edges
        int u = ids[original[i]], v = ids[changed[i]];
        dist[u][v] = min(dist[u][v], (long long)cost[i]);
    }
    for (int w = 0; w < k; w++)                   // Floyd-Warshall
        for (int i = 0; i < k; i++) {
            if (dist[i][w] == INF) continue;
            for (int j = 0; j < k; j++)
                if (dist[i][w] + dist[w][j] < dist[i][j])
                    dist[i][j] = dist[i][w] + dist[w][j];
        }
    set<int> lens;
    for (auto& s : original) lens.insert(s.size());
    int n = source.size();
    vector<long long> dp(n + 1, INF);
    dp[0] = 0;                                    // dp[i]=cost for prefix i
    for (int i = 1; i <= n; i++) {
        if (source[i - 1] == target[i - 1] && dp[i - 1] < INF)
            dp[i] = dp[i - 1];                    // chars match: free
        for (int L : lens) {
            int j = i - L;
            if (j < 0 || dp[j] == INF) continue;
            string a = source.substr(j, L), b = target.substr(j, L);
            if (ids.count(a) && ids.count(b)) {
                long long c = dist[ids[a]][ids[b]];
                if (c < INF) dp[i] = min(dp[i], dp[j] + c);
            }
        }
    }
    return dp[n] < INF ? dp[n] : -1;
}
Time: O(k³ + n·L·m) Space: O(k² + n)