Problem Statement in English
You’re given two strings s1 and s2 of the same length. We say that s2 is a scrambled string of s1 if we can transform s1 into s2 using a series of recursive operations.
A recursive operation is governed by the following rules:
- If the string is empty or has only one character, it is considered a scrambled string of itself.
- If the string has more than one character, we can split it into two non-empty substrings at any index
i(1 <= i < length of the string). This results in two substrings:s1[0:i]ands1[i:]. - The strings can either be recombined in the same order or swapped. That is, we can form two new strings:
s1[0:i] + s1[i:]ors1[i:] + s1[0:i]. - We can then recursively apply the same operation to each of the two substrings.
Return true if s2 is a scrambled string of s1, otherwise return false.
Approach
There’s no smart way to do this. You just have to simulate the process of scrambling and check if s2 can be obtained from s1. The recursive approach is straightforward, but we can optimize it using memoization to avoid redundant calculations.
For each index in the string, we can split s1 into two parts and check both possible combinations (with and without swapping) to see if they can form s2. If any combination works, we return true. If none work, we return false.
We can recursively check all possible splits and combinations, and use memoization to store results of previously computed substrings to improve efficiency.
The reason there are no loops is because in each step the size of the string is reduced, and we are only checking the two possible combinations for each split.
And we’re done!
Solution in Python
class Solution:
@cache
def isScramble(self, s1: str, s2: str) -> bool:
# Base Cases
if s1 == s2:
return True
if len(s1) != len(s2):
return False
# Optimization: Early exit if character counts don't match
if sorted(s1) != sorted(s2):
return False
n = len(s1)
for i in range(1, n):
# Case 1: Without swapping subtrees
if self.isScramble(s1[:i], s2[:i]) and self.isScramble(s1[i:], s2[i:]):
return True
# Case 2: With swapping subtrees
if self.isScramble(s1[:i], s2[n - i:]) and self.isScramble(s1[i:], s2[:n - i]):
return True
return False
Complexity
Time: $O(n^5)$
This is because there are $2$ strings, each with $n - k + 1$ substrings. Further, $k$ ranges from $1$ to $n - 1$. Upon squaring, we get $n^3$ states.In each state we use a nested loop in order to slice it and sort the substrings. Here the nested loop dominates the time complexity.
Thus the overall time complexity is $O(n^5)$.
Space: $O(n^4)$
We established that there are $n^3$ states. Each state may have upto $n$ characters, thus giving us a space complexity of $O(n^4)$.
Mistakes I Made
I dismissed dynamic programming because I thought that there was no way to get past the cyclical nature of the problem. I also bungled up the time and space complexity analysis.
And we are done.