Facebook Pixel

3361. Shift Distance Between Two Strings


Problem Description

You are given two strings s and t of the same length, along with two integer arrays nextCost and previousCost. Each character in the strings s and t can be shifted either to the next letter or to the previous letter in the alphabet. The cost to shift a character s[i] to the next letter is determined by nextCost[j], where j is the position of s[i] in the alphabet, and the cost to shift to the previous letter is determined by previousCost[j]. If a character 'z' is shifted next, it wraps around to 'a', and similarly, 'a' shifting previous wraps around to 'z'. Your goal is to transform the string s into the string t with the minimum total cost of such operations. This minimum total cost is referred to as the "shift distance".

Intuition

To solve this problem, we need to determine the most cost-effective way to transform each character in s to the corresponding character in t. Since we can either shift to the next or previous letter in the alphabet, we can calculate the cost for each possible shift and choose the lesser cost.

  1. Pre-calculate Costs: First, we compute the cumulative costs of shifting through the alphabet both forwards (using nextCost) and backwards (using previousCost). This allows us to quickly determine the cost of shifting from any character s[i] to any t[i].

  2. Calculate Cost Efficiently: For each pair of characters s[i] and t[i], find:

    • The cost c1 to shift s[i] forward to reach t[i]. We need to wrap around if t[i] precedes s[i] in the alphabet.
    • The cost c2 to shift s[i] backward to reach t[i]. Similarly, wrap around if s[i] precedes t[i].
  3. Compare and Sum Costs: For each character transformation, compare c1 and c2 and add the smaller value to the total cost. This ensures that we are always choosing the cheapest way to transform s into t.

By implementing these steps, the algorithm efficiently calculates the minimum shift distance for transforming s into t.

Learn more about Prefix Sum patterns.

Solution Approach

The solution employs an efficient approach using pre-computed cumulative costs for shifting characters both forwards and backwards through the alphabet. Below is a step-by-step breakdown of the solution:

  1. Initialize Arrays for Cumulative Costs:

    • We use two arrays s1 and s2 to store the cumulative costs for shifting forward and backward, respectively. Each array has a size of 2 * m + 1 where m is the number of letters in the alphabet (26). This size accounts for wrapping around the alphabet.
  2. Calculate Forward Cumulative Costs:

    • Iterate through the alphabet twice (m << 1) and populate s1 with cumulative costs using the nextCost array. The entry s1[i + 1] represents the cost to shift from 'a' to the character at position i.
  3. Calculate Backward Cumulative Costs:

    • Similarly, populate s2 using previousCost. The entry s2[i + 1] represents the cost to move backwards from 'a' to character at position i, considering the wrap around effect.
  4. Compute the Shift Distance:

    • For each character pair (s[i], t[i]), compute:
      • x and y as the alphabet indices of s[i] and t[i] respectively.
      • c1 as the cost to move from x to y forward, calculated as s1[y + m if y < x else y] - s1[x].
      • c2 as the cost to move from x to y backward, calculated as s2[x + m if x < y else x] - s2[y].
    • Add the minimum of c1 and c2 to the total cost.
  5. Return the Total Shift Distance:

    • The final accumulated cost in ans represents the minimum total cost to transform s into t.

This method efficiently determines the shift distance by leveraging cumulative cost arrays to quickly ascertain the cost of any necessary transformations in constant time.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's consider an example where s = "abc", t = "bcd", with nextCost and previousCost as follows:

  • nextCost = [1, 1, 1, ..., 1] (i.e., a constant 1 cost to move forward)
  • previousCost = [1, 1, 1, ..., 1] (i.e., a constant 1 cost to move backward)

The goal is to transform s into t with minimum cost:

  1. Pre-calculate Costs:

    • We will use arrays s1 and s2 to hold cumulative costs for forward and backward movements.
  2. Calculate Forward Cumulative Costs (s1):

    • For s1, iterate over twice the alphabet length (52 characters):
      s1[1] = s1[0] + nextCost[0] = 1
      s1[2] = s1[1] + nextCost[1] = 2
      s1[3] = s1[2] + nextCost[2] = 3
      ...
    • s1[i] captures cumulative cost to move from 'a' to any character.
  3. Calculate Backward Cumulative Costs (s2):

    • Similarly, populate s2 for backward movements:
      s2[1] = s2[0] + previousCost[0] = 1
      s2[2] = s2[1] + previousCost[1] = 2
      s2[3] = s2[2] + previousCost[2] = 3
      ...
    • s2[i] captures cumulative cost to move back from 'a' to any character.
  4. Compute the Shift Distance:

    • For each pair (s[i], t[i]), calculate costs:
      • For 'a' to 'b':
        • x = 0, y = 1
        • Forward cost c1: s1[1] - s1[0] = 1
        • Backward cost c2: s2[0+m] - s2[1] = 1 + 26 - 1 = 26
        • Choose minimum: add c1 = 1 to total cost.
      • For 'b' to 'c':
        • x = 1, y = 2
        • Forward cost c1: s1[2] - s1[1] = 1
        • Backward cost c2: s2[1+m] - s2[2] = 26 + 1 - 2 = 25
        • Choose minimum: add c1 = 1 to total cost.
      • For 'c' to 'd':
        • x = 2, y = 3
        • Forward cost c1: s1[3] - s1[2] = 1
        • Backward cost c2: s2[2+m] - s2[3] = 26 + 2 - 3 = 25
        • Choose minimum: add c1 = 1 to total cost.
  5. Return the Total Shift Distance:

    • The total cost to transform s = "abc" to t = "bcd" is 1 + 1 + 1 = 3.

