Check If a String Can Break Another String
MedPrompt
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.
Example 1:
Input: s1 = "abc", s2 = "xya"
Output: true
Explanation: "ayx" is a permutation of s2="xya" which can break to string "abc" which is a permutation of s1="abc".Example 2:
Input: s1 = "abe", s2 = "acd"
Output: false
Explanation: All permutations for s1="abe" are: "abe", "aeb", "bae", "bea", "eab" and "eba" and all permutation for s2="acd" are: "acd", "adc", "cad", "cda", "dac" and "dca". However, there is not any permutation from s1 which can break some permutation from s2 and vice-versa.Example 3:
Input: s1 = "leetcodee", s2 = "interview"
Output: true
Constraints:
s1.length == ns2.length == n1 <= n <= 10^5- All strings consist of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach literally translates the problem statement into code. It generates every possible permutation of s1 and s2 and checks if any pair of permutations satisfies the "break" condition. This method is used to establish a baseline and understand the full scope of the problem before optimizing.
Algorithm
-
- Generate all unique permutations of string
s1. Store them in a listperms1.
- Generate all unique permutations of string
-
- Generate all unique permutations of string
s2. Store them in a listperms2.
- Generate all unique permutations of string
-
- Iterate through each permutation
p1inperms1.
- Iterate through each permutation
-
- Inside this loop, iterate through each permutation
p2inperms2.
- Inside this loop, iterate through each permutation
-
- Check if
p1can breakp2. This is done by comparing characters at each indexi:p1[i] >= p2[i].
- Check if
-
- If
p1breaksp2, returntrue.
- If
-
- Check if
p2can breakp1. This is done by comparing characters at each indexi:p2[i] >= p1[i].
- Check if
-
- If
p2breaksp1, returntrue.
- If
-
- If the loops complete without finding a valid pair, return
false.
- If the loops complete without finding a valid pair, return
Walkthrough
The core idea is to explore all (n!) * (n!) pairs of permutations. We can write a recursive helper function, say generatePermutations(string), that returns a list of all its permutations. We would call this function for both s1 and s2. Then, we use nested loops to iterate through every permutation p1 from s1's list and every permutation p2 from s2's list. Inside the loops, we check two conditions: does p1 break p2? and does p2 break p1? If either condition is met for any pair, we immediately return true. If we check all pairs and find no such case, we return false. This approach is computationally very expensive and is not feasible for the given constraints, but it demonstrates a direct, albeit naive, understanding of the problem.
// This is a conceptual illustration and will Time Limit Exceed.class Solution { public boolean checkIfCanBreak(String s1, String s2) { // This is not a practical solution due to N! complexity. // The generation of permutations is complex and omitted for brevity. // List<String> perms1 = generatePermutations(s1); // List<String> perms2 = generatePermutations(s2); // // for (String p1 : perms1) { // for (String p2 : perms2) { // if (canBreak(p1, p2) || canBreak(p2, p1)) { // return true; // } // } // } return false; // Placeholder for the logic } private boolean canBreak(String a, String b) { for (int i = 0; i < a.length(); i++) { if (a.charAt(i) < b.charAt(i)) { return false; } } return true; }}Complexity
Time
O(n * (n!)^2). Generating permutations is `O(n * n!)`. We do this twice. Then we have a nested loop of size `n!` x `n!`, with an `O(n)` comparison inside.
Space
O(n * n!) to store all the permutations.
Trade-offs
Pros
Conceptually simple and directly follows the problem definition.
Cons
Extremely inefficient with a time complexity of O(n * (n!)^2), making it infeasible for n > 10.
High memory usage to store all permutations.
A key insight is that if any permutation of s1 can break a permutation of s2, then the alphabetically sorted version of s1 must be able to break the alphabetically sorted version of s2. This simplifies the problem from checking (n!)² pairs to checking just one pair of sorted strings.
Algorithm
-
- Convert
s1into a character arrayarr1.
- Convert
-
- Convert
s2into a character arrayarr2.
- Convert
-
- Sort
arr1in non-decreasing order.
- Sort
-
- Sort
arr2in non-decreasing order.
- Sort
-
- Define a helper function
canBreak(a, b)that checks if stringacan break stringb. It iterates fromi = 0ton-1and returnsfalseifa[i] < b[i]for anyi. If the loop completes, it returnstrue.
- Define a helper function
-
- Return the result of
canBreak(arr1, arr2) || canBreak(arr2, arr1).
- Return the result of
Walkthrough
The problem asks if there exists any permutation. To make the condition x[i] >= y[i] as easy to satisfy as possible, we should pair the smallest characters of one string with the smallest of the other, the second smallest with the second smallest, and so on. This is achieved by sorting both strings. Let sorted_s1 be the string s1 with its characters sorted alphabetically, and sorted_s2 be the sorted version of s2. The problem then reduces to checking two conditions:
- Can
s1breaks2? We check ifsorted_s1[i] >= sorted_s2[i]for alli. - Can
s2breaks1? We check ifsorted_s2[i] >= sorted_s1[i]for alli. If either of these conditions holds true, the function returnstrue. Otherwise, it returnsfalse. The implementation involves converting the strings to character arrays, sorting them, and then iterating through them to perform the checks.
import java.util.Arrays; class Solution { public boolean checkIfCanBreak(String s1, String s2) { char[] arr1 = s1.toCharArray(); char[] arr2 = s2.toCharArray(); Arrays.sort(arr1); Arrays.sort(arr2); return canBreak(arr1, arr2) || canBreak(arr2, arr1); } private boolean canBreak(char[] arrA, char[] arrB) { for (int i = 0; i < arrA.length; i++) { if (arrA[i] < arrB[i]) { return false; } } return true; }}Complexity
Time
O(n log n). The dominant operation is sorting the two character arrays of length `n`. The final linear scans take `O(n)`.
Space
O(n). In Java, `toCharArray()` creates new arrays of size `n`. Some sorting algorithms might also use space (e.g., `O(log n)` for quicksort stack, `O(n)` for mergesort).
Trade-offs
Pros
Drastically more efficient than the brute-force approach.
Relatively simple to implement using standard library sorting functions.
Cons
The time complexity is dominated by sorting, which is O(n log n). This can be improved upon.
Requires O(n) extra space for the character arrays in Java.
This is the most efficient approach, leveraging the fact that the input strings consist of a limited character set (lowercase English letters). It uses frequency arrays and a single pass to check the break conditions in linear time, avoiding the O(n log n) cost of sorting.
Algorithm
-
- Initialize two integer arrays,
count1andcount2, of size 26 to all zeros.
- Initialize two integer arrays,
-
- Iterate through
s1ands2to populate the frequency counts. For each charactercins1, incrementcount1[c - 'a']. Do similarly fors2andcount2.
- Iterate through
-
- Initialize two boolean flags,
s1_breaks_s2 = trueands2_breaks_s1 = true.
- Initialize two boolean flags,
-
- Initialize two integer variables for prefix sums,
s1_sum = 0ands2_sum = 0.
- Initialize two integer variables for prefix sums,
-
- Loop from
i = 0to 25 (representing characters 'a' to 'z').
- Loop from
-
- In each iteration, update the prefix sums:
s1_sum += count1[i]ands2_sum += count2[i].
- In each iteration, update the prefix sums:
-
- Check the break conditions using the prefix sums:
- If
s1_sum > s2_sum, thens2cannot breaks1. Sets2_breaks_s1 = false. - If
s2_sum > s1_sum, thens1cannot breaks2. Sets1_breaks_s2 = false.
-
- After the loop, return
s1_breaks_s2 || s2_breaks_s1.
- After the loop, return
Walkthrough
The core insight is that s2 can break s1 if and only if for every character c from 'a' to 'z', the count of characters in s1 less than or equal to c is less than or equal to the count of characters in s2 less than or equal to c. A similar condition holds for s1 breaking s2. This can be checked efficiently using prefix sums of character frequency counts. The algorithm is as follows:
- Create two frequency arrays,
count1andcount2, of size 26. - Populate these arrays by iterating through
s1ands2, which takesO(n)time. - We then check two possibilities simultaneously: if
s1can breaks2, and ifs2can breaks1. - We iterate from
i = 0to 25 (for 'a' to 'z'), maintaining running prefix sums of the counts. Let these bes1_sumands2_sum. - In each iteration, if
s1_sumbecomes greater thans2_sum, it violates the condition fors2breakings1. Ifs2_sumbecomes greater thans1_sum, it violates the condition fors1breakings2. - If after checking all characters, at least one of the two "break" possibilities remains true, we return
true. Otherwise,false.
class Solution { public boolean checkIfCanBreak(String s1, String s2) { int n = s1.length(); int[] count1 = new int[26]; int[] count2 = new int[26]; for (int i = 0; i < n; i++) { count1[s1.charAt(i) - 'a']++; count2[s2.charAt(i) - 'a']++; } boolean s1BreaksS2 = true; boolean s2BreaksS1 = true; int s1PrefixSum = 0; int s2PrefixSum = 0; for (int i = 0; i < 26; i++) { s1PrefixSum += count1[i]; s2PrefixSum += count2[i]; // Condition for s2 to break s1 is prefix_sum(s1) <= prefix_sum(s2) if (s1PrefixSum > s2PrefixSum) { s2BreaksS1 = false; } // Condition for s1 to break s2 is prefix_sum(s2) <= prefix_sum(s1) if (s2PrefixSum > s1PrefixSum) { s1BreaksS2 = false; } } return s1BreaksS2 || s2BreaksS1; }}Complexity
Time
O(n + k), where `n` is the length of the strings and `k` is the alphabet size (26). Since `k` is constant, this simplifies to `O(n)`.
Space
O(k), where `k` is the alphabet size (26). This is constant space, so `O(1)`.
Trade-offs
Pros
Optimal time complexity of O(n) as it only requires a few linear passes.
Constant space complexity O(1) as the frequency arrays are of fixed size (26).
Most efficient solution for the given constraints.
Cons
The logic involving prefix sums is slightly less intuitive than direct sorting.
This approach is specialized for inputs with a small, fixed character set.
Solutions
Solution
class Solution {public boolean checkIfCanBreak(String s1, String s2) { char[] cs1 = s1.toCharArray(); char[] cs2 = s2.toCharArray(); Arrays.sort(cs1); Arrays.sort(cs2); return check(cs1, cs2) || check(cs2, cs1); }private boolean check(char[] cs1, char[] cs2) { for (int i = 0; i < cs1.length; ++i) { if (cs1[i] < cs2[i]) { return false; } } return true; }}class Solution {public: bool checkIfCanBreak(string s1, string s2) { sort(s1.begin(), s1.end()); sort(s2.begin(), s2.end()); return check(s1, s2) || check(s2, s1); } bool check(string &s1, string &s2) { for (int i = 0; i < s1.size(); ++i) { if (s1[i] < s2[i]) { return false; } } return true; }};class Solution: def checkIfCanBreak(self, s1: str, s2: str) -> bool: cs1 = sorted(s1) cs2 = sorted(s2) return all(a >= b for a, b in zip(cs1, cs2)) or all(a <= b for a, b in zip(cs1, cs2))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.