4 minute read

Problem Statement

2976

Intuition

To solve this problem, my initial thought is to use Dijkstra’s algorithm to find the shortest path between each pair of characters from ‘a’ to ‘z’. This will allow me to determine the minimum cost to convert any character in the source string to the corresponding character in the target string. By constructing a graph where the nodes represent characters and the edges represent the conversion costs, I can efficiently compute the minimum conversion cost using a shortest path algorithm.

Approach

First, I’ll create a graph where each node represents a character from ‘a’ to ‘z’, and the edges represent the cost to convert one character to another. I’ll then use Dijkstra’s algorithm to compute the shortest path between each pair of characters. This will result in a matrix where shortest_path_matrix[i][j] contains the minimum cost to convert character i to character j.

Once I have this matrix, I can iterate through each character in the source string and find the corresponding character in the target string. If the characters are different, I’ll use the shortest path matrix to determine the conversion cost and accumulate the total cost. If there’s no possible conversion (indicated by an infinite cost), I’ll return -1.

Complexity

  • Time complexity: The time complexity of this approach is (O(N^2 \log N + M \log M)), where (N) is the number of characters (26) and (M) is the number of conversion rules. The (N^2 \log N) term comes from running Dijkstra’s algorithm (N) times on a graph with (N) nodes. The (M \log M) term accounts for inserting the conversion rules into the priority queue in Dijkstra’s algorithm.

  • Space complexity: The space complexity is (O(N^2)), which is used for storing the shortest path matrix.

Code

import heapq
from typing import List

class Solution:
    def dijkstra(self, graph, src, shortest_path_matrix, n):
        queue = [(0, src)]
        shortest_path_matrix[:] = [float('inf')] * n
        shortest_path_matrix[src] = 0
        while queue:
            cost, node = heapq.heappop(queue)
            if cost > shortest_path_matrix[node]:
                continue
            for next_node, next_cost in graph[node]:
                if shortest_path_matrix[next_node] > cost + next_cost:
                    shortest_path_matrix[next_node] = cost + next_cost
                    heapq.heappush(queue, (shortest_path_matrix[next_node], next_node))

    def minimumCost(self, source: str, target: str, original: List[str], changed: List[str], cost: List[int]) -> int:
        if len(source) != len(target):
            return -1
        N = 26
        graph = [[] for _ in range(N)]

        shortest_path_matrix = [[float('inf')] * N for _ in range(N)]

        for i in range(N):
            shortest_path_matrix[i][i] = 0

        for i in range(len(cost)):
            src_idx = ord(original[i]) - ord('a')
            dst_idx = ord(changed[i]) - ord('a')
            c = cost[i]
            graph[src_idx].append((dst_idx, c))

        for i in range(N):
            self.dijkstra(graph, i, shortest_path_matrix[i], N)

        total_cost = 0
        for i in range(min(len(source), len(target))):
            if source[i] != target[i]:
                row = ord(source[i]) - ord('a')
                col = ord(target[i]) - ord('a')
                if shortest_path_matrix[row][col] == float('inf'):
                    return -1
                total_cost += shortest_path_matrix[row][col]

        return total_cost

Editorial

Approach 1: Dijkstra’s Algorithm

class Solution:
    def minimumCost(
        self,
        source: str,
        target: str,
        original: List[str],
        changed: List[str],
        cost: List[int],
    ) -> int:
        # Create a graph representation of character conversions
        adjacency_list = [[] for _ in range(26)]

        # Populate the adjacency list with character conversions
        conversion_count = len(original)
        for i in range(conversion_count):
            adjacency_list[ord(original[i]) - ord("a")].append(
                (ord(changed[i]) - ord("a"), cost[i])
            )

        # Calculate shortest paths for all possible character conversions
        min_conversion_costs = [
            self._dijkstra(i, adjacency_list) for i in range(26)
        ]

        # Calculate the total cost of converting source to target
        total_cost = 0
        for s, t in zip(source, target):
            if s != t:
                char_conversion_cost = min_conversion_costs[ord(s) - ord("a")][
                    ord(t) - ord("a")
                ]
                if char_conversion_cost == float("inf"):
                    return -1  # Conversion not possible
                total_cost += char_conversion_cost

        return total_cost

    def _dijkstra(
        self, start_char: int, adjacency_list: List[List[tuple]]
    ) -> List[int]:
        # Priority queue to store characters with their conversion cost, sorted by cost
        priority_queue = [(0, start_char)]

        # List to store the minimum conversion cost to each character
        min_costs = [float("inf")] * 26

        while priority_queue:
            current_cost, current_char = heapq.heappop(priority_queue)

            if min_costs[current_char] != float("inf"):
                continue

            min_costs[current_char] = current_cost

            # Explore all possible conversions from the current character
            for target_char, conversion_cost in adjacency_list[current_char]:
                new_total_cost = current_cost + conversion_cost

                # If we found a cheaper conversion, update its cost
                if min_costs[target_char] == float("inf"):
                    heapq.heappush(
                        priority_queue, (new_total_cost, target_char)
                    )

        # Return the list of minimum conversion costs from the starting character to all others
        return min_costs

Approach 2: Floyd-Warshall Algorithm

class Solution:
    def minimumCost(
        self,
        source: str,
        target: str,
        original: List[str],
        changed: List[str],
        cost: List[int],
    ) -> int:
        # Initialize result to store the total minimum cost
        total_cost = 0

        # Initialize a 2D list to store the minimum transformation cost
        # between any two characters
        min_cost = [[float("inf")] * 26 for _ in range(26)]

        # Fill the initial transformation costs from the given original,
        # changed, and cost arrays
        for orig, chg, cst in zip(original, changed, cost):
            start_char = ord(orig) - ord("a")
            end_char = ord(chg) - ord("a")
            min_cost[start_char][end_char] = min(
                min_cost[start_char][end_char], cst
            )

        # Use Floyd-Warshall algorithm to find the shortest path between any
        # two characters
        for k in range(26):
            for i in range(26):
                for j in range(26):
                    min_cost[i][j] = min(
                        min_cost[i][j], min_cost[i][k] + min_cost[k][j]
                    )

        # Calculate the total minimum cost to transform the source string to
        # the target string
        for src, tgt in zip(source, target):
            if src == tgt:
                continue
            source_char = ord(src) - ord("a")
            target_char = ord(tgt) - ord("a")

            # If the transformation is not possible, return -1
            if min_cost[source_char][target_char] == float("inf"):
                return -1
            total_cost += min_cost[source_char][target_char]

        return total_cost