Facebook Pixel

1433. Check If a String Can Break Another String

Problem Description

You are given two strings s1 and s2 of the same length. Your task is to determine if some permutation (rearrangement) of string s1 can "break" some permutation of string s2, or if some permutation of s2 can break some permutation of s1.

A string x can break string y (both having the same length n) if for every position i from 0 to n-1, the character at position i in string x is greater than or equal to the character at position i in string y when compared alphabetically. In other words, x[i] >= y[i] for all valid indices.

The key insight is that you can rearrange both strings in any way you want before checking if one can break the other. You need to return true if either:

  • Some permutation of s1 can break some permutation of s2, OR
  • Some permutation of s2 can break some permutation of s1

For example, if s1 = "abc" and s2 = "xya", you could rearrange s1 to "abc" and s2 to "axy". Then checking position by position: 'a' >= 'a' (true), 'b' >= 'x' (false). So s1 cannot break s2 in this arrangement. However, if we check if s2 can break s1: 'a' <= 'a' (true), 'x' >= 'b' (true), 'y' >= 'c' (true), so this permutation of s2 can break this permutation of s1, making the answer true.

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

Intuition

The key realization is that if we want to check whether one string can break another, we need to find the optimal arrangement for both strings. Think about it this way: if string x wants to break string y, we want to maximize our chances at each position.

To maximize the chance that x[i] >= y[i] for all positions, we should:

  • Arrange x to have its characters in the best possible order to be "larger"
  • Arrange y to have its characters in the order that makes them easiest to beat

The optimal strategy is to sort both strings. Why? Because when both strings are sorted:

  • The smallest character in x is matched against the smallest character in y
  • The second smallest in x is matched against the second smallest in y
  • And so on...

This greedy approach works because if the sorted version of x cannot break the sorted version of y, then no permutation of x can break any permutation of y. This is because sorting gives us the best possible matching - pairing smallest with smallest, largest with largest.

For example, if sorted x = "abc" and sorted y = "def", and we find that 'a' < 'd', then no matter how we rearrange x, we'll always have an 'a' that needs to be matched with something from y, and the best case scenario for that 'a' is to be matched with 'd' (the smallest in y). Since even that fails, no arrangement will work.

Since the problem asks if either s1 can break s2 OR s2 can break s1, we simply need to check both conditions on the sorted strings. If either condition holds true (all characters of one sorted string are >= the corresponding characters of the other sorted string), we return true.

Learn more about Greedy and Sorting patterns.

Solution Approach

The solution implements the greedy approach we identified in the intuition:

  1. Sort both strings: Convert both s1 and s2 into sorted character arrays. In Python, sorted(s1) returns a list of characters sorted in alphabetical order.

    cs1 = sorted(s1)
    cs2 = sorted(s2)
  2. Check both breaking conditions: We need to verify if either:

    • cs1 can break cs2: Every character in cs1 is greater than or equal to the corresponding character in cs2
    • cs2 can break cs1: Every character in cs2 is greater than or equal to the corresponding character in cs1
  3. Use the all() function with zip():

    • zip(cs1, cs2) pairs up characters at the same positions from both sorted strings
    • all(a >= b for a, b in zip(cs1, cs2)) checks if cs1 can break cs2 by verifying that every character a from cs1 is >= the corresponding character b from cs2
    • all(a <= b for a, b in zip(cs1, cs2)) checks if cs2 can break cs1 (equivalent to checking b >= a)
  4. Return the logical OR of both conditions: If either condition is true, return true:

    return all(a >= b for a, b in zip(cs1, cs2)) or all(a <= b for a, b in zip(cs1, cs2))

The time complexity is O(n log n) due to sorting both strings, where n is the length of the strings. The space complexity is O(n) for storing the sorted character arrays. The actual comparison takes O(n) time, but sorting dominates the overall complexity.

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 an example with s1 = "abe" and s2 = "acd".

Step 1: Sort both strings

  • cs1 = sorted("abe") = ['a', 'b', 'e']
  • cs2 = sorted("acd") = ['a', 'c', 'd']

Step 2: Check if cs1 can break cs2 We compare position by position:

  • Position 0: 'a' >= 'a' โœ“ (True)
  • Position 1: 'b' >= 'c' โœ— (False, since 'b' < 'c')

Since not all positions satisfy the condition, cs1 cannot break cs2.

Step 3: Check if cs2 can break cs1 We compare position by position:

  • Position 0: 'a' >= 'a' โœ“ (True)
  • Position 1: 'c' >= 'b' โœ“ (True)
  • Position 2: 'd' >= 'e' โœ— (False, since 'd' < 'e')

