1540. Can Convert String in K Moves
Problem Description
You are given two strings s
and t
, and an integer k
. Your task is to determine if you can transform string s
into string t
within k
moves.
In each move i
(where 1 ≤ i ≤ k
), you can either:
- Select an index
j
in strings
(using 1-based indexing) that hasn't been selected in any previous move, and shift the character at that position byi
times - Do nothing (skip the move)
A shift operation replaces a character with the next letter in the alphabet. For example, 'a' becomes 'b', 'b' becomes 'c', and 'z' wraps around to become 'a'. Shifting by i
means performing the shift operation i
times consecutively.
Key constraints:
- Each index in string
s
can be selected at most once throughout all moves - The shift amount for a character selected in move
i
is exactlyi
(not adjustable) - You have at most
k
moves available
The function should return true
if it's possible to convert s
into t
within k
moves, and false
otherwise.
For example, if you need to shift a character by 3 positions, you must do it in move 3. If you need to shift another character also by 3 positions, you would need to wait until move 29 (3 + 26), since move 3 is already used and the alphabet cycles every 26 letters.
Intuition
The key insight is that each character position needs a specific shift amount to transform from s
to t
. For each position, we can calculate exactly how many shifts are needed: (t[i] - s[i] + 26) % 26
. Characters that need 0 shifts don't require any moves.
Since we can only use each move number once, if multiple characters need the same shift amount, we face a constraint. For example, if two characters both need 3 shifts, the first can use move 3, but the second must wait until move 29 (3 + 26), the third would need move 55 (3 + 26*2), and so on.
This pattern emerges because the alphabet has 26 letters. If a shift amount x
is already used, the next available move that gives the same shift amount is x + 26
(since shifting by x + 26
is equivalent to shifting by x
when wrapping around the alphabet).
Therefore, for each required shift amount from 1 to 25, we count how many positions need that shift. If we have cnt[i]
positions needing shift amount i
, they would use moves: i
, i + 26
, i + 52
, ..., i + 26*(cnt[i]-1)
. The last move needed would be i + 26*(cnt[i]-1)
.
The solution becomes clear: calculate the shift needed for each position, count the frequency of each shift amount, and verify that the maximum move number required for any shift amount doesn't exceed k
. If all required moves fit within k
, the transformation is possible.
Solution Approach
The implementation follows a straightforward approach based on the intuition:
First, we check if the strings have equal length. If len(s) != len(t)
, transformation is impossible, so we return False
.
Next, we create a frequency array cnt
of size 26 to count how many characters need each shift amount (0 through 25). We iterate through both strings simultaneously using zip(s, t)
:
- For each pair of characters
(a, b)
, we calculate the required shift:x = (ord(b) - ord(a) + 26) % 26
- The
+ 26
ensures we handle negative differences correctly (whenb < a
alphabetically) - We increment
cnt[x]
to track how many positions need this shift amount
Then, we verify if all required moves fit within k
. For each shift amount i
from 1 to 25:
- If
cnt[i]
positions need shift amounti
, they would use moves:i
,i + 26
,i + 52
, ..., up toi + 26*(cnt[i]-1)
- The maximum move number needed for shift amount
i
is:i + 26*(cnt[i]-1)
- If this exceeds
k
, the transformation is impossible
We skip checking shift amount 0 because positions that don't need shifting (where s[j] == t[j]
) don't consume any moves.
The algorithm runs in O(n)
time where n
is the length of the strings, with O(1)
extra space for the fixed-size frequency array. This efficient solution leverages the cyclic nature of the alphabet and the constraint that each move number can only be used once.
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 concrete example to illustrate the solution approach.
Example: s = "abc"
, t = "bcd"
, k = 10
Step 1: Check string lengths
- Both strings have length 3, so we can proceed.
Step 2: Calculate required shifts for each position
- Position 0: 'a' → 'b' requires (ord('b') - ord('a') + 26) % 26 = (98 - 97 + 26) % 26 = 1 shift
- Position 1: 'b' → 'c' requires (ord('c') - ord('b') + 26) % 26 = (99 - 98 + 26) % 26 = 1 shift
- Position 2: 'c' → 'd' requires (ord('d') - ord('c') + 26) % 26 = (100 - 99 + 26) % 26 = 1 shift
Step 3: Build frequency array
cnt[1] = 3
(three positions need 1 shift)cnt[i] = 0
for all other i from 0 to 25
Step 4: Determine which moves are needed Since we have 3 positions that all need shift amount 1:
- First position uses move 1 (shift by 1)
- Second position uses move 27 (1 + 26×1 = 27, also shifts by 1 after wrapping)
- Third position uses move 53 (1 + 26×2 = 53, also shifts by 1 after wrapping)
The maximum move needed is 53.
Step 5: Check if all moves fit within k
- Maximum move needed: 53
- Available moves: k = 10
- Since 53 > 10, we cannot complete the transformation
Result: Return false
Counter-example with larger k: If k = 60
instead:
- Same calculations show we need moves 1, 27, and 53
- Since 53 ≤ 60, all required moves fit within k
- Return
true
This example demonstrates why multiple characters needing the same shift amount require moves separated by intervals of 26 - the alphabet size - making it crucial to check if the highest required move number fits within our limit k.
Solution Implementation
1class Solution:
2 def canConvertString(self, s: str, t: str, k: int) -> bool:
3 # If strings have different lengths, conversion is impossible
4 if len(s) != len(t):
5 return False
6
7 # Array to count the frequency of each shift distance (0-25)
8 shift_count = [0] * 26
9
10 # Calculate the shift distance needed for each character pair
11 for char_s, char_t in zip(s, t):
12 # Calculate circular shift distance from char_s to char_t
13 # Adding 26 ensures positive result before modulo
14 shift_distance = (ord(char_t) - ord(char_s) + 26) % 26
15 shift_count[shift_distance] += 1
16
17 # Check if all required shifts can be performed within k moves
18 for shift in range(1, 26):
19 # For each shift distance, if we need it multiple times,
20 # we must use moves: shift, shift+26, shift+52, etc.
21 # The last move for this shift would be: shift + 26*(count-1)
22 max_move_for_shift = shift + 26 * (shift_count[shift] - 1)
23 if max_move_for_shift > k:
24 return False
25
26 return True
27
1class Solution {
2 public boolean canConvertString(String s, String t, int k) {
3 // Check if strings have different lengths - conversion is impossible
4 if (s.length() != t.length()) {
5 return false;
6 }
7
8 // Array to count frequency of each shift distance (0-25)
9 int[] shiftCount = new int[26];
10
11 // Calculate the shift distance needed for each character position
12 for (int i = 0; i < s.length(); i++) {
13 // Calculate circular shift distance from s[i] to t[i]
14 // Adding 26 ensures positive result before modulo operation
15 int shiftDistance = (t.charAt(i) - s.charAt(i) + 26) % 26;
16 // Increment count for this shift distance
17 shiftCount[shiftDistance]++;
18 }
19
20 // Check if all required shifts can be performed within k moves
21 for (int shift = 1; shift < 26; shift++) {
22 // For each shift distance, calculate the maximum move number needed
23 // First occurrence uses move 'shift', second uses 'shift + 26',
24 // third uses 'shift + 52', and so on
25 // Formula: shift + 26 * (occurrences - 1)
26 if (shift + 26 * (shiftCount[shift] - 1) > k) {
27 return false;
28 }
29 }
30
31 // All shifts can be performed within k moves
32 return true;
33 }
34}
35
1class Solution {
2public:
3 bool canConvertString(string s, string t, int k) {
4 // Cannot convert if strings have different lengths
5 if (s.size() != t.size()) {
6 return false;
7 }
8
9 // Array to count frequency of each shift amount (0-25)
10 int shiftCount[26]{};
11
12 // Calculate the shift needed for each character position
13 for (int i = 0; i < s.size(); ++i) {
14 // Calculate forward cyclic shift from s[i] to t[i]
15 // Adding 26 before modulo ensures positive result
16 int shiftAmount = (t[i] - s[i] + 26) % 26;
17 ++shiftCount[shiftAmount];
18 }
19
20 // Check if all required shifts can be performed within k moves
21 for (int shift = 1; shift < 26; ++shift) {
22 // For each shift value, if we need it multiple times,
23 // we must use moves: shift, shift+26, shift+52, etc.
24 // The last move for this shift would be: shift + 26 * (count - 1)
25 int lastMoveForThisShift = shift + 26 * (shiftCount[shift] - 1);
26
27 if (lastMoveForThisShift > k) {
28 return false;
29 }
30 }
31
32 return true;
33 }
34};
35
1function canConvertString(s: string, t: string, k: number): boolean {
2 // Cannot convert if strings have different lengths
3 if (s.length !== t.length) {
4 return false;
5 }
6
7 // Array to count frequency of each shift amount (0-25)
8 const shiftCount: number[] = new Array(26).fill(0);
9
10 // Calculate the shift needed for each character position
11 for (let i = 0; i < s.length; i++) {
12 // Calculate forward cyclic shift from s[i] to t[i]
13 // Adding 26 before modulo ensures positive result
14 const shiftAmount = (t.charCodeAt(i) - s.charCodeAt(i) + 26) % 26;
15 shiftCount[shiftAmount]++;
16 }
17
18 // Check if all required shifts can be performed within k moves
19 for (let shift = 1; shift < 26; shift++) {
20 // For each shift value, if we need it multiple times,
21 // we must use moves: shift, shift+26, shift+52, etc.
22 // The last move for this shift would be: shift + 26 * (count - 1)
23 const lastMoveForThisShift = shift + 26 * (shiftCount[shift] - 1);
24
25 if (lastMoveForThisShift > k) {
26 return false;
27 }
28 }
29
30 return true;
31}
32
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of strings s
and t
.
- The initial length comparison takes
O(1)
time - The first loop iterates through both strings simultaneously using
zip(s, t)
, which takesO(n)
time - For each character pair, we calculate the shift value and update the count array, both operations take
O(1)
time - The second loop iterates through a fixed range of 26 values (1 to 25), which takes
O(26) = O(1)
time - Therefore, the overall time complexity is
O(n) + O(1) = O(n)
Space Complexity: O(1)
- The
cnt
array has a fixed size of 26 elements regardless of input size, usingO(26) = O(1)
space - The variables
a
,b
,x
, andi
use constant space - No additional data structures that scale with input size are used
- Therefore, the space complexity is
O(1)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Negative Shift Distances
Pitfall: When calculating the shift distance from char_s
to char_t
, if char_t
comes before char_s
alphabetically (e.g., transforming 'd' to 'a'), a naive calculation ord(char_t) - ord(char_s)
would give a negative value. Taking modulo of a negative number in Python works correctly, but in some languages or implementations, developers might get unexpected results.
Example: Converting 'd' to 'a' requires shifting by 23 positions (d→e→...→z→a), not -3.
Solution: Always add 26 before taking modulo to ensure a positive result:
shift_distance = (ord(char_t) - ord(char_s) + 26) % 26
2. Off-by-One Error in Calculating Maximum Move Number
Pitfall: When multiple characters need the same shift amount, it's easy to miscalculate the maximum move number needed. For instance, if 3 characters need a shift of 5, they would use moves 5, 31 (5+26), and 57 (5+52). The formula is shift + 26 * (count - 1)
, not shift + 26 * count
.
Solution: Remember that the first occurrence uses move number shift
, and each subsequent occurrence adds 26:
max_move_for_shift = shift + 26 * (shift_count[shift] - 1)
3. Including Zero Shifts in the Validation Loop
Pitfall: Including shift amount 0 in the validation loop (characters that don't need changing) might seem logical but is unnecessary and could lead to confusion. These positions don't consume any moves.
Solution: Start the validation loop from 1, not 0:
for shift in range(1, 26): # Start from 1, not 0
# validation logic
4. Not Checking String Length Equality First
Pitfall: Diving straight into shift calculations without first verifying that both strings have the same length can lead to index errors or incorrect results.
Solution: Always check length equality as the first validation:
if len(s) != len(t):
return False
Which of the following is a min heap?
Recommended Readings
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 assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!