Facebook Pixel

1540. Can Convert String in K Moves

MediumHash TableString
Leetcode Link

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 string s (using 1-based indexing) that hasn't been selected in any previous move, and shift the character at that position by i 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 exactly i (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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 (when b < 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 amount i, they would use moves: i, i + 26, i + 52, ..., up to i + 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 Evaluator

Example 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 takes O(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, using O(26) = O(1) space
  • The variables a, b, x, and i 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

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

Load More