Since not all positions satisfy the condition, cs2 cannot break cs1.

Step 4: Return result Since neither condition is satisfied (False or False = False), we return false.

Let's try another example where the answer is true: s1 = "abc" and s2 = "xya".

Step 1: Sort both strings

  • cs1 = sorted("abc") = ['a', 'b', 'c']
  • cs2 = sorted("xya") = ['a', 'x', 'y']

Step 2: Check if cs1 can break cs2

  • Position 0: 'a' >= 'a' โœ“
  • Position 1: 'b' >= 'x' โœ— (False)

cs1 cannot break cs2.

Step 3: Check if cs2 can break cs1

  • Position 0: 'a' >= 'a' โœ“
  • Position 1: 'x' >= 'b' โœ“
  • Position 2: 'y' >= 'c' โœ“

All positions satisfy the condition, so cs2 can break cs1.

Step 4: Return result Since at least one condition is satisfied (False or True = True), we return true.

Solution Implementation

1class Solution:
2    def checkIfCanBreak(self, s1: str, s2: str) -> bool:
3        # Sort both strings to compare characters lexicographically
4        sorted_s1 = sorted(s1)
5        sorted_s2 = sorted(s2)
6      
7        # Check if s1 can break s2 (all characters in sorted_s1 >= corresponding characters in sorted_s2)
8        s1_breaks_s2 = all(char1 >= char2 for char1, char2 in zip(sorted_s1, sorted_s2))
9      
10        # Check if s2 can break s1 (all characters in sorted_s2 >= corresponding characters in sorted_s1)
11        s2_breaks_s1 = all(char1 <= char2 for char1, char2 in zip(sorted_s1, sorted_s2))
12      
13        # Return true if either string can break the other
14        return s1_breaks_s2 or s2_breaks_s1
15
1class Solution {
2    /**
3     * Checks if one string can break another string.
4     * A string x can break string y if x[i] >= y[i] for all positions i when both strings are sorted.
5     * 
6     * @param s1 First input string
7     * @param s2 Second input string
8     * @return true if either s1 can break s2 or s2 can break s1, false otherwise
9     */
10    public boolean checkIfCanBreak(String s1, String s2) {
11        // Convert strings to character arrays for sorting
12        char[] charArray1 = s1.toCharArray();
13        char[] charArray2 = s2.toCharArray();
14      
15        // Sort both character arrays in ascending order
16        Arrays.sort(charArray1);
17        Arrays.sort(charArray2);
18      
19        // Check if either array can break the other
20        // Returns true if charArray1 can break charArray2 OR charArray2 can break charArray1
21        return canBreak(charArray1, charArray2) || canBreak(charArray2, charArray1);
22    }
23
24    /**
25     * Helper method to check if the first character array can break the second one.
26     * Array1 can break Array2 if Array1[i] >= Array2[i] for all positions i.
27     * 
28     * @param array1 First character array (potential breaker)
29     * @param array2 Second character array (to be broken)
30     * @return true if array1 can break array2, false otherwise
31     */
32    private boolean canBreak(char[] array1, char[] array2) {
33        // Iterate through all positions in the arrays
34        for (int i = 0; i < array1.length; i++) {
35            // If any character in array1 is smaller than the corresponding character in array2,
36            // array1 cannot break array2
37            if (array1[i] < array2[i]) {
38                return false;
39            }
40        }
41        // All characters in array1 are greater than or equal to those in array2
42        return true;
43    }
44}
45
1class Solution {
2public:
3    /**
4     * Check if one string can break another string
5     * A string x can break string y if x[i] >= y[i] for all i after sorting both strings
6     * @param s1 First input string
7     * @param s2 Second input string
8     * @return true if either s1 can break s2 or s2 can break s1
9     */
10    bool checkIfCanBreak(string s1, string s2) {
11        // Sort both strings to compare characters in lexicographical order
12        sort(s1.begin(), s1.end());
13        sort(s2.begin(), s2.end());
14      
15        // Check if s1 can break s2 OR s2 can break s1
16        return canBreak(s1, s2) || canBreak(s2, s1);
17    }
18
19private:
20    /**
21     * Helper function to check if first string can break the second string
22     * @param first The string that attempts to break the second
23     * @param second The string being broken
24     * @return true if first[i] >= second[i] for all positions i
25     */
26    bool canBreak(const string& first, const string& second) {
27        // Iterate through each position and compare characters
28        for (int i = 0; i < first.size(); ++i) {
29            // If any character in first is smaller than corresponding character in second,
30            // first cannot break second
31            if (first[i] < second[i]) {
32                return false;
33            }
34        }
35        // All characters in first are greater than or equal to those in second
36        return true;
37    }
38};
39
1/**
2 * Checks if one string can "break" another string
3 * A string x can break string y if x[i] >= y[i] for all positions i after sorting both strings
4 * @param s1 - First input string
5 * @param s2 - Second input string
6 * @returns true if s1 can break s2 OR s2 can break s1, false otherwise
7 */
8function checkIfCanBreak(s1: string, s2: string): boolean {
9    // Convert strings to character arrays for sorting
10    const sortedChars1: string[] = Array.from(s1);
11    const sortedChars2: string[] = Array.from(s2);
12  
13    // Sort both character arrays in ascending order
14    sortedChars1.sort();
15    sortedChars2.sort();
16  
17    // Check if either string can break the other
18    return canBreak(sortedChars1, sortedChars2) || canBreak(sortedChars2, sortedChars1);
19}
20
21/**
22 * Helper function to check if first character array can break the second
23 * @param chars1 - First sorted character array
24 * @param chars2 - Second sorted character array
25 * @returns true if chars1[i] >= chars2[i] for all indices, false otherwise
26 */
27function canBreak(chars1: string[], chars2: string[]): boolean {
28    // Compare each character at the same position
29    for (let i = 0; i < chars1.length; i++) {
30        // If any character in chars1 is less than corresponding character in chars2
31        if (chars1[i] < chars2[i]) {
32            return false;
33        }
34    }
35    return true;
36}
37

Time and Space Complexity

Time Complexity: O(n log n) where n is the length of the input strings.

  • The sorted() function is called twice, once for each string. Sorting has a time complexity of O(n log n) for each string.
  • The zip() operation creates pairs of characters and runs in O(n) time.
  • The all() function with generator expression iterates through all pairs, which takes O(n) time in the worst case.
  • This happens at most twice (once for each all() check due to the or operator's short-circuit evaluation).
  • Overall: O(n log n) + O(n log n) + O(n) + O(n) = O(n log n)

Space Complexity: O(n) where n is the length of the input strings.

  • The sorted() function creates two new sorted lists cs1 and cs2, each containing n characters.
  • The zip() creates an iterator that doesn't consume additional space beyond O(1).
  • The generator expressions in all() don't create additional lists, they evaluate lazily.
  • Total space used: O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Attempting to Check All Permutations

A natural but incorrect approach would be trying to generate all possible permutations of both strings and checking every combination. This would result in O(n! ร— n!) time complexity, which is computationally infeasible even for small strings.

Wrong Approach:

from itertools import permutations

def checkIfCanBreak(s1, s2):
    # DON'T DO THIS - Time Limit Exceeded!
    for perm1 in permutations(s1):
        for perm2 in permutations(s2):
            if all(c1 >= c2 for c1, c2 in zip(perm1, perm2)):
                return True
    return False

Solution: Use the greedy approach by sorting both strings first, which gives the optimal arrangement in O(n log n) time.

2. Forgetting to Check Both Directions

A common mistake is only checking if s1 can break s2 but forgetting to check if s2 can break s1.

Wrong Implementation:

def checkIfCanBreak(s1, s2):
    sorted_s1 = sorted(s1)
    sorted_s2 = sorted(s2)
    # Only checks one direction - INCOMPLETE!
    return all(c1 >= c2 for c1, c2 in zip(sorted_s1, sorted_s2))

Solution: Always check both conditions with an OR operation:

return all(c1 >= c2 for c1, c2 in zip(sorted_s1, sorted_s2)) or \
       all(c1 <= c2 for c1, c2 in zip(sorted_s1, sorted_s2))

3. Misunderstanding the Breaking Condition

Some might think that "breaking" means strictly greater (>) instead of greater than or equal to (>=).

Wrong Comparison:

# Using strict inequality - WRONG!
s1_breaks_s2 = all(c1 > c2 for c1, c2 in zip(sorted_s1, sorted_s2))

Solution: Use >= for comparisons, as equal characters are allowed in the breaking condition.

4. Not Handling Edge Cases Properly

While the problem guarantees equal-length strings, in practice you might want to validate inputs.

Better Implementation with Validation:

def checkIfCanBreak(s1, s2):
    if len(s1) != len(s2):
        return False  # Or raise an exception
  
    sorted_s1 = sorted(s1)
    sorted_s2 = sorted(s2)
  
    return all(c1 >= c2 for c1, c2 in zip(sorted_s1, sorted_s2)) or \
           all(c1 <= c2 for c1, c2 in zip(sorted_s1, sorted_s2))
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More