Leetcode 1433. Check If a String Can Break Another String

Problem Explanation:

The problem poses a question that involves comparing two strings based on their alphabetical order. The two strings, s1 and s2 of equal length, are evaluated to determine if s1 has a permutation which alphabetically "breaks" that of s2 or vice versa. The term "break" in this context refers to a case where all characters in string x (a permutation of s1) have an alphabetical order greater than or equal to respective characters in string y (a permutation of s2), when both strings are compared character by character from index 0 to n-1.

To illustrate with an example, consider the strings s1 = "abc" and s2 = "xya":

  • The alphabetically sorted permutation of s2 is "ayx", which can "break" the given string s1= "abc".

In contrast, if s1 = "abe" and s2 = "acd":

  • No permutation of s1 can break any of the permutations of s2 and vice versa.

The solution involves evaluating all possible permutations of the two strings to determine if any pair satisfies the break condition.

Solution Approach:

The algorithm works by creating two count arrays that keep track of the frequency of each character in both strings. We then check if the count of each character in first string is greater than or equal to the count of the same character in the other string. If true for all characters, it implies that one string can break the other. We repeat the process the other way around to check both possibilities: s1 breaking s2 or s2 breaking s1.

For example, let's take s1 = "abe" and s2 = "acd":

  • s1 -> count1 = {1, 1, 0, 0, 1}
  • s2 -> count2 = {1, 0, 1, 1, 0}
  • s1 cannot break s2 and s2 cannot break s1.

The underlying algorithmic pattern is frequency counting.

Python Solution:

1
2python
3from collections import Counter
4class Solution:
5  def checkIfCanBreak(self, s1: str, s2: str) -> bool:
6    # Get Frequency Counters of s1 and s2
7    count1 = Counter(s1)
8    count2 = Counter(s2)
9
10    # Combine keys() of both counters and sort
11    keys = sorted(count1.keys() | count2.keys())
12    diff1, diff2 = 0, 0
13
14    for k in keys:
15      diff1 += count1[k]
16      diff2 += count2[k]
17
18      if diff1 < diff2 or diff2 < diff1:
19        return False
20    return True

Java Solution:

1
2java
3import java.util.*;
4class Solution {
5  public boolean checkIfCanBreak(String s1, String s2) {
6    int[] count1 = new int[26];
7    int[] count2 = new int[26];
8
9    for (char c : s1.toCharArray())
10      count1[c - 'a']++;
11
12    for (char c : s2.toCharArray())
13      count2[c - 'a']++;
14
15    return canBreak(s1, count1, count2) || canBreak(s2, count2, count1);
16  }
17
18  private boolean canBreak(String s, int[] count1, int[] count2) {
19    int cumulative1 = 0, cumulative2 = 0;
20    for (int i = 0; i < 26; i++) {
21      cumulative1 += count1[i];
22      cumulative2 += count2[i];
23      if (cumulative1 > cumulative2)
24        return false;
25    }
26    return true;
27  }
28}

C++ Solution:

1
2cpp
3#include <vector>
4
5class Solution {
6public:
7  bool checkIfCanBreak(string s1, string s2) {
8    vector<int> count1(26), count2(26);
9    for (char c : s1)
10      count1[c - 'a']++;
11    for (char c : s2)
12      count2[c - 'a']++;
13    return canBreak(count1, count2) || canBreak(count2, count1);
14  }
15
16private:
17  bool canBreak(vector<int>& count1, vector<int>& count2) {
18    int sum1 = 0, sum2 = 0;
19    for (int i = 0; i < 26; ++i) {
20      sum1 += count1[i];
21      sum2 += count2[i];
22      if (sum1 > sum2)
23        return false;
24    }
25    return true;
26  }
27};

C# Solution:

1
2csharp
3public class Solution {
4  public bool CheckIfCanBreak(string s1, string s2) {
5    int[] count1 = new int[26], count2 = new int[26];
6    for (int i = 0; i < s1.Length; ++i) {
7      ++count1[s1[i] - 'a'];
8      ++count2[s2[i] - 'a'];
9    }
10    return canBreak(count1, count2) || canBreak(count2, count1);
11  }
12
13  private bool canBreak(int[] c1, int[] c2) {
14    for (int i1 = 0, i2 = 0; i1 < 26; ++i1) {
15      while (c1[i1]-- > 0) {
16        while (c2[i2] == 0) ++i2;
17        if (i1 > i2++) return false;
18      }
19    }
20    return true;
21  }
22}

JavaScript Solution:

1
2javascript
3class Solution {
4  checkIfCanBreak(s1, s2) {
5    let count1 = Array(26).fill(0), count2 = Array(26).fill(0);
6    
7    for(let i=0;i<s1.length;i++) {
8      count1[s1.charCodeAt(i) - 'a'.charCodeAt(0)]++;
9      count2[s2.charCodeAt(i) - 'a'.charCodeAt(0)]++;
10    }
11    
12    return this.canBreak(count1, count2) || this.canBreak(count2, count1);
13  }
14
15  canBreak(count1, count2) {
16    let diff = 0;
17    
18    for(let i=0;i<26;i++) {
19      diff += count2[i] - count1[i];
20      if(diff < 0)
21        return false;
22    }
23    return true;
24  }
25};

Explanation of Solution:

The above solutions for Python, Java, JavaScript, C++ and C# share the same basic looping algorithm to find the frequency count of each character in both strings. They then check whether one count is greater than or equal to the other in sequential order through a method called canBreak(). If it is, then it returns True or false depending on the language syntax. If it's not, it reverts the process by inverting the two frequency counts and checks again to ensure that the other string has no breaking permutation.

Since the running time of each solution is linear, the algorithm is highly efficient as it only requires a maximum of O(2n) operations. This ensures that the algorithm performs optimally even for long sequences.

In Python, we use collections.Counter to generate a counter dictionary that tracks the frequency of each character. We then sequentially compare the cumulative counts of the two strings. If either counter overshadows the other at any given point, we terminate and return False. If the counters go through the entire process without interruption, we return True.

In Java, the "canBreak" function works by creating count arrays for both strings, and iterating through each letter's frequency count. If the cumulative sum for one string is ever higher than that of the other, the function returns false, demonstrating that one string cannot 'break' the other.

In JavaScript, the "canBreak" function works using the cumulative difference rather than the cumulative count of characters, but the logic is essentially the same. It iterates from 'a' to 'z' and checks whether the cumulative difference is ever less than zero i.e., any character is more frequent in the second string. If so, it returns false.

The C++ and C# solutions are similar to Java and JavaScript, replacing strings with vectors and arrays respectively but keeping the basic logic intact.

Conclusively, these solutions efficiently determine whether any permutation of one string can break another by assessing character frequency counts and cumulative sums, providing a robust solution to the problem across multiple programming languages.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