Thus, the minimum cost to transform s into t with the given costs is 3.

Solution Implementation

1from typing import List
2
3class Solution:
4    def shiftDistance(
5        self, s: str, t: str, nextCost: List[int], previousCost: List[int]
6    ) -> int:
7        # number of letters in the alphabet
8        alphabet_size = 26
9      
10        # Arrays to store cumulative costs for shifting forward and backward
11        cumulative_next_cost = [0] * ((alphabet_size << 1) + 1)
12        cumulative_previous_cost = [0] * ((alphabet_size << 1) + 1)
13      
14        # Calculate cumulative forward costs
15        for i in range(alphabet_size << 1):
16            cumulative_next_cost[i + 1] = cumulative_next_cost[i] + nextCost[i % alphabet_size]
17      
18        # Calculate cumulative backward costs
19        for i in range(alphabet_size << 1):
20            cumulative_previous_cost[i + 1] = cumulative_previous_cost[i] + previousCost[(i + 1) % alphabet_size]
21      
22        total_cost = 0
23      
24        # Calculate the total minimum cost to convert string s to string t
25        for char_s, char_t in zip(s, t):
26            index_s = ord(char_s) - ord('a')
27            index_t = ord(char_t) - ord('a')
28          
29            # Calculate the forward cost from char_s to char_t
30            if index_t < index_s:
31                forward_cost = cumulative_next_cost[index_t + alphabet_size] - cumulative_next_cost[index_s]
32            else:
33                forward_cost = cumulative_next_cost[index_t] - cumulative_next_cost[index_s]
34              
35            # Calculate the backward cost from char_s to char_t
36            if index_s < index_t:
37                backward_cost = cumulative_previous_cost[index_s + alphabet_size] - cumulative_previous_cost[index_t]
38            else:
39                backward_cost = cumulative_previous_cost[index_s] - cumulative_previous_cost[index_t]
40          
41            # Add the minimum of the forward or backward cost to the total cost
42            total_cost += min(forward_cost, backward_cost)
43      
44        return total_cost
45
1class Solution {
2    public long shiftDistance(String s, String t, int[] nextCost, int[] previousCost) {
3        int alphabetSize = 26; // Assume a fixed alphabet size (for letters 'a' to 'z')
4        long[] forwardCosts = new long[(alphabetSize << 1) + 1]; // Array to store cumulative costs for forward shifts
5        long[] backwardCosts = new long[(alphabetSize << 1) + 1]; // Array to store cumulative costs for backward shifts
6
7        // Calculate cumulative forward shift costs
8        for (int i = 0; i < (alphabetSize << 1); i++) {
9            forwardCosts[i + 1] = forwardCosts[i] + nextCost[i % alphabetSize];
10        }
11
12        // Calculate cumulative backward shift costs
13        for (int i = 0; i < (alphabetSize << 1); i++) {
14            backwardCosts[i + 1] = backwardCosts[i] + previousCost[(i + 1) % alphabetSize];
15        }
16
17        long totalCost = 0;
18
19        // Calculate the total minimum shift cost for transforming 's' into 't'
20        for (int i = 0; i < s.length(); i++) {
21            int startCharIndex = s.charAt(i) - 'a'; // Get position of current character in 's'
22            int targetCharIndex = t.charAt(i) - 'a'; // Get position of target character in 't'
23
24            // Calculate cost of shifting forward
25            long forwardShiftCost = forwardCosts[targetCharIndex + (targetCharIndex < startCharIndex ? alphabetSize : 0)] - forwardCosts[startCharIndex];
26          
27            // Calculate cost of shifting backward
28            long backwardShiftCost = backwardCosts[startCharIndex + (startCharIndex < targetCharIndex ? alphabetSize : 0)] - backwardCosts[targetCharIndex];
29          
30            // Add the smaller of the two costs to the total
31            totalCost += Math.min(forwardShiftCost, backwardShiftCost);
32        }
33      
34        return totalCost; // Return the calculated total minimum cost
35    }
36}
37
1class Solution {
2public:
3    long long shiftDistance(string s, string t, vector<int>& nextCost, vector<int>& previousCost) {
4        // The number of letters in the alphabet
5        int alphabetSize = 26;
6      
7        // Precompute the cumulative costs
8        vector<long long> nextCumulative((alphabetSize << 1) + 1, 0);
9        vector<long long> prevCumulative((alphabetSize << 1) + 1, 0);
10      
11        // Fill the cumulative cost vectors for forward and backward shifting
12        for (int i = 0; i < (alphabetSize << 1); ++i) {
13            // Accumulate cost for the nextCost wrapping around using mod operator
14            nextCumulative[i + 1] = nextCumulative[i] + nextCost[i % alphabetSize];
15            // Accumulate cost for the previousCost with an offset for wrapping
16            prevCumulative[i + 1] = prevCumulative[i] + previousCost[(i + 1) % alphabetSize];
17        }
18
19        // Initialize the total minimum cost
20        long long totalCost = 0;
21
22        // Iterate through each character in the strings
23        for (int i = 0; i < s.size(); ++i) {
24            int sourceIndex = s[i] - 'a'; // Index of current character in source string
25            int targetIndex = t[i] - 'a'; // Index of current character in target string
26          
27            // Calculate the cost using nextCumulative for forward shift
28            long long forwardCost = nextCumulative[targetIndex + (targetIndex < sourceIndex ? alphabetSize : 0)] - nextCumulative[sourceIndex];
29          
30            // Calculate the cost using prevCumulative for backward shift
31            long long backwardCost = prevCumulative[sourceIndex + (sourceIndex < targetIndex ? alphabetSize : 0)] - prevCumulative[targetIndex];
32          
33            // Add the minimum of the forward and backward shifting costs
34            totalCost += min(forwardCost, backwardCost);
35        }
36
37        return totalCost; // Return the total minimum cost of shifting
38    }
39};
40
1function shiftDistance(s: string, t: string, nextCost: number[], previousCost: number[]): number {
2    // Define the total number of characters in the alphabet
3    const alphabetSize = 26;
4  
5    // Initialize cost accumulation arrays
6    // s1 will store cumulative next costs, s2 will store cumulative previous costs
7    const s1: number[] = Array((alphabetSize << 1) + 1).fill(0);
8    const s2: number[] = Array((alphabetSize << 1) + 1).fill(0);
9
10    // Populate the cumulative cost arrays
11    for (let i = 0; i < alphabetSize << 1; i++) {
12        // Accumulate costs for nextCost and store in s1
13        s1[i + 1] = s1[i] + nextCost[i % alphabetSize];
14        // Accumulate costs for previousCost and store in s2
15        s2[i + 1] = s2[i] + previousCost[(i + 1) % alphabetSize];
16    }
17  
18    let totalCost = 0;
19    const charCodeOffset = 'a'.charCodeAt(0); // Character 'a' ASCII offset
20  
21    // Loop through each character of strings s and t
22    for (let i = 0; i < s.length; i++) {
23        const sCharIndex = s.charCodeAt(i) - charCodeOffset; // Current character index in s
24        const tCharIndex = t.charCodeAt(i) - charCodeOffset; // Current character index in t
25      
26        // Calculate forward shift cost
27        const forwardCost = s1[tCharIndex + (tCharIndex < sCharIndex ? alphabetSize : 0)] - s1[sCharIndex];
28      
29        // Calculate backward shift cost
30        const backwardCost = s2[sCharIndex + (sCharIndex < tCharIndex ? alphabetSize : 0)] - s2[tCharIndex];
31      
32        // Choose the minimum of the forward and backward shift costs
33        totalCost += Math.min(forwardCost, backwardCost);
34    }
35  
36    return totalCost; // Return the total minimum shift cost to transform s into t
37}
38

Time and Space Complexity

The time complexity of the shiftDistance function is O(n + m), where n is the length of the input strings s and t, and m is the fixed constant of 26 representing the number of letters in the English alphabet. The iteration over string characters contributes to the O(n) component, while the precomputation loops over costs contribute to O(m).

The space complexity is O(m), due to the auxiliary arrays s1 and s2 that store cumulative costs and have a size proportional to 2m + 1. Here, m is 26, which is a fixed constant, so in practical terms, this complexity simplifies to O(1).

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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


Load More