Facebook Pixel

1737. Change Minimum Characters to Satisfy One of Three Conditions

MediumHash TableStringCountingPrefix Sum
Leetcode Link

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:

  1. Every letter in string a is strictly less than every letter in string b (alphabetically). For example, if a = "abc" and b = "def", this condition is satisfied because 'a', 'b', 'c' are all less than 'd', 'e', 'f'.

  2. Every letter in string b is strictly less than every letter in string a (alphabetically). This is the reverse of condition 1.

  3. Both strings a and b consist of only one distinct letter. For example, a = "aaaa" and b = "bbb" would satisfy this condition if we change them to a = "cccc" and b = "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" and b = "abe", to satisfy condition 1, we could change a to "ccc" (2 operations) and b 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 for b, totaling 4 operations.

The answer would be the minimum among all possible ways to satisfy any of the three conditions.

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

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 position i or greater need to be changed to something less than i
  • All characters in string b that are at positions less than i need to be changed to something at i 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 (make a < b)
  • f(cnt2, cnt1): Check condition 2 (make b < 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 Evaluator

Example 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" in a, change "be" in b)
  • Target 'b': 6 - 0 - 1 = 5 operations (change "ace" in a, change "ae" in b)
  • Target 'c': 6 - 1 - 0 = 5 operations (change "ae" in a, change "abe" in b)
  • Target 'd': 6 - 0 - 0 = 6 operations (change all characters)
  • Target 'e': 6 - 1 - 1 = 4 operations (change "ac" in a, change "ab" in b)
  • 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
  • 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
  • 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

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
  • Boundary at 'c' (i=2):

    • In b: change 'e' (≥'c') → 1 operation
    • In a: change 'a' (<'c') → 1 operation
    • Total: 2 operations
  • 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

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 string a
  • Building cnt2 array: O(n) - iterating through string b
  • 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 computes sum(cnt1[i:]) and sum(cnt2[:i]). Each sum operation takes O(C) time, resulting in O(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 index i)
  • All characters in the second string must be >= i (at least character at index i)
  • Therefore:
    • Change all characters >= i in the first string (make them smaller)
    • Change all characters < i in the second string (make them larger)

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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More