Facebook Pixel

2976. Minimum Cost to Convert String I

Problem Description

You have two strings source and target of equal length n, both containing only lowercase English letters. You want to transform source into target using character replacements.

You're given three arrays that define the available character transformations:

  • original[i]: a character that can be changed
  • changed[i]: what the character can be changed to
  • cost[i]: the cost of changing original[i] to changed[i]

In each operation, you can pick any character x in your current string and change it to character y if there exists an index j where:

  • original[j] = x
  • changed[j] = y
  • The operation costs cost[j]

Note that the same character transformation (from x to y) might appear multiple times with different costs - you can choose any available option.

Your task is to find the minimum total cost to convert source to target. If it's impossible to do so, return -1.

For example, if source = "abc" and target = "adc", you need to change the character at position 1 from 'b' to 'd'. If there's a transformation available from 'b' to 'd' with cost 3, then the minimum cost would be 3. However, you might also chain transformations - for instance, if 'b' can change to 'e' with cost 1, and 'e' can change to 'd' with cost 1, then the minimum cost would be 2.

The solution uses Floyd-Warshall algorithm to find the shortest path (minimum cost) between any two characters, treating each character as a node in a graph and transformations as weighted directed edges.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing this as a graph problem where we need to find the minimum cost path between characters.

Think of each lowercase letter as a node in a graph. The given transformations create directed edges between these nodes, where the edge weight represents the transformation cost. When we need to change a character in source to match the corresponding character in target, we're essentially asking: "What's the cheapest way to get from node (source character) to node (target character)?"

The crucial observation is that we might not always have a direct transformation available. For instance, to change 'a' to 'c', we might not have a direct 'a' → 'c' transformation, but we could have 'a' → 'b' with cost 2 and 'b' → 'c' with cost 3, giving us a total cost of 5. This means we need to consider paths that go through intermediate characters.

Since we only have 26 possible characters (nodes), we can precompute the minimum cost between every pair of characters. This is a classic "all-pairs shortest path" problem. Floyd-Warshall algorithm is perfect here because:

  1. We have a small, dense graph (only 26 nodes)
  2. We need the shortest paths between all pairs of nodes
  3. The algorithm handles the case where multiple edges exist between the same pair of nodes (we just keep the minimum cost)

Once we've computed the minimum transformation cost between every pair of characters using Floyd-Warshall, converting source to target becomes straightforward. For each position where the characters differ, we look up the precomputed minimum cost. If any character can't be transformed (cost is infinity), the entire transformation is impossible.

The time complexity of Floyd-Warshall is O(26³) which is constant, and checking each character pair in the strings takes O(n), making this approach efficient.

Learn more about Graph and Shortest Path patterns.

Solution Approach

We implement the solution using Floyd-Warshall algorithm to find minimum transformation costs between all character pairs.

Step 1: Initialize the Cost Matrix

Create a 26×26 matrix g where g[i][j] represents the minimum cost to transform character i to character j:

  • Initialize all values to infinity: g[i][j] = inf
  • Set diagonal to 0 since transforming a character to itself costs nothing: g[i][i] = 0
g = [[inf] * 26 for _ in range(26)]
for i in range(26):
    g[i][i] = 0

Step 2: Populate Direct Transformation Costs

Process the input arrays original, changed, and cost. For each transformation:

  • Convert characters to indices (0-25) using ord(x) - ord('a')
  • Update the matrix with the minimum cost for each transformation (handling duplicates)
for x, y, z in zip(original, changed, cost):
    x = ord(x) - ord('a')
    y = ord(y) - ord('a')
    g[x][y] = min(g[x][y], z)

Step 3: Apply Floyd-Warshall Algorithm

Find the shortest paths between all pairs of characters by considering intermediate nodes:

  • For each intermediate character k
  • For each source character i and destination character j
  • Check if going through k gives a cheaper path: g[i][j] = min(g[i][j], g[i][k] + g[k][j])
for k in range(26):
    for i in range(26):
        for j in range(26):
            g[i][j] = min(g[i][j], g[i][k] + g[k][j])

Step 4: Calculate Total Transformation Cost

Traverse source and target strings simultaneously:

  • If characters at position match, no cost needed
  • If they differ, look up the minimum transformation cost from g
  • If transformation cost is infinity, return -1 (impossible)
  • Otherwise, add the cost to the running total
