Using greedy without proof
Wrong move: Locally optimal choices may fail globally.
Usually fails on: Counterexamples appear on crafted input orderings.
Fix: Verify with exchange argument or monotonic objective before committing.
Move from brute-force thinking to an efficient approach using greedy strategy.
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^5Problem summary: 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.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Greedy
"abc" "xya"
"abe" "acd"
"leetcodee" "interview"
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1433: Check If a String Can Break Another String
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;
}
}
// Accepted solution for LeetCode #1433: Check If a String Can Break Another String
func checkIfCanBreak(s1 string, s2 string) bool {
cs1 := []byte(s1)
cs2 := []byte(s2)
sort.Slice(cs1, func(i, j int) bool { return cs1[i] < cs1[j] })
sort.Slice(cs2, func(i, j int) bool { return cs2[i] < cs2[j] })
check := func(cs1, cs2 []byte) bool {
for i := range cs1 {
if cs1[i] < cs2[i] {
return false
}
}
return true
}
return check(cs1, cs2) || check(cs2, cs1)
}
# Accepted solution for LeetCode #1433: Check If a String Can Break Another String
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)
)
// Accepted solution for LeetCode #1433: Check If a String Can Break Another String
struct Solution;
impl Solution {
fn check_if_can_break(s1: String, s2: String) -> bool {
let n = s1.len();
let mut s1: Vec<char> = s1.chars().collect();
let mut s2: Vec<char> = s2.chars().collect();
s1.sort_unstable();
s2.sort_unstable();
let mut sum1 = 0;
let mut sum2 = 0;
for i in 0..n {
if s1[i] <= s2[i] {
sum1 += 1;
}
if s1[i] >= s2[i] {
sum2 += 1;
}
}
sum1 == n || sum2 == n
}
}
#[test]
fn test() {
let s1 = "abc".to_string();
let s2 = "xya".to_string();
let res = true;
assert_eq!(Solution::check_if_can_break(s1, s2), res);
let s1 = "abe".to_string();
let s2 = "acd".to_string();
let res = false;
assert_eq!(Solution::check_if_can_break(s1, s2), res);
let s1 = "leetcodee".to_string();
let s2 = "interview".to_string();
let res = true;
assert_eq!(Solution::check_if_can_break(s1, s2), res);
}
// Accepted solution for LeetCode #1433: Check If a String Can Break Another String
function checkIfCanBreak(s1: string, s2: string): boolean {
const cs1: string[] = Array.from(s1);
const cs2: string[] = Array.from(s2);
cs1.sort();
cs2.sort();
const check = (cs1: string[], cs2: string[]) => {
for (let i = 0; i < cs1.length; i++) {
if (cs1[i] < cs2[i]) {
return false;
}
}
return true;
};
return check(cs1, cs2) || check(cs2, cs1);
}
Use this to step through a reusable interview workflow for this problem.
Try every possible combination of choices. With n items each having two states (include/exclude), the search space is 2ⁿ. Evaluating each combination takes O(n), giving O(n × 2ⁿ). The recursion stack or subset storage uses O(n) space.
Greedy algorithms typically sort the input (O(n log n)) then make a single pass (O(n)). The sort dominates. If the input is already sorted or the greedy choice can be computed without sorting, time drops to O(n). Proving greedy correctness (exchange argument) is harder than the implementation.
Review these before coding to avoid predictable interview regressions.
Wrong move: Locally optimal choices may fail globally.
Usually fails on: Counterexamples appear on crafted input orderings.
Fix: Verify with exchange argument or monotonic objective before committing.