1737. Change Minimum Characters to Satisfy One of Three Conditions
Problem Description
You are given two strings a
and b
that consist of lowercase letters. In one operation, you can change any character in either string to any lowercase letter.
Your goal is to make the strings satisfy exactly one of these three conditions:
-
Every letter in string
a
is strictly less than every letter in stringb
(alphabetically). For example, ifa = "abc"
andb = "def"
, this condition is satisfied because 'a', 'b', 'c' are all less than 'd', 'e', 'f'. -
Every letter in string
b
is strictly less than every letter in stringa
(alphabetically). This is the reverse of condition 1. -
Both strings
a
andb
consist of only one distinct letter. For example,a = "aaaa"
andb = "bbb"
would satisfy this condition if we change them toa = "cccc"
andb = "ccc"
.
You need to find the minimum number of operations (character changes) required to achieve any one of these three conditions.
For example:
- If
a = "ace"
andb = "abe"
, to satisfy condition 1, we could changea
to"ccc"
(2 operations) andb
to"ddd"
(3 operations), totaling 5 operations. - To satisfy condition 3, we could change both strings to all 'a's, which would take 2 operations for
a
and 2 operations forb
, totaling 4 operations.
The answer would be the minimum among all possible ways to satisfy any of the three conditions.
Intuition
The key insight is that we need to consider all three conditions and find which one requires the minimum operations.
For condition 3 (both strings have only one distinct letter), we can think of it this way: if we pick a target letter, say 'c', then we need to change all characters in both strings that are not 'c' to 'c'. The cost would be the total number of characters minus the count of 'c' in both strings. We should try all 26 possible letters and pick the one with minimum cost.
For conditions 1 and 2 (one string's letters all less than the other's), we need to establish a "boundary" in the alphabet. Imagine drawing a line somewhere in the alphabet - everything to the left goes to one string, everything to the right goes to the other.
For condition 1 (every letter in a
< every letter in b
), we need to pick a boundary position. If we choose position i
(where i
represents a letter), then:
- All characters in string
a
that are at positioni
or greater need to be changed to something less thani
- All characters in string
b
that are at positions less thani
need to be changed to something ati
or greater
The cost is: sum(cnt1[i:]) + sum(cnt2[:i])
where cnt1[i:]
counts characters in a
that need to be moved left, and cnt2[:i]
counts characters in b
that need to be moved right.
We try all possible boundary positions (from 'b' to 'z' as boundaries, since we need at least one letter on each side) and find the minimum cost.
Condition 2 is symmetric - we just swap the roles of strings a
and b
.
By calculating the minimum cost for all three conditions and taking the overall minimum, we get our answer.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation follows a three-step approach:
Step 1: Count character frequencies
First, we create two frequency arrays cnt1
and cnt2
of size 26 to count occurrences of each letter in strings a
and b
respectively:
cnt1 = [0] * 26
cnt2 = [0] * 26
for c in a:
cnt1[ord(c) - ord('a')] += 1
for c in b:
cnt2[ord(c) - ord('a')] += 1
Step 2: Check condition 3 (both strings with same character)
We iterate through all 26 possible target characters. For each character at index i
, the cost to make both strings consist of only that character is:
- Characters to change in
a
:m - cnt1[i]
(total length minus existing count) - Characters to change in
b
:n - cnt2[i]
(total length minus existing count) - Total:
m + n - cnt1[i] - cnt2[i]
ans = m + n # Initialize with maximum possible operations
for c1, c2 in zip(cnt1, cnt2):
ans = min(ans, m + n - c1 - c2)
Step 3: Check conditions 1 and 2 (one string less than the other)
We define a helper function f(cnt1, cnt2)
that finds the minimum operations to make all characters in the first string strictly less than all characters in the second string:
def f(cnt1, cnt2):
for i in range(1, 26): # Try each boundary position
t = sum(cnt1[i:]) + sum(cnt2[:i])
nonlocal ans
ans = min(ans, t)
For boundary position i
:
sum(cnt1[i:])
: Count of characters in first string that are>= i
(need to be changed to< i
)sum(cnt2[:i])
: Count of characters in second string that are< i
(need to be changed to>= i
)
We call this function twice:
f(cnt1, cnt2)
: Check condition 1 (makea < b
)f(cnt2, cnt1)
: Check condition 2 (makeb < a
)
The final answer is the minimum among all three conditions.
Time Complexity: O(n + m)
where n
and m
are the lengths of strings a
and b
. We iterate through both strings once for counting, and the enumeration of 26 letters is constant time.
Space Complexity: O(1)
as we only use two fixed-size arrays of length 26.
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 small example with a = "ace"
and b = "abe"
.
Step 1: Count character frequencies
For string a = "ace"
:
- 'a' appears 1 time →
cnt1[0] = 1
- 'c' appears 1 time →
cnt1[2] = 1
- 'e' appears 1 time →
cnt1[4] = 1
For string b = "abe"
:
- 'a' appears 1 time →
cnt2[0] = 1
- 'b' appears 1 time →
cnt2[1] = 1
- 'e' appears 1 time →
cnt2[4] = 1
Step 2: Check condition 3 (both strings same character)
We try making both strings consist of a single character. The cost for each letter is 3 + 3 - cnt1[i] - cnt2[i]
:
- Target 'a':
6 - 1 - 1 = 4
operations (change "ce" ina
, change "be" inb
) - Target 'b':
6 - 0 - 1 = 5
operations (change "ace" ina
, change "ae" inb
) - Target 'c':
6 - 1 - 0 = 5
operations (change "ae" ina
, change "abe" inb
) - Target 'd':
6 - 0 - 0 = 6
operations (change all characters) - Target 'e':
6 - 1 - 1 = 4
operations (change "ac" ina
, change "ab" inb
) - All other letters:
6
operations
Minimum for condition 3: 4 operations
Step 3a: Check condition 1 (make all of a
< all of b
)
We try different boundary positions. For boundary i
, we need:
- Characters in
a
that are≥ i
to become< i
- Characters in
b
that are< i
to become≥ i
Let's check key boundaries:
-
Boundary at 'b' (i=1):
- In
a
: change 'c' and 'e' (≥'b') → 2 operations - In
b
: change 'a' (<'b') → 1 operation - Total: 3 operations
- In
-
Boundary at 'c' (i=2):
- In
a
: change 'c' and 'e' (≥'c') → 2 operations - In
b
: change 'a' and 'b' (<'c') → 2 operations - Total: 4 operations
- In
-
Boundary at 'f' (i=5):
- In
a
: change nothing (all <'f') → 0 operations - In
b
: change 'a', 'b', 'e' (<'f') → 3 operations - Total: 3 operations
- In
Minimum for condition 1: 3 operations
Step 3b: Check condition 2 (make all of b
< all of a
)
Now we swap roles - try to make everything in b
less than everything in a
:
-
Boundary at 'b' (i=1):
- In
b
: change 'b' and 'e' (≥'b') → 2 operations - In
a
: change 'a' (<'b') → 1 operation - Total: 3 operations
- In
-
Boundary at 'c' (i=2):
- In
b
: change 'e' (≥'c') → 1 operation - In
a
: change 'a' (<'c') → 1 operation - Total: 2 operations
- In
-
Boundary at 'f' (i=5):
- In
b
: change nothing (all <'f') → 0 operations - In
a
: change 'a', 'c', 'e' (<'f') → 3 operations - Total: 3 operations
- In
Minimum for condition 2: 2 operations
Final Answer: The minimum across all three conditions is 2 operations (achieved by condition 2 with boundary at 'c', making b = "bbb"
and a = "cce"
).
Solution Implementation
1class Solution:
2 def minCharacters(self, a: str, b: str) -> int:
3 """
4 Find minimum operations to satisfy one of three conditions:
5 1. Every character in 'a' is strictly less than every character in 'b'
6 2. Every character in 'b' is strictly less than every character in 'a'
7 3. Both strings consist of only one unique character
8 """
9
10 def calculate_operations_for_separation(freq_first: list[int], freq_second: list[int]) -> None:
11 """
12 Calculate operations needed to make all chars in first string < all chars in second string.
13 Try each possible separator position from 'b' to 'z'.
14
15 Args:
16 freq_first: Frequency array of first string (to be made smaller)
17 freq_second: Frequency array of second string (to be made larger)
18 """
19 nonlocal min_operations
20
21 # Try each character position as separator (1 corresponds to 'b', 25 to 'z')
22 for separator_idx in range(1, 26):
23 # Operations needed:
24 # - Change all chars >= separator in first string to something smaller
25 # - Change all chars < separator in second string to something larger
26 operations_needed = sum(freq_first[separator_idx:]) + sum(freq_second[:separator_idx])
27 min_operations = min(min_operations, operations_needed)
28
29 # Get string lengths
30 len_a, len_b = len(a), len(b)
31
32 # Initialize frequency arrays for both strings (26 letters)
33 freq_a = [0] * 26
34 freq_b = [0] * 26
35
36 # Count character frequencies in string a
37 for char in a:
38 freq_a[ord(char) - ord('a')] += 1
39
40 # Count character frequencies in string b
41 for char in b:
42 freq_b[ord(char) - ord('a')] += 1
43
44 # Initialize minimum operations to worst case (changing all characters)
45 min_operations = len_a + len_b
46
47 # Condition 3: Check cost to make both strings consist of same single character
48 # For each possible target character, calculate operations needed
49 for count_a, count_b in zip(freq_a, freq_b):
50 # Operations = total chars - chars already matching target character
51 operations_for_same_char = len_a + len_b - count_a - count_b
52 min_operations = min(min_operations, operations_for_same_char)
53
54 # Condition 1: Make all chars in a < all chars in b
55 calculate_operations_for_separation(freq_a, freq_b)
56
57 # Condition 2: Make all chars in b < all chars in a
58 calculate_operations_for_separation(freq_b, freq_a)
59
60 return min_operations
61
1class Solution {
2 private int minOperations;
3
4 public int minCharacters(String a, String b) {
5 int lengthA = a.length();
6 int lengthB = b.length();
7
8 // Count frequency of each character in both strings
9 int[] frequencyA = new int[26];
10 int[] frequencyB = new int[26];
11
12 // Build frequency array for string a
13 for (int i = 0; i < lengthA; ++i) {
14 ++frequencyA[a.charAt(i) - 'a'];
15 }
16
17 // Build frequency array for string b
18 for (int i = 0; i < lengthB; ++i) {
19 ++frequencyB[b.charAt(i) - 'a'];
20 }
21
22 // Initialize minimum operations to total length (worst case)
23 minOperations = lengthA + lengthB;
24
25 // Option 3: Make both strings consist of the same character
26 // Try each character from 'a' to 'z' as the target character
27 for (int targetChar = 0; targetChar < 26; ++targetChar) {
28 // Calculate operations needed: total chars minus chars already equal to target
29 int operationsNeeded = lengthA + lengthB - frequencyA[targetChar] - frequencyB[targetChar];
30 minOperations = Math.min(minOperations, operationsNeeded);
31 }
32
33 // Option 1: Make every character in a strictly less than every character in b
34 calculateMinOperationsForStrictOrdering(frequencyA, frequencyB);
35
36 // Option 2: Make every character in b strictly less than every character in a
37 calculateMinOperationsForStrictOrdering(frequencyB, frequencyA);
38
39 return minOperations;
40 }
41
42 /**
43 * Calculate minimum operations to make all characters in first string
44 * strictly greater than all characters in second string.
45 *
46 * @param greaterStringFreq frequency array of string that should have greater characters
47 * @param lesserStringFreq frequency array of string that should have lesser characters
48 */
49 private void calculateMinOperationsForStrictOrdering(int[] greaterStringFreq, int[] lesserStringFreq) {
50 // Try each character from 'b' to 'z' as the minimum character for the greater string
51 for (int pivotChar = 1; pivotChar < 26; ++pivotChar) {
52 int operationsNeeded = 0;
53
54 // Count characters in greater string that need to be changed (those < pivotChar)
55 for (int j = pivotChar; j < 26; ++j) {
56 operationsNeeded += greaterStringFreq[j];
57 }
58
59 // Count characters in lesser string that need to be changed (those >= pivotChar)
60 for (int j = 0; j < pivotChar; ++j) {
61 operationsNeeded += lesserStringFreq[j];
62 }
63
64 minOperations = Math.min(minOperations, operationsNeeded);
65 }
66 }
67}
68
1class Solution {
2public:
3 int minCharacters(string a, string b) {
4 int lengthA = a.size();
5 int lengthB = b.size();
6
7 // Count frequency of each character in both strings
8 vector<int> frequencyA(26, 0);
9 vector<int> frequencyB(26, 0);
10
11 for (char& ch : a) {
12 frequencyA[ch - 'a']++;
13 }
14 for (char& ch : b) {
15 frequencyB[ch - 'a']++;
16 }
17
18 int minOperations = lengthA + lengthB;
19
20 // Option 3: Make both strings have only one distinct character
21 // Try each character from 'a' to 'z' as the target character
22 for (int targetChar = 0; targetChar < 26; targetChar++) {
23 // Operations needed = total chars - chars already equal to target
24 int operationsNeeded = lengthA + lengthB - frequencyA[targetChar] - frequencyB[targetChar];
25 minOperations = min(minOperations, operationsNeeded);
26 }
27
28 // Lambda function to calculate operations for making all chars in first string
29 // strictly less than all chars in second string
30 auto calculateStrictlyLess = [&](vector<int>& firstFreq, vector<int>& secondFreq) {
31 // Try each character from 'b' to 'z' as the dividing point
32 // First string will have all chars < dividing point
33 // Second string will have all chars >= dividing point
34 for (int dividingPoint = 1; dividingPoint < 26; dividingPoint++) {
35 int operationsNeeded = 0;
36
37 // Count chars in first string that need to be changed (chars >= dividingPoint)
38 for (int j = dividingPoint; j < 26; j++) {
39 operationsNeeded += firstFreq[j];
40 }
41
42 // Count chars in second string that need to be changed (chars < dividingPoint)
43 for (int j = 0; j < dividingPoint; j++) {
44 operationsNeeded += secondFreq[j];
45 }
46
47 minOperations = min(minOperations, operationsNeeded);
48 }
49 };
50
51 // Option 1: Make every character in a strictly less than every character in b
52 calculateStrictlyLess(frequencyA, frequencyB);
53
54 // Option 2: Make every character in b strictly less than every character in a
55 calculateStrictlyLess(frequencyB, frequencyA);
56
57 return minOperations;
58 }
59};
60
1function minCharacters(a: string, b: string): number {
2 const lengthA = a.length;
3 const lengthB = b.length;
4
5 // Initialize frequency arrays for characters a-z (26 letters)
6 const frequencyA = new Array(26).fill(0);
7 const frequencyB = new Array(26).fill(0);
8
9 // ASCII value of 'a' for character index calculation
10 const asciiBase = 'a'.charCodeAt(0);
11
12 // Count character frequencies in string a
13 for (const char of a) {
14 frequencyA[char.charCodeAt(0) - asciiBase]++;
15 }
16
17 // Count character frequencies in string b
18 for (const char of b) {
19 frequencyB[char.charCodeAt(0) - asciiBase]++;
20 }
21
22 // Prefix sums to track characters less than or equal to current character
23 let prefixSumA = 0;
24 let prefixSumB = 0;
25
26 // Initialize result with maximum possible operations (changing all characters)
27 let minOperations = lengthA + lengthB;
28
29 // Check for each character from 'a' to 'y' (not 'z' as we need a split point)
30 for (let i = 0; i < 25; i++) {
31 prefixSumA += frequencyA[i];
32 prefixSumB += frequencyB[i];
33
34 // Calculate minimum operations for three cases:
35 // Case 1: Make all chars in a > all chars in b (a chars <= i become > i, b chars > i become <= i)
36 const case1Operations = lengthA - prefixSumA + prefixSumB;
37
38 // Case 2: Make all chars in b > all chars in a (b chars <= i become > i, a chars > i become <= i)
39 const case2Operations = prefixSumA + lengthB - prefixSumB;
40
41 // Case 3: Make both strings consist of only character i
42 const case3Operations = lengthA + lengthB - frequencyA[i] - frequencyB[i];
43
44 minOperations = Math.min(minOperations, case1Operations, case2Operations, case3Operations);
45 }
46
47 // Special case: Make both strings consist of only character 'z'
48 minOperations = Math.min(minOperations, lengthA + lengthB - frequencyA[25] - frequencyB[25]);
49
50 return minOperations;
51}
52
Time and Space Complexity
Time Complexity: O(m + n + C²)
, where m
and n
are the lengths of strings a
and b
respectively, and C
is the size of the character set (C = 26
).
- Building
cnt1
array:O(m)
- iterating through stringa
- Building
cnt2
array:O(n)
- iterating through stringb
- Finding minimum for condition 3 (making all characters same):
O(C)
- iterating through 26 possible characters - Function
f
called twice: Each call iterates from 1 to 25, and for each iteration computessum(cnt1[i:])
andsum(cnt2[:i])
. Each sum operation takesO(C)
time, resulting inO(C²)
for each call - Total for both
f
calls:2 × O(C²) = O(C²)
Overall: O(m) + O(n) + O(C) + O(C²) = O(m + n + C²)
Space Complexity: O(C)
, where C = 26
cnt1
array:O(26) = O(C)
cnt2
array:O(26) = O(C)
- Variable
ans
and other scalar variables:O(1)
Total space: O(C) + O(C) + O(1) = O(C)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Including 'a' as a Valid Separator Character
The Problem: A common mistake is trying to use 'a' (index 0) as a separator when checking conditions 1 and 2. This would mean trying to make all characters in one string less than 'a', which is impossible since 'a' is already the smallest lowercase letter.
Incorrect Implementation:
def calculate_operations_for_separation(freq_first, freq_second):
# WRONG: Starting from index 0
for separator_idx in range(0, 26): # This includes 'a' as separator
operations_needed = sum(freq_first[separator_idx:]) + sum(freq_second[:separator_idx])
min_operations = min(min_operations, operations_needed)
Why It's Wrong:
- When
separator_idx = 0
, we're trying to make all characters in the second string less than 'a' sum(freq_second[:0])
= 0 (no characters to change)- But we need characters < 'a' in the second string, which is impossible
Correct Solution: Always start the separator loop from index 1 (character 'b'):
for separator_idx in range(1, 26): # Start from 'b', not 'a'
Pitfall 2: Including 'z' + 1 as a Valid Separator
The Problem: Similarly, trying to use index 26 (beyond 'z') as a separator would mean making all characters in one string greater than 'z', which is impossible with lowercase letters.
Incorrect Implementation:
def calculate_operations_for_separation(freq_first, freq_second):
# WRONG: Going up to index 26
for separator_idx in range(1, 27): # This includes position after 'z'
operations_needed = sum(freq_first[separator_idx:]) + sum(freq_second[:separator_idx])
min_operations = min(min_operations, operations_needed)
Why It's Wrong:
- When
separator_idx = 26
, we're trying to make all characters in the first string >= a character that doesn't exist - This gives incorrect results as it counts operations that can't actually be performed
Correct Solution: Stop the separator loop at index 25 (character 'z'):
for separator_idx in range(1, 26): # Ends at 'z', not beyond
Pitfall 3: Misunderstanding the Separator Logic
The Problem: Confusion about what the separator index represents and which characters need to be changed.
Common Misunderstanding:
Thinking that for separator at index i
:
- Characters at index
i
in the first string should be kept - Characters at index
i
in the second string should be kept
Correct Understanding:
For separator at index i
:
- All characters in the first string must be
< i
(strictly less than character at indexi
) - All characters in the second string must be
>= i
(at least character at indexi
) - Therefore:
- Change all characters
>= i
in the first string (make them smaller) - Change all characters
< i
in the second string (make them larger)
- Change all characters
Visual Example: If separator is at index 4 (character 'e'):
- First string must have only {'a', 'b', 'c', 'd'}
- Second string must have only {'e', 'f', ..., 'z'}
- Any 'e' or larger in first string must be changed
- Any 'd' or smaller in second string must be changed
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!