ans = 0
for a, b in zip(source, target):
    if a != b:
        x, y = ord(a) - ord('a'), ord(b) - ord('a')
        if g[x][y] >= inf:
            return -1
        ans += g[x][y]
return ans

The algorithm efficiently handles:

  • Multiple paths between characters (keeps minimum)
  • Indirect transformations through intermediate characters
  • Impossible transformations (returns -1)

Time Complexity: O(26³ + n + m) where n is string length and m is the number of transformations Space Complexity: O(26²) for the cost matrix

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Given:

  • source = "abc"
  • target = "aec"
  • original = ['b', 'b', 'c']
  • changed = ['e', 'c', 'e']
  • cost = [2, 5, 1]

This means we have three transformations available:

  1. 'b' → 'e' with cost 2
  2. 'b' → 'c' with cost 5
  3. 'c' → 'e' with cost 1

Step 1: Initialize Cost Matrix

We create a 26×26 matrix (we'll show only relevant characters a-e for brevity):

    a   b   c   d   e
a   0   ∞   ∞   ∞   ∞
b0   ∞   ∞   ∞
c   ∞   ∞   0   ∞   ∞
d   ∞   ∞   ∞   0e   ∞   ∞   ∞   ∞   0

Step 2: Add Direct Transformations

Process each transformation:

  • 'b' → 'e' with cost 2: g[1][4] = 2
  • 'b' → 'c' with cost 5: g[1][2] = 5
  • 'c' → 'e' with cost 1: g[2][4] = 1

Matrix becomes:

    a   b   c   d   e
a   0   ∞   ∞   ∞   ∞
b0   52
c   ∞   ∞   01
d   ∞   ∞   ∞   0e   ∞   ∞   ∞   ∞   0

Step 3: Floyd-Warshall Algorithm

Consider paths through intermediate nodes. Key discovery:

  • When k='c' (index 2), we check if 'b' → 'c' → 'e' is cheaper than 'b' → 'e'
  • Current: g[b][e] = 2
  • Via 'c': g[b][c] + g[c][e] = 5 + 1 = 6
  • Keep the minimum: g[b][e] = 2

After Floyd-Warshall, we have all shortest paths between characters.

Step 4: Calculate Total Cost

Compare source = "abc" with target = "aec":

Position 0: 'a' vs 'a' → Match, cost = 0 Position 1: 'b' vs 'e' → Need transformation

  • Look up g[b][e] = 2
  • Add to total: cost = 2

Position 2: 'c' vs 'c' → Match, cost = 0

Result: Total minimum cost = 2

Note: If we needed to transform 'b' to 'd' but had no path available (direct or indirect), g[b][d] would remain infinity and we'd return -1.

Solution Implementation

1from typing import List
2from math import inf
3
4
5class Solution:
6    def minimumCost(
7        self,
8        source: str,
9        target: str,
10        original: List[str],
11        changed: List[str],
12        cost: List[int],
13    ) -> int:
14        # Initialize adjacency matrix for 26 lowercase letters
15        # graph[i][j] represents minimum cost to transform character i to character j
16        graph = [[inf] * 26 for _ in range(26)]
17      
18        # Cost of transforming a character to itself is 0
19        for i in range(26):
20            graph[i][i] = 0
21      
22        # Build the graph with given transformations
23        # Keep only the minimum cost if multiple transformations exist
24        for orig_char, changed_char, transform_cost in zip(original, changed, cost):
25            from_index = ord(orig_char) - ord('a')
26            to_index = ord(changed_char) - ord('a')
27            graph[from_index][to_index] = min(graph[from_index][to_index], transform_cost)
28      
29        # Floyd-Warshall algorithm to find shortest paths between all pairs
30        for intermediate in range(26):
31            for start in range(26):
32                for end in range(26):
33                    # Check if path through intermediate node is cheaper
34                    graph[start][end] = min(
35                        graph[start][end], 
36                        graph[start][intermediate] + graph[intermediate][end]
37                    )
38      
39        # Calculate total cost to transform source to target
40        total_cost = 0
41        for source_char, target_char in zip(source, target):
42            # Skip if characters are already the same
43            if source_char != target_char:
44                from_index = ord(source_char) - ord('a')
45                to_index = ord(target_char) - ord('a')
46              
47                # Check if transformation is possible
48                if graph[from_index][to_index] >= inf:
49                    return -1
50              
51                total_cost += graph[from_index][to_index]
52      
53        return total_cost
54
1class Solution {
2    public long minimumCost(String source, String target, char[] original, char[] changed, int[] cost) {
3        // Define a large value to represent infinity (unreachable)
4        final int INFINITY = 1 << 29;
5      
6        // Initialize a 2D array to store minimum transformation costs between characters
7        // graph[i][j] represents the minimum cost to transform character i to character j
8        int[][] graph = new int[26][26];
9      
10        // Initialize all transformation costs to infinity, except self-transformations (cost 0)
11        for (int i = 0; i < 26; i++) {
12            Arrays.fill(graph[i], INFINITY);
13            graph[i][i] = 0; // Cost to transform a character to itself is 0
14        }
15      
16        // Build the initial graph with direct transformation costs
17        for (int i = 0; i < original.length; i++) {
18            int fromChar = original[i] - 'a';  // Convert character to index (0-25)
19            int toChar = changed[i] - 'a';     // Convert character to index (0-25)
20            int transformCost = cost[i];
21            // Keep the minimum cost if multiple transformations exist between same characters
22            graph[fromChar][toChar] = Math.min(graph[fromChar][toChar], transformCost);
23        }
24      
25        // Apply Floyd-Warshall algorithm to find shortest paths between all character pairs
26        for (int intermediate = 0; intermediate < 26; intermediate++) {
27            for (int start = 0; start < 26; start++) {
28                for (int end = 0; end < 26; end++) {
29                    // Check if path through intermediate character is shorter
30                    graph[start][end] = Math.min(graph[start][end], 
31                                                graph[start][intermediate] + graph[intermediate][end]);
32                }
33            }
34        }
35      
36        // Calculate total minimum cost to transform source string to target string
37        long totalCost = 0;
38        int stringLength = source.length();
39      
40        for (int i = 0; i < stringLength; i++) {
41            int sourceChar = source.charAt(i) - 'a';  // Convert to index (0-25)
42            int targetChar = target.charAt(i) - 'a';  // Convert to index (0-25)
43          
44            // Only need transformation if characters are different
45            if (sourceChar != targetChar) {
46                // Check if transformation is possible
47                if (graph[sourceChar][targetChar] >= INFINITY) {
48                    return -1;  // Transformation impossible
49                }
50                totalCost += graph[sourceChar][targetChar];
51            }
52        }
53      
54        return totalCost;
55    }
56}
57
1class Solution {
2public:
3    long long minimumCost(string source, string target, vector<char>& original, vector<char>& changed, vector<int>& cost) {
4        // Define a large value to represent infinity (no direct path)
5        const int INF = 1 << 29;
6      
7        // Initialize adjacency matrix for 26 letters (a-z)
8        // graph[i][j] represents the minimum cost to change from letter i to letter j
9        int graph[26][26];
10      
11        // Initialize all distances to infinity, except diagonal (same letter) which is 0
12        for (int i = 0; i < 26; ++i) {
13            fill(begin(graph[i]), end(graph[i]), INF);
14            graph[i][i] = 0;  // Cost to change a letter to itself is 0
15        }
16
17        // Build the graph from the given transformations
18        for (int i = 0; i < original.size(); ++i) {
19            int fromChar = original[i] - 'a';  // Convert char to index (0-25)
20            int toChar = changed[i] - 'a';     // Convert char to index (0-25)
21            int transformCost = cost[i];
22          
23            // Keep the minimum cost if there are multiple transformations for the same pair
24            graph[fromChar][toChar] = min(graph[fromChar][toChar], transformCost);
25        }
26
27        // Apply Floyd-Warshall algorithm to find shortest paths between all pairs
28        for (int intermediate = 0; intermediate < 26; ++intermediate) {
29            for (int start = 0; start < 26; ++start) {
30                for (int end = 0; end < 26; ++end) {
31                    // Update shortest path from start to end through intermediate node
32                    graph[start][end] = min(graph[start][end], 
33                                           graph[start][intermediate] + graph[intermediate][end]);
34                }
35            }
36        }
37
38        // Calculate the total minimum cost to transform source string to target string
39        long long totalCost = 0;
40        int stringLength = source.length();
41      
42        for (int i = 0; i < stringLength; ++i) {
43            int sourceChar = source[i] - 'a';  // Convert to index (0-25)
44            int targetChar = target[i] - 'a';  // Convert to index (0-25)
45          
46            // Only need transformation if characters are different
47            if (sourceChar != targetChar) {
48                // Check if transformation is possible
49                if (graph[sourceChar][targetChar] >= INF) {
50                    return -1;  // Impossible to transform this character
51                }
52                totalCost += graph[sourceChar][targetChar];
53            }
54        }
55      
56        return totalCost;
57    }
58};
59
1/**
2 * Calculates the minimum cost to transform source string to target string
3 * using character replacement rules with associated costs
4 * @param source - The source string to transform
5 * @param target - The target string to achieve
6 * @param original - Array of original characters that can be replaced
7 * @param changed - Array of characters that original[i] can be changed to
8 * @param cost - Array of costs for each transformation
9 * @returns The minimum total cost, or -1 if transformation is impossible
10 */
11function minimumCost(
12    source: string,
13    target: string,
14    original: string[],
15    changed: string[],
16    cost: number[],
17): number {
18    const sourceLength = source.length;
19    const transformationsCount = original.length;
20    const INFINITY = Number.POSITIVE_INFINITY;
21  
22    // Initialize cost matrix for all possible character transformations (26x26 for lowercase letters)
23    const costMatrix: number[][] = Array.from(
24        { length: 26 }, 
25        () => Array(26).fill(INFINITY)
26    );
27  
28    /**
29     * Converts a character to its index (0-25 for 'a'-'z')
30     * @param character - The character to convert
31     * @returns The index of the character
32     */
33    const getCharacterIndex = (character: string): number => {
34        return character.charCodeAt(0) - 'a'.charCodeAt(0);
35    };
36
37    // Set cost of transforming a character to itself as 0
38    for (let i = 0; i < 26; i++) {
39        costMatrix[i][i] = 0;
40    }
41  
42    // Populate the cost matrix with given transformation rules
43    // Keep minimum cost if multiple transformations exist between same characters
44    for (let i = 0; i < transformationsCount; i++) {
45        const fromIndex = getCharacterIndex(original[i]);
46        const toIndex = getCharacterIndex(changed[i]);
47        const transformCost = cost[i];
48        costMatrix[fromIndex][toIndex] = Math.min(
49            costMatrix[fromIndex][toIndex], 
50            transformCost
51        );
52    }
53
54    // Apply Floyd-Warshall algorithm to find shortest paths between all character pairs
55    for (let intermediate = 0; intermediate < 26; intermediate++) {
56        for (let start = 0; start < 26; start++) {
57            // Skip if no path from start to intermediate
58            if (costMatrix[start][intermediate] === INFINITY) {
59                continue;
60            }
61          
62            for (let end = 0; end < 26; end++) {
63                // Skip if no path from intermediate to end
64                if (costMatrix[intermediate][end] === INFINITY) {
65                    continue;
66                }
67              
68                // Update shortest path from start to end through intermediate
69                costMatrix[start][end] = Math.min(
70                    costMatrix[start][end],
71                    costMatrix[start][intermediate] + costMatrix[intermediate][end]
72                );
73            }
74        }
75    }
76
77    // Calculate total cost to transform source to target
78    let totalCost = 0;
79  
80    for (let i = 0; i < sourceLength; i++) {
81        const sourceCharIndex = getCharacterIndex(source[i]);
82        const targetCharIndex = getCharacterIndex(target[i]);
83      
84        // Skip if characters are already the same
85        if (sourceCharIndex === targetCharIndex) {
86            continue;
87        }
88      
89        // Return -1 if transformation is impossible
90        if (costMatrix[sourceCharIndex][targetCharIndex] === INFINITY) {
91            return -1;
92        }
93      
94        // Add transformation cost to total
95        totalCost += costMatrix[sourceCharIndex][targetCharIndex];
96    }
97  
98    return totalCost;
99}
100

Time and Space Complexity

Time Complexity: O(m + n + |Σ|³) where:

  • m is the length of the original array (same as changed and cost arrays)
  • n is the length of the source string (same as target string)
  • |Σ| = 26 represents the size of the alphabet (26 lowercase English letters)

The time complexity breaks down as follows:

  • Initializing the graph g with infinity values: O(|Σ|²) = O(26²)
  • Setting diagonal elements to 0: O(|Σ|) = O(26)
  • Processing all transformation costs from original to changed: O(m) iterations, each with O(1) operations
  • Floyd-Warshall algorithm to find shortest paths: O(|Σ|³) = O(26³) with three nested loops
  • Computing the minimum cost by iterating through source and target: O(n) iterations, each with O(1) lookup

Overall: O(26² + 26 + m + 26³ + n) = O(m + n + |Σ|³)

Space Complexity: O(|Σ|²) where |Σ| = 26

The space complexity is determined by:

  • The 2D graph matrix g of size 26 × 26: O(|Σ|²) = O(26²)
  • Other variables use constant space: O(1)

Overall: O(|Σ|²) = O(26²)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Handling Multiple Transformation Costs Between Same Character Pairs

Pitfall: A critical mistake is overwriting transformation costs instead of keeping the minimum when the same character transformation appears multiple times in the input.

Incorrect Implementation:

# Wrong: This overwrites previous costs
for x, y, z in zip(original, changed, cost):
    x = ord(x) - ord('a')
    y = ord(y) - ord('a')
    g[x][y] = z  # ❌ Overwrites existing value

Why It's Wrong: If the input contains:

  • original = ['a', 'a'], changed = ['b', 'b'], cost = [5, 2]
  • The second transformation would overwrite the first, keeping cost 2
  • But what if the order was reversed? We'd keep cost 5 instead of 2

Correct Implementation:

# Correct: Keep the minimum cost
for x, y, z in zip(original, changed, cost):
    x = ord(x) - ord('a')
    y = ord(y) - ord('a')
    g[x][y] = min(g[x][y], z)  # ✅ Keeps minimum

2. Incorrect Infinity Value Handling

Pitfall: Using a numeric value that's too small as "infinity" or not properly checking for unreachable paths.

Incorrect Implementation:

# Wrong: Using a small value as infinity
INF = 10**6  # ❌ Might be too small for some inputs
g = [[INF] * 26 for _ in range(26)]

# Later in the code:
if g[x][y] == INF:  # ❌ Exact equality check is fragile
    return -1

Why It's Wrong:

  • If costs sum up beyond your chosen "infinity" value, the algorithm breaks
  • After Floyd-Warshall, the value might be slightly different due to additions

Correct Implementation:

from math import inf

# Correct: Use Python's built-in infinity
g = [[inf] * 26 for _ in range(26)]

# Check with comparison, not equality
if g[x][y] >= inf:  # ✅ Handles infinity properly
    return -1

3. Forgetting to Initialize Self-Transformations to Zero

Pitfall: Not setting the diagonal of the cost matrix to 0, which represents the cost of "transforming" a character to itself.

Incorrect Implementation:

# Wrong: Forgetting to initialize diagonal
g = [[inf] * 26 for _ in range(26)]
# ❌ Missing: g[i][i] = 0

Why It's Wrong:

  • Floyd-Warshall assumes zero cost for self-loops
  • Without this, paths that go through the same character would have infinite cost
  • This breaks the algorithm's ability to find indirect paths

Correct Implementation:

g = [[inf] * 26 for _ in range(26)]
# Correct: Initialize diagonal to 0
for i in range(26):
    g[i][i] = 0  # ✅ Essential for Floyd-Warshall

4. Processing Characters That Are Already Equal

Pitfall: Attempting to look up transformation costs for characters that are already the same, potentially adding unnecessary costs or causing errors.

Incorrect Implementation:

# Wrong: Always looking up transformation cost
total_cost = 0
for a, b in zip(source, target):
    x, y = ord(a) - ord('a'), ord(b) - ord('a')
    # ❌ This adds cost even when a == b
    total_cost += g[x][y]

Why It's Wrong:

  • When a == b, we shouldn't add any cost
  • Even though g[i][i] = 0, it's inefficient and conceptually wrong

Correct Implementation:

total_cost = 0
for a, b in zip(source, target):
    if a != b:  # ✅ Only process different characters
        x, y = ord(a) - ord('a'), ord(b) - ord('a')
        if g[x][y] >= inf:
            return -1
        total_cost += g[x][y]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More