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.
-
Pre-calculate Costs: First, we compute the cumulative costs of shifting through the alphabet both forwards (using
nextCost
) and backwards (usingpreviousCost
). This allows us to quickly determine the cost of shifting from any characters[i]
to anyt[i]
. -
Calculate Cost Efficiently: For each pair of characters
s[i]
andt[i]
, find:- The cost
c1
to shifts[i]
forward to reacht[i]
. We need to wrap around ift[i]
precedess[i]
in the alphabet. - The cost
c2
to shifts[i]
backward to reacht[i]
. Similarly, wrap around ifs[i]
precedest[i]
.
- The cost
-
Compare and Sum Costs: For each character transformation, compare
c1
andc2
and add the smaller value to the total cost. This ensures that we are always choosing the cheapest way to transforms
intot
.
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:
-
Initialize Arrays for Cumulative Costs:
- We use two arrays
s1
ands2
to store the cumulative costs for shifting forward and backward, respectively. Each array has a size of2 * m + 1
wherem
is the number of letters in the alphabet (26). This size accounts for wrapping around the alphabet.
- We use two arrays
-
Calculate Forward Cumulative Costs:
- Iterate through the alphabet twice (
m << 1
) and populates1
with cumulative costs using thenextCost
array. The entrys1[i + 1]
represents the cost to shift from 'a' to the character at positioni
.
- Iterate through the alphabet twice (
-
Calculate Backward Cumulative Costs:
- Similarly, populate
s2
usingpreviousCost
. The entrys2[i + 1]
represents the cost to move backwards from 'a' to character at positioni
, considering the wrap around effect.
- Similarly, populate
-
Compute the Shift Distance:
- For each character pair
(s[i], t[i])
, compute:x
andy
as the alphabet indices ofs[i]
andt[i]
respectively.c1
as the cost to move fromx
toy
forward, calculated ass1[y + m if y < x else y] - s1[x]
.c2
as the cost to move fromx
toy
backward, calculated ass2[x + m if x < y else x] - s2[y]
.
- Add the minimum of
c1
andc2
to the total cost.
- For each character pair
-
Return the Total Shift Distance:
- The final accumulated cost in
ans
represents the minimum total cost to transforms
intot
.
- The final accumulated cost in
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 EvaluatorExample 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:
-
Pre-calculate Costs:
- We will use arrays
s1
ands2
to hold cumulative costs for forward and backward movements.
- We will use arrays
-
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.
- For
-
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.
- Similarly, populate
-
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.
- For 'a' to 'b':
- For each pair
-
Return the Total Shift Distance:
- The total cost to transform
s = "abc"
tot = "bcd"
is1 + 1 + 1 = 3
.
- The total cost to transform
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.
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!