Files
codetutor/backend/data/questions/can-convert-string-in-k-moves.yaml
2025-05-25 10:16:13 +01:00

194 lines
9.3 KiB
YAML

title: Can Convert String in K Moves
slug: can-convert-string-in-k-moves
difficulty: medium
leetcode_id: 1540
leetcode_url: https://leetcode.com/problems/can-convert-string-in-k-moves/
categories:
- strings
- hash-tables
patterns:
- greedy
description: |
Given two strings `s` and `t`, your goal is to convert `s` into `t` in `k` moves or less.
During the i<sup>th</sup> (`1 <= i <= k`) move you can:
- Choose any index `j` (1-indexed) from `s`, such that `1 <= j <= s.length` and `j` has not been chosen in any previous move, and shift the character at that index `i` times.
- Do nothing.
Shifting a character means replacing it by the next letter in the alphabet (wrapping around so that `'z'` becomes `'a'`). Shifting a character by `i` means applying the shift operations `i` times.
Remember that any index `j` can be picked at most once.
Return `true` if it's possible to convert `s` into `t` in no more than `k` moves, otherwise return `false`.
constraints: |
- `1 <= s.length, t.length <= 10^5`
- `0 <= k <= 10^9`
- `s`, `t` contain only lowercase English letters
examples:
- input: 's = "input", t = "ouput", k = 9'
output: "true"
explanation: "In the 6th move, we shift 'i' 6 times to get 'o'. And in the 7th move we shift 'n' to get 'u'."
- input: 's = "abc", t = "bcd", k = 10'
output: "false"
explanation: "We need to shift each character in s one time to convert it into t. We can shift 'a' to 'b' during the 1st move. However, there is no way to shift the other characters in the remaining moves to obtain t from s."
- input: 's = "aab", t = "bbb", k = 27'
output: "true"
explanation: "In the 1st move, we shift the first 'a' 1 time to get 'b'. In the 27th move, we shift the second 'a' 27 times to get 'b'."
explanation:
intuition: |
Think of each character transformation as requiring a specific number of "shift credits". To change `'a'` to `'b'`, you need 1 shift. To change `'a'` to `'c'`, you need 2 shifts. And since the alphabet wraps around, changing `'z'` to `'a'` needs 1 shift.
The key insight is that each move number can only be used **once**. Move 1 gives you 1 shift, move 2 gives you 2 shifts, and so on up to move `k`. But here's the twist: shifting by 27 is the same as shifting by 1 (since the alphabet has 26 letters), shifting by 28 is the same as shifting by 2, and so on.
So if you need to shift two different characters by 1, you can use move 1 for one and move 27 for the other (since `27 % 26 = 1`). Similarly, move 53 also gives an effective shift of 1.
The problem becomes: for each required shift amount (1 through 25), count how many characters need that shift. Then check if we have enough moves available to cover all of them. The first character needing shift `d` uses move `d`, the second uses move `d + 26`, the third uses move `d + 52`, and so on.
approach: |
We solve this using a **Counting with Modular Arithmetic** approach:
**Step 1: Handle base cases**
- If the lengths of `s` and `t` differ, return `false` immediately — we can't add or remove characters
- If `s` equals `t`, return `true` — no moves needed
&nbsp;
**Step 2: Calculate required shifts for each position**
- For each index `i`, calculate the shift needed: `(t[i] - s[i] + 26) % 26`
- The `+ 26` handles wraparound (e.g., `'a'` to `'z'` needs 25 shifts, not -1)
- If the shift is 0, the characters already match — no move needed for this position
&nbsp;
**Step 3: Count occurrences of each shift amount**
- Use a hash map (or array of size 26) to count how many positions need each shift amount (1 to 25)
- Shift amount 0 means the characters are already equal — skip these
&nbsp;
**Step 4: Check if enough moves are available**
- For each shift amount `d` from 1 to 25:
- If `count[d]` positions need this shift, the last one will use move `d + 26 * (count[d] - 1)`
- If this exceeds `k`, return `false`
- If all shifts can be covered, return `true`
&nbsp;
The formula `d + 26 * (count - 1)` works because: the 1<sup>st</sup> character uses move `d`, the 2<sup>nd</sup> uses move `d + 26`, the 3<sup>rd</sup> uses move `d + 52`, and so on. The n<sup>th</sup> character uses move `d + 26 * (n - 1)`.
common_pitfalls:
- title: Ignoring Modular Equivalence
description: |
A common mistake is thinking each shift amount (1-25) can only be used once total. But moves repeat every 26 values!
For example, if `k = 100` and you need shift amount 1 for three characters:
- Character 1 uses move 1
- Character 2 uses move 27 (since `27 % 26 = 1`)
- Character 3 uses move 53
- Character 4 would need move 79, and so on
You can use any shift amount multiple times, as long as the move number doesn't exceed `k`.
wrong_approach: "Assuming each shift amount 1-25 can only be used once"
correct_approach: "Use moves d, d+26, d+52, ... for the same shift amount d"
- title: Off-by-One in Counting
description: |
When calculating the maximum move needed for `count` characters requiring shift `d`, the formula is `d + 26 * (count - 1)`, not `d + 26 * count`.
The first character uses move `d` (that's `d + 26 * 0`), not `d + 26`. With `count = 1`, you only need move `d`. With `count = 2`, you need moves `d` and `d + 26`.
wrong_approach: "Using d + 26 * count as the maximum move"
correct_approach: "Using d + 26 * (count - 1) as the maximum move"
- title: Forgetting Length Check
description: |
If `s` and `t` have different lengths, conversion is impossible. Shifting only changes characters — it cannot add or remove them. Always check `len(s) == len(t)` first.
key_takeaways:
- "**Modular arithmetic insight**: When values wrap around (like alphabet letters), think in terms of equivalence classes — shift 1, 27, 53, etc. are all equivalent"
- "**Counting for greedy allocation**: When multiple items need the same resource, count them and calculate the worst-case requirement"
- "**Constraint analysis**: With `k` up to 10^9, you can't iterate through all moves — the counting approach runs in O(n) time"
- "**Related problems**: This pattern of modular counting appears in scheduling problems and cyclic resource allocation"
time_complexity: "O(n). We iterate through both strings once to calculate shifts and counts, where `n` is the length of the strings."
space_complexity: "O(1). We use a fixed-size array of 26 elements to count shift frequencies, regardless of input size."
solutions:
- approach_name: Counting with Modular Arithmetic
is_optimal: true
code: |
def can_convert_string(s: str, t: str, k: int) -> bool:
# Strings must be same length — we can't add or remove characters
if len(s) != len(t):
return False
# Count how many characters need each shift amount (1-25)
shift_count = [0] * 26
for i in range(len(s)):
# Calculate shift needed (with wraparound)
shift = (ord(t[i]) - ord(s[i]) + 26) % 26
# Shift of 0 means characters already match — skip
if shift > 0:
shift_count[shift] += 1
# Check if we have enough moves for each shift amount
for shift in range(1, 26):
if shift_count[shift] == 0:
continue
# The nth character needing this shift uses move: shift + 26 * (n-1)
# So the last (count-th) character needs move: shift + 26 * (count-1)
max_move_needed = shift + 26 * (shift_count[shift] - 1)
if max_move_needed > k:
return False
return True
explanation: |
**Time Complexity:** O(n) — We iterate through the strings once to count shifts, then check 26 possible shift amounts.
**Space Complexity:** O(1) — We use a fixed array of 26 integers regardless of input size.
The key insight is that move `m` provides an effective shift of `m % 26`. So for characters needing shift `d`, we can use moves `d`, `d+26`, `d+52`, etc. We count how many characters need each shift amount, then verify the maximum required move doesn't exceed `k`.
- approach_name: Hash Map Variant
is_optimal: true
code: |
from collections import defaultdict
def can_convert_string(s: str, t: str, k: int) -> bool:
if len(s) != len(t):
return False
# Track how many times each shift amount is needed
shift_count = defaultdict(int)
for sc, tc in zip(s, t):
shift = (ord(tc) - ord(sc)) % 26
if shift != 0:
shift_count[shift] += 1
# For each shift amount, check if the last required move exceeds k
for shift, count in shift_count.items():
# Moves used: shift, shift+26, shift+52, ..., shift+26*(count-1)
if shift + 26 * (count - 1) > k:
return False
return True
explanation: |
**Time Complexity:** O(n) — Single pass through the strings.
**Space Complexity:** O(1) — At most 25 unique shift amounts (1-25).
This variant uses a dictionary instead of a fixed array. The logic is identical, but some find it more readable. The dictionary only stores non-zero counts, which is slightly more memory-efficient when few shift amounts are needed.