title: Check If a String Can Break Another String
slug: check-if-a-string-can-break-another-string
difficulty: medium
leetcode_id: 1433
leetcode_url: https://leetcode.com/problems/check-if-a-string-can-break-another-string/
categories:
  - strings
  - sorting
patterns:
  - slug: greedy
    is_optimal: true

function_signature: "def check_if_can_break(s1: str, s2: str) -> bool:"

test_cases:
  visible:
    - input: { s1: "abc", s2: "xya" }
      expected: true
    - input: { s1: "abe", s2: "acd" }
      expected: false
    - input: { s1: "leetcodee", s2: "interview" }
      expected: true
  hidden:
    - input: { s1: "aaa", s2: "aaa" }
      expected: true
    - input: { s1: "a", s2: "z" }
      expected: true
    - input: { s1: "zzz", s2: "aaa" }
      expected: true
    - input: { s1: "ab", s2: "ba" }
      expected: true
    - input: { s1: "acb", s2: "bca" }
      expected: true
    - input: { s1: "xyz", s2: "abc" }
      expected: true

description: |
  Given two strings: `s1` and `s2` with the same size, check if some permutation of string `s1` can break some permutation of string `s2` or vice-versa. In other words `s2` can break `s1` or vice-versa.

  A string `x` can *break* string `y` (both of size `n`) if `x[i] >= y[i]` (in alphabetical order) for all `i` between `0` and `n-1`.

constraints: |
  - `s1.length == n`
  - `s2.length == n`
  - `1 <= n <= 10^5`
  - All strings consist of lowercase English letters.

examples:
  - input: 's1 = "abc", s2 = "xya"'
    output: "true"
    explanation: '"ayx" is a permutation of s2="xya" which can break string "abc" which is a permutation of s1="abc".'
  - input: 's1 = "abe", s2 = "acd"'
    output: "false"
    explanation: 'All permutations for s1="abe" are: "abe", "aeb", "bae", "bea", "eab" and "eba" and all permutations for s2="acd" are: "acd", "adc", "cad", "cda", "dac" and "dca". However, there is no permutation from s1 which can break some permutation from s2 and vice-versa.'
  - input: 's1 = "leetcodee", s2 = "interview"'
    output: "true"
    explanation: "After sorting both strings, we can check that one can break the other."

explanation:
  intuition: |
    Think of this problem like a head-to-head competition between two teams of characters.

    Each character in one string needs to "beat" (or at least tie with) the corresponding character in the other string. The key insight is that we have the freedom to **rearrange both strings** in any order we like before comparing.

    Imagine you're a coach arranging a lineup for a match. You want to pair your strongest players against their weakest, your second-strongest against their second-weakest, and so on. This is exactly what sorting achieves: it aligns the "strengths" (alphabetical values) of characters in an optimal way.

    If we sort both strings, we get the best possible matchup for determining if one can break the other. After sorting, if string `x` can break string `y`, it means character-by-character from the smallest to the largest, `x` always has an equal or greater character. If neither direction works after sorting, then no permutation will work.

  approach: |
    We solve this using a **Sort and Compare** approach:

    **Step 1: Sort both strings**

    - Convert each string to a list of characters and sort them
    - This arranges characters from smallest ('a') to largest ('z')
    - Sorting gives us the optimal alignment for comparison

    &nbsp;

    **Step 2: Check if s1 can break s2**

    - Compare characters position by position
    - For s1 to break s2, every character in sorted s1 must be >= the corresponding character in sorted s2
    - Track this with a simple loop and a boolean flag

    &nbsp;

    **Step 3: Check if s2 can break s1**

    - Similarly, check if every character in sorted s2 is >= the corresponding character in sorted s1
    - We can do both checks in a single pass by tracking two conditions

    &nbsp;

    **Step 4: Return result**

    - Return `True` if either direction works (s1 breaks s2 OR s2 breaks s1)
    - Return `False` only if neither direction is possible

    &nbsp;

    The greedy insight is that sorting gives the optimal pairing. If any permutation allows breaking, the sorted permutation will also allow it.

  common_pitfalls:
    - title: Checking All Permutations
      description: |
        A brute-force approach would generate all permutations of both strings and check every pair. With `n` characters, there are `n!` permutations for each string, leading to O(n! * n!) comparisons.

        For `n = 10`, this is already `3,628,800 * 3,628,800` pairs. With `n <= 10^5`, this is completely infeasible.

        The key insight is that sorting both strings gives the optimal comparison, so we only need to check sorted strings.
      wrong_approach: "Enumerate all n! permutations of each string"
      correct_approach: "Sort both strings once and compare directly"

    - title: Checking Only One Direction
      description: |
        The problem asks if s1 can break s2 **OR** s2 can break s1. A common mistake is only checking one direction and returning `False` prematurely.

        For example, with `s1 = "abc"` and `s2 = "xya"`, s1 cannot break s2 (after sorting: "abc" vs "axy", and 'b' < 'x'), but s2 CAN break s1 ("axy" >= "abc" at every position).

        Always check both directions before returning `False`.
      wrong_approach: "Check only if s1 breaks s2"
      correct_approach: "Check both directions and return True if either works"

    - title: Forgetting Equality Is Allowed
      description: |
        The definition states `x[i] >= y[i]`, not `x[i] > y[i]`. Equal characters satisfy the breaking condition.

        For `s1 = "aaa"` and `s2 = "aaa"`, both strings can break each other since all positions have equal characters.

        Make sure to use `>=` in your comparisons, not `>`.

  key_takeaways:
    - "**Sorting optimises permutation problems**: When you can rearrange elements freely, sorting often reveals the optimal configuration"
    - "**Greedy alignment**: Matching smallest-to-smallest and largest-to-largest gives the best chance for one sequence to dominate another"
    - "**Check both directions**: Bidirectional conditions require testing both possibilities before concluding failure"
    - "**Reduce complexity through insight**: This problem appears exponential (permutations) but is actually O(n log n) with the sorting insight"

  time_complexity: "O(n log n). Sorting both strings dominates the runtime, where `n` is the string length. The comparison pass is O(n)."
  space_complexity: "O(n). We need to store the sorted versions of both strings. In Python, sorting a string requires converting to a list first."

solutions:
  - approach_name: Sort and Compare
    is_optimal: true
    code: |
      def check_if_can_break(s1: str, s2: str) -> bool:
          # Sort both strings to get optimal character alignment
          sorted_s1 = sorted(s1)
          sorted_s2 = sorted(s2)

          # Track if s1 can break s2 and if s2 can break s1
          s1_breaks_s2 = True
          s2_breaks_s1 = True

          # Compare character by character
          for c1, c2 in zip(sorted_s1, sorted_s2):
              # If any position fails, that direction can't break
              if c1 < c2:
                  s1_breaks_s2 = False
              if c2 < c1:
                  s2_breaks_s1 = False

              # Early exit if neither direction is possible
              if not s1_breaks_s2 and not s2_breaks_s1:
                  return False

          # Return True if either direction works
          return s1_breaks_s2 or s2_breaks_s1
    explanation: |
      **Time Complexity:** O(n log n) — Sorting dominates; the comparison loop is O(n).

      **Space Complexity:** O(n) — Storage for sorted character lists.

      We sort both strings and compare them position by position. By sorting, we ensure the smallest characters are compared first, which is the optimal alignment for determining if one string can break another. We check both directions simultaneously and return early if we find that neither is possible.

  - approach_name: Counting Sort Optimisation
    is_optimal: true
    code: |
      def check_if_can_break(s1: str, s2: str) -> bool:
          # Count character frequencies (26 lowercase letters)
          count1 = [0] * 26
          count2 = [0] * 26

          for c in s1:
              count1[ord(c) - ord('a')] += 1
          for c in s2:
              count2[ord(c) - ord('a')] += 1

          # Build sorted strings from counts
          sorted_s1 = []
          sorted_s2 = []
          for i in range(26):
              sorted_s1.extend([chr(i + ord('a'))] * count1[i])
              sorted_s2.extend([chr(i + ord('a'))] * count2[i])

          # Check both directions
          s1_breaks_s2 = all(c1 >= c2 for c1, c2 in zip(sorted_s1, sorted_s2))
          s2_breaks_s1 = all(c2 >= c1 for c1, c2 in zip(sorted_s1, sorted_s2))

          return s1_breaks_s2 or s2_breaks_s1
    explanation: |
      **Time Complexity:** O(n) — Counting and building sorted arrays is linear.

      **Space Complexity:** O(n) — Storage for sorted character arrays.

      Since we're dealing with lowercase English letters only (26 characters), we can use counting sort for O(n) time instead of O(n log n). We count the frequency of each character, then reconstruct sorted strings from the counts. This is faster for large inputs but the difference is often negligible in practice.
