1347. Minimum Number of Steps to Make Two Strings Anagram
Problem Description
You are given two strings s
and t
that have the same length. Your goal is to transform string t
into an anagram of string s
by replacing characters in t
.
In each step, you can:
- Choose any character in string
t
- Replace it with any other character
An anagram is a rearrangement of letters - two strings are anagrams if they contain exactly the same characters with the same frequencies, just potentially in a different order. For example, "listen" and "silent" are anagrams.
The task is to find the minimum number of steps (character replacements) needed to make t
an anagram of s
.
For example:
- If
s = "bab"
andt = "aba"
, they are already anagrams, so the answer is0
- If
s = "leetcode"
andt = "practice"
, you would need to replace several characters int
to match the character frequencies ins
The key insight is that you need to make the character frequencies in t
match those in s
. If s
has more of a certain character than t
, you'll need to add that character to t
by replacing other characters. The minimum number of replacements equals the number of excess characters in t
that don't match with s
.
Intuition
To make t
an anagram of s
, we need the character frequencies in both strings to be identical. Let's think about what this means:
If s
has 3 'a's and t
has 5 'a's, then t
has 2 extra 'a's that need to be replaced with other characters. Conversely, if s
has 5 'a's and t
has only 3 'a's, then t
is missing 2 'a's, which means we need to replace 2 other characters in t
to become 'a's.
The key realization is that we only need to count the excess characters in t
. Why? Because:
- For every character that appears more frequently in
t
than ins
, we have excess characters that must be replaced - For every character that appears less frequently in
t
than ins
, there's a deficit that will be filled by replacing those excess characters
Since each replacement fixes both an excess (removing an unwanted character) and a deficit (adding a needed character), the minimum number of replacements equals the total number of excess characters in t
.
Here's the clever part of the solution: We start by counting all characters in s
. Then, as we traverse t
, we decrement the count for each character we encounter. When a count goes negative, it means we've seen this character more times in t
than it exists in s
- this is an excess character that needs replacement. By counting how many times we go negative, we get the exact number of replacements needed.
For example, if s = "aab"
and t = "bba"
:
- Count in
s
:{'a': 2, 'b': 1}
- Processing
t
:- First 'b': count becomes
{'a': 2, 'b': 0}
- Second 'b': count becomes
{'a': 2, 'b': -1}
(excess found, increment answer) - 'a': count becomes
{'a': 1, 'b': -1}
- First 'b': count becomes
- Result: 1 replacement needed
Solution Approach
The solution uses a counting approach with a hash table to track character frequencies.
Step 1: Count characters in string s
We use Python's Counter
from the collections module to create a hash table cnt
that stores the frequency of each character in string s
. For example, if s = "leetcode"
, then cnt
would be {'l': 1, 'e': 3, 't': 1, 'c': 1, 'o': 1, 'd': 1}
.
Step 2: Process string t
and count excess characters
We traverse through each character c
in string t
and perform two operations:
- Decrement the count of character
c
in our hash table:cnt[c] -= 1
- Check if the count becomes negative:
cnt[c] < 0
When cnt[c]
becomes negative, it indicates that character c
appears more times in t
than in s
. This is an excess character that needs to be replaced.
Step 3: Track the number of replacements
We maintain a variable ans
to count the total number of excess characters. Each time we encounter a negative count (meaning we found an excess character), we increment ans
by 1.
Implementation walkthrough:
class Solution:
def minSteps(self, s: str, t: str) -> int:
cnt = Counter(s) # Count frequency of each character in s
ans = 0 # Initialize replacement counter
for c in t: # Process each character in t
cnt[c] -= 1 # Decrement the count for this character
ans += cnt[c] < 0 # If count is negative, increment answer
return ans
The expression ans += cnt[c] < 0
is a clever Python idiom where cnt[c] < 0
evaluates to True
(which equals 1) when the count is negative, or False
(which equals 0) otherwise.
Time Complexity: O(n)
where n
is the length of the strings, as we traverse both strings once.
Space Complexity: O(k)
where k
is the number of unique characters in string s
, typically O(26)
for lowercase English letters, which is effectively O(1)
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Example: s = "anagram"
and t = "mangaar"
Step 1: Count characters in string s
First, we create a frequency map of characters in s
:
s = "anagram"
→cnt = {'a': 3, 'n': 1, 'g': 1, 'r': 1, 'm': 1}
Step 2: Process each character in t
and track excess
Now we traverse t = "mangaar"
character by character:
Character | Action | cnt after decrement | Is negative? | ans |
---|---|---|---|---|
'm' | cnt['m'] = 1 - 1 = 0 | {'a': 3, 'n': 1, 'g': 1, 'r': 1, 'm': 0} | No | 0 |
'a' | cnt['a'] = 3 - 1 = 2 | {'a': 2, 'n': 1, 'g': 1, 'r': 1, 'm': 0} | No | 0 |
'n' | cnt['n'] = 1 - 1 = 0 | {'a': 2, 'n': 0, 'g': 1, 'r': 1, 'm': 0} | No | 0 |
'g' | cnt['g'] = 1 - 1 = 0 | {'a': 2, 'n': 0, 'g': 0, 'r': 1, 'm': 0} | No | 0 |
'a' | cnt['a'] = 2 - 1 = 1 | {'a': 1, 'n': 0, 'g': 0, 'r': 1, 'm': 0} | No | 0 |
'a' | cnt['a'] = 1 - 1 = 0 | {'a': 0, 'n': 0, 'g': 0, 'r': 1, 'm': 0} | No | 0 |
'r' | cnt['r'] = 1 - 1 = 0 | {'a': 0, 'n': 0, 'g': 0, 'r': 0, 'm': 0} | No | 0 |
Result: All counts reach exactly 0, meaning t
already contains the exact same characters as s
. Therefore, ans = 0
- no replacements needed! Indeed, "mangaar" is already an anagram of "anagram".
Let's try another example where replacements ARE needed:
s = "abc"
and t = "bba"
Step 1: Count characters in s
:
cnt = {'a': 1, 'b': 1, 'c': 1}
Step 2: Process t = "bba"
:
Character | Action | cnt after decrement | Is negative? | ans |
---|---|---|---|---|
'b' | cnt['b'] = 1 - 1 = 0 | {'a': 1, 'b': 0, 'c': 1} | No | 0 |
'b' | cnt['b'] = 0 - 1 = -1 | {'a': 1, 'b': -1, 'c': 1} | Yes! | 1 |
'a' | cnt['a'] = 1 - 1 = 0 | {'a': 0, 'b': -1, 'c': 1} | No | 1 |
Result: ans = 1
This makes sense! String t
has an extra 'b' (2 'b's vs 1 in s
) and is missing a 'c'. We need exactly 1 replacement: change one 'b' to 'c' to get "bca" or "abc", both anagrams of "abc".
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def minSteps(self, s: str, t: str) -> int:
5 # Count frequency of each character in string s
6 char_count = Counter(s)
7
8 # Track the number of characters that need to be changed
9 steps_needed = 0
10
11 # Process each character in string t
12 for char in t:
13 # Decrement the count for this character
14 char_count[char] -= 1
15
16 # If count becomes negative, it means t has more of this character than s
17 # We need to change this excess character in t
18 if char_count[char] < 0:
19 steps_needed += 1
20
21 return steps_needed
22
1class Solution {
2 public int minSteps(String s, String t) {
3 // Array to store character frequency difference
4 // Index represents character (0 for 'a', 1 for 'b', ..., 25 for 'z')
5 int[] characterCount = new int[26];
6
7 // Count frequency of each character in string s
8 for (char character : s.toCharArray()) {
9 characterCount[character - 'a']++;
10 }
11
12 // Initialize counter for minimum steps needed
13 int minimumSteps = 0;
14
15 // Process each character in string t
16 for (char character : t.toCharArray()) {
17 // Decrement count for current character
18 // If count becomes negative, it means this character appears more in t than in s
19 if (--characterCount[character - 'a'] < 0) {
20 // Increment steps counter for each extra character in t
21 minimumSteps++;
22 }
23 }
24
25 // Return the minimum number of steps to make t an anagram of s
26 return minimumSteps;
27 }
28}
29
1class Solution {
2public:
3 int minSteps(string s, string t) {
4 // Array to store frequency of each character in string s
5 // Index 0-25 represents 'a'-'z'
6 int charFrequency[26] = {0};
7
8 // Count frequency of each character in string s
9 for (char ch : s) {
10 charFrequency[ch - 'a']++;
11 }
12
13 // Count minimum steps needed to make t an anagram of s
14 int minStepsCount = 0;
15
16 // Process each character in string t
17 for (char ch : t) {
18 // Decrement frequency for current character
19 // If frequency becomes negative, it means this character appears
20 // more times in t than in s, so we need to replace it
21 charFrequency[ch - 'a']--;
22 if (charFrequency[ch - 'a'] < 0) {
23 minStepsCount++;
24 }
25 }
26
27 return minStepsCount;
28 }
29};
30
1/**
2 * Calculates the minimum number of steps to make two strings anagrams
3 * by counting character frequency differences
4 * @param s - First string
5 * @param t - Second string
6 * @returns Minimum number of character replacements needed
7 */
8function minSteps(s: string, t: string): number {
9 // Initialize array to track character frequency (26 lowercase letters)
10 const characterCount: number[] = Array(26).fill(0);
11
12 // Count frequency of each character in string s
13 for (const character of s) {
14 // Convert character to index (0-25) by subtracting ASCII value of 'a'
15 const index: number = character.charCodeAt(0) - 97;
16 characterCount[index]++;
17 }
18
19 // Track the number of steps needed
20 let stepsNeeded: number = 0;
21
22 // Process each character in string t
23 for (const character of t) {
24 // Convert character to index and decrement its count
25 const index: number = character.charCodeAt(0) - 97;
26 characterCount[index]--;
27
28 // If count goes negative, this character appears more in t than in s
29 // We need to replace it, so increment steps
30 if (characterCount[index] < 0) {
31 stepsNeeded++;
32 }
33 }
34
35 return stepsNeeded;
36}
37
Time and Space Complexity
The time complexity is O(m + n)
, where m
is the length of string s
and n
is the length of string t
. This is because:
- Creating the Counter for string
s
takesO(m)
time as it needs to iterate through all characters ins
- The for loop iterates through all characters in string
t
, which takesO(n)
time - Each operation inside the loop (dictionary access, decrement, comparison) takes
O(1)
time
The space complexity is O(|Σ|)
, where |Σ|
represents the size of the character set. In this problem, since we're dealing with lowercase letters, |Σ| = 26
. The Counter object stores at most 26 unique characters (all lowercase letters), so the space used is bounded by the alphabet size rather than the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Problem - Counting Both Deficits and Excesses
The Mistake:
A common error is trying to count both the characters that are missing from t
(deficit) AND the characters that are excess in t
, then taking some combination of these counts. For example:
# INCORRECT approach
def minSteps(self, s: str, t: str) -> int:
cnt_s = Counter(s)
cnt_t = Counter(t)
missing = 0 # Characters in s but not enough in t
excess = 0 # Characters in t but not enough in s
for char in cnt_s:
if cnt_s[char] > cnt_t.get(char, 0):
missing += cnt_s[char] - cnt_t.get(char, 0)
for char in cnt_t:
if cnt_t[char] > cnt_s.get(char, 0):
excess += cnt_t[char] - cnt_s.get(char, 0)
return max(missing, excess) # Or return (missing + excess) // 2
Why This is Wrong:
The number of missing characters always equals the number of excess characters. If s
needs 3 more 'a's than t
has, then t
must have 3 excess characters of other types. Counting both leads to redundant work and potential confusion.
The Correct Insight:
You only need to count EITHER the excess characters in t
OR the deficit characters in t
. Both will give the same answer. The provided solution elegantly counts only the excess characters.
Pitfall 2: Using Wrong Default Value for Counter
The Mistake:
# INCORRECT - will cause KeyError
def minSteps(self, s: str, t: str) -> int:
cnt = {} # Using regular dict instead of Counter
for c in s:
cnt[c] = cnt.get(c, 0) + 1
ans = 0
for c in t:
cnt[c] -= 1 # KeyError if c not in cnt!
if cnt[c] < 0:
ans += 1
return ans
Why This is Wrong:
If character c
from string t
doesn't exist in string s
, a regular dictionary will throw a KeyError when trying to decrement. Counter automatically handles missing keys by treating them as having a value of 0.
The Solution:
Either use Counter
(which handles missing keys gracefully) or manually check for key existence:
# Option 1: Use Counter (recommended) cnt = Counter(s) # Option 2: Manual checking with regular dict for c in t: cnt[c] = cnt.get(c, 0) - 1 if cnt[c] < 0: ans += 1
Pitfall 3: Overthinking with Sorting or Complex Matching
The Mistake:
# INCORRECT - Unnecessarily complex
def minSteps(self, s: str, t: str) -> int:
s_sorted = sorted(s)
t_sorted = sorted(t)
changes = 0
i, j = 0, 0
while i < len(s) and j < len(t):
if s_sorted[i] == t_sorted[j]:
i += 1
j += 1
elif s_sorted[i] < t_sorted[j]:
changes += 1
i += 1
else:
j += 1
return changes + (len(s) - i)
Why This is Wrong: Sorting and trying to match characters position-by-position doesn't correctly count the minimum replacements needed. This approach fails because it doesn't properly track character frequencies and can miss valid matches.
The Solution: Stick to frequency counting. The problem is fundamentally about matching character frequencies, not about matching positions or finding a specific arrangement.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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!