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 changedchanged[i]
: what the character can be changed tocost[i]
: the cost of changingoriginal[i]
tochanged[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.
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:
- We have a small, dense graph (only 26 nodes)
- We need the shortest paths between all pairs of nodes
- 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 characterj
- 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 EvaluatorExample 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:
- 'b' → 'e' with cost 2
- 'b' → 'c' with cost 5
- '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 ∞ ∞ ∞ ∞ b ∞ 0 ∞ ∞ ∞ c ∞ ∞ 0 ∞ ∞ d ∞ ∞ ∞ 0 ∞ e ∞ ∞ ∞ ∞ 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 ∞ ∞ ∞ ∞ b ∞ 0 5 ∞ 2 c ∞ ∞ 0 ∞ 1 d ∞ ∞ ∞ 0 ∞ e ∞ ∞ ∞ ∞ 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 theoriginal
array (same aschanged
andcost
arrays)n
is the length of thesource
string (same astarget
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
tochanged
:O(m)
iterations, each withO(1)
operations - Floyd-Warshall algorithm to find shortest paths:
O(|Σ|³) = O(26³)
with three nested loops - Computing the minimum cost by iterating through
source
andtarget
:O(n)
iterations, each withO(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 size26 × 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]
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
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Shortest Path Between A and B Prereq BFS on Graph problems graph_bfs Given an unweighted connected graph return the length of the shortest path between two nodes A and B in terms of the number of edges Assume there always exists a path between nodes A and B Input graph
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!