Can Convert String in K Moves
MedPrompt
Given two strings s and t, your goal is to convert s into t in k moves or less.
During the ith (1 <= i <= k) move you can:
- Choose any index
j(1-indexed) froms, such that1 <= j <= s.lengthandjhas not been chosen in any previous move, and shift the character at that indexitimes. - 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.
Example 1:
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'.Example 2:
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.Example 3:
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'.
Constraints:
1 <= s.length, t.length <= 10^50 <= k <= 10^9s,tcontain only lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves calculating the required circular shift for each character pair (s[i], t[i]). We then group the indices based on their required shift value. For each group that requires a shift d, we determine the sequence of moves needed. Since each index must be used with a unique move number, if a shift d is required for p different indices, we must use p distinct moves that are all congruent to d modulo 26. The smallest such moves are d, d+26, d+52, ..., d+(p-1)*26. We must check if the largest of these required moves is within the given limit k. If this condition holds for all required shifts, the conversion is possible.
Algorithm
- If
s.length() != t.length(), returnfalse. - Initialize a
HashMap<Integer, List<Integer>> shiftMap. - Iterate
ifrom 0 tos.length() - 1:- Calculate
shift = (t.charAt(i) - s.charAt(i) + 26) % 26. - If
shift > 0, add the indexito the list associated with the keyshiftinshiftMap.
- Calculate
- Iterate through each entry (
d,indices) inshiftMap:- Let
p = indices.size(). - Calculate the largest move needed for this shift:
max_move_needed = d + (long)(p - 1) * 26. - If
max_move_needed > k, returnfalse.
- Let
- If the loop completes, return
true.
Walkthrough
First, we handle the base case: if the lengths of s and t are not equal, conversion is impossible, so we return false.
We use a HashMap<Integer, List<Integer>> where keys are the required shift values (1-25) and values are lists of indices that need that specific shift.
We iterate from i = 0 to s.length() - 1. For each index i, we calculate the necessary shift d to transform s[i] to t[i]. The formula for the shift is d = (t.charAt(i) - s.charAt(i) + 26) % 26.
- If
dis 0,s[i]already equalst[i], so no move is needed for this index. - If
dis greater than 0, we add the indexito the list associated with the shiftdin ourHashMap.
After populating the map, we iterate through each entry. For each shift d and its corresponding list of indices of size p:
- We need
punique moves. The smallest available moves that result in a shift ofdared, d + 26, d + 52, .... - To accommodate all
pindices, the largest move we'll need isd + (p - 1) * 26. - We check if this largest required move exceeds
k. If it does, we don't have enough moves, so we returnfalse.
If we successfully check all required shifts without exceeding k, it means the conversion is possible, and we return true.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; class Solution { public boolean canConvertString(String s, String t, int k) { if (s.length() != t.length()) { return false; } Map<Integer, List<Integer>> shiftMap = new HashMap<>(); for (int i = 0; i < s.length(); i++) { int shift = (t.charAt(i) - s.charAt(i) + 26) % 26; if (shift > 0) { shiftMap.computeIfAbsent(shift, key -> new ArrayList<>()).add(i); } } for (Map.Entry<Integer, List<Integer>> entry : shiftMap.entrySet()) { int shift = entry.getKey(); int count = entry.getValue().size(); long maxMoveNeeded = (long)shift + (long)(count - 1) * 26; if (maxMoveNeeded > k) { return false; } } return true; }}Complexity
Time
O(N), where N is the length of the strings. The first loop iterates N times to populate the map. The second loop iterates at most 25 times (for each possible shift value from 1 to 25). Thus, the complexity is dominated by the first loop.
Space
O(N), where N is the length of the strings. In the worst-case scenario, the `HashMap` might need to store all N indices if they all require a shift (e.g., `s="aaa"`, `t="bbb"`).
Trade-offs
Pros
Conceptually straightforward: group by requirement, then check feasibility for each group.
Correctly solves the problem.
Cons
Uses O(N) extra space to store the indices, which is unnecessary as only the count of indices per shift is required.
Slightly more complex implementation due to managing a
HashMapof lists compared to a simple frequency array.
This approach optimizes the previous one by realizing that we don't need to store the actual indices that require a certain shift. All we need is the count of indices for each required shift. By counting the occurrences of each necessary shift d, we can directly calculate the largest move number required for that shift without storing all the indices. This reduces the space complexity from linear to constant.
Algorithm
- If
s.length() != t.length(), returnfalse. - Initialize an integer array
countsof size 26 to all zeros. - Iterate
ifrom 0 tos.length() - 1:- Calculate
shift = (t.charAt(i) - s.charAt(i) + 26) % 26. - Increment
counts[shift].
- Calculate
- Iterate
dfrom 1 to 25:- If
counts[d] > 0:- Let
p = counts[d]. - Calculate
max_move_needed = d + (long)(p - 1) * 26. - If
max_move_needed > k, returnfalse.
- Let
- If
- If the loop completes, return
true.
Walkthrough
The core logic remains the same: for a shift d needed p times, the largest move required will be d + (p-1)*26. We must verify this is at most k.
Instead of a HashMap storing lists of indices, we use a simple integer array, say counts, of size 26. counts[d] will store the number of times a shift of d is required.
- We start by checking if
sandthave the same length. If not, we returnfalse. - We iterate through the strings from
i = 0tos.length() - 1.- For each index
i, we compute the required shiftd = (t.charAt(i) - s.charAt(i) + 26) % 26. - We then increment
counts[d].
- For each index
- After counting all required shifts, we iterate through our
countsarray fromd = 1to 25 (as a shift of 0 requires no action).- For each
d, ifcounts[d]is greater than 0, it means we need to perform a shift ofdforcounts[d]characters. - The largest move for this shift
dwill bed + (counts[d] - 1) * 26. - We check if this value is greater than
k. If it is, we immediately know the conversion is impossible and returnfalse.
- For each
- If we iterate through all possible shifts (1 to 25) and the condition is never violated, it means we can find a valid move for every required character change within the
klimit. We returntrue.
class Solution { public boolean canConvertString(String s, String t, int k) { if (s.length() != t.length()) { return false; } int[] counts = new int[26]; for (int i = 0; i < s.length(); i++) { int shift = (t.charAt(i) - s.charAt(i) + 26) % 26; counts[shift]++; } for (int d = 1; d < 26; d++) { if (counts[d] > 0) { long maxMoveNeeded = (long)d + (long)(counts[d] - 1) * 26; if (maxMoveNeeded > k) { return false; } } } return true; }}Complexity
Time
O(N), where N is the length of the strings. We iterate through the strings once (O(N)) and then iterate through the `counts` array which is of fixed size 26 (O(1)). The total time complexity is O(N).
Space
O(1), as we only use an integer array of fixed size 26, which is constant space and does not depend on the input string length.
Trade-offs
Pros
Optimal time complexity of O(N).
Optimal space complexity of O(1).
Simple and clean implementation using a basic array.
Cons
There are no significant cons to this approach as it is optimal for the given constraints.
Solutions
Solution
class Solution {public boolean canConvertString(String s, String t, int k) { if (s.length() != t.length()) { return false; } int[] cnt = new int[26]; for (int i = 0; i < s.length(); ++i) { int x = (t.charAt(i) - s.charAt(i) + 26) % 26; ++cnt[x]; } for (int i = 1; i < 26; ++i) { if (i + 26 * (cnt[i] - 1) > k) { return false; } } return true; }}class Solution {public: bool canConvertString(string s, string t, int k) { if (s.size() != t.size()) { return false; } int cnt[26]{}; for (int i = 0; i < s.size(); ++i) { int x = (t[i] - s[i] + 26) % 26; ++cnt[x]; } for (int i = 1; i < 26; ++i) { if (i + 26 * (cnt[i] - 1) > k) { return false; } } return true; }};class Solution: def canConvertString(self, s: str, t: str, k: int) -> bool: if len(s) != len(t): return False cnt = [0] * 26 for a, b in zip(s, t): x = (ord(b) - ord(a) + 26) % 26 cnt[x] += 1 for i in range(1, 26): if i + 26 * (cnt[i] - 1) > k: return False return TrueVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.