Description:
We can scramble a string s to get a string t using the following algorithm:
- If the length of the string is 1, stop.
- If the length of the string is > 1, do the following:
- Split the string into two non-empty substrings at a random index, i.e., if the string is
s
, divide it tox
andy
wheres = x + y
. - Randomly decide to swap the two substrings or to keep them in the same order. i.e., after this step,
s
may becomes = x + y
ors = y + x
. - Apply step 1 recursively on each of the two substrings
x
andy
.
- Split the string into two non-empty substrings at a random index, i.e., if the string is
Given two strings s1
and s2
of the same length, return true
if s2
is a scrambled string of s1
, otherwise, return false
.
Examples:
Example 1:
Input: s1 = "great", s2 = "rgeat"
Output: true
Explanation: One possible scenario applied on s1 is:
"great" --> "gr/eat" // divide at random index.
"gr/eat" --> "gr/eat" // random decision is not to swap the two substrings and keep them in order.
"gr/eat" --> "g/r / e/at" // apply the same algorithm recursively on both substrings. divide at random index each of them.
"g/r / e/at" --> "r/g / e/at" // random decision was to swap the first substring and to keep the second substring in the same order.
"r/g / e/at" --> "r/g / e/ a/t" // again apply the algorithm recursively, divide "at" to "a/t".
"r/g / e/ a/t" --> "r/g / e/ a/t" // random decision is to keep both substrings in the same order.
The algorithm stops now, and the result string is "rgeat" which is s2.
As one possible scenario led s1 to be scrambled to s2, we return true.
Example 2:
Input: s1 = "abcde", s2 = "caebd"
Output: false
Example 3:
Input: s1 = "a", s2 = "a"
Output: true
Solution in Python:
Python
class Solution:
def isScramble(self, s1: str, s2: str) -> bool:
# A memoization dictionary to store results of subproblems
memo = {}
# Helper function that performs the recursive check
def recursive_check(s1, s2):
# If both strings are equal, they are trivially scramble strings
if s1 == s2:
return True
# If the lengths are different, they cannot be scramble strings
if len(s1) != len(s2):
return False
# If the sorted characters of both strings are not equal,
# they cannot be scramble strings
if sorted(s1) != sorted(s2):
return False
# If the pair (s1, s2) is already computed, return its result
if (s1, s2) in memo:
return memo[(s1, s2)]
n = len(s1)
for i in range(1, n):
# Check if there's a valid split that makes s1 and s2 scramble strings
if (recursive_check(s1[:i], s2[:i]) and recursive_check(s1[i:], s2[i:])) or \
(recursive_check(s1[:i], s2[-i:]) and recursive_check(s1[i:], s2[:-i])):
memo[(s1, s2)] = True
return True
memo[(s1, s2)] = False
return False
# Initial call to the helper function with the full strings
return recursive_check(s1, s2)
Explanation:
- Memoization Dictionary:
- We use a dictionary
memo
to store the results of subproblems. This helps in avoiding recomputation of the same subproblem multiple times.
- We use a dictionary
- Recursive Helper Function:
- The function
recursive_check
takes two strings,s1
ands2
, and checks ifs2
is a scrambled version ofs1
.
- The function
- Base Cases:
- If
s1
is equal tos2
, then they are trivially scrambled strings. - If the lengths of
s1
ands2
are different, they cannot be scrambled versions of each other. - If the sorted characters of
s1
ands2
are not the same, they cannot be scrambled versions.
- If
- Memoization Check:
- Before performing the checks, we see if the result for the pair
(s1, s2)
is already computed and stored in the memo dictionary.
- Before performing the checks, we see if the result for the pair
- Recursive Splitting and Checking:
- We try splitting the string
s1
at every possible indexi
(from 1 ton-1
) and check both scenarios:- The first
i
characters ofs1
with the firsti
characters ofs2
and the remaining with the remaining. - The first
i
characters ofs1
with the lasti
characters ofs2
and the remaining with the remaining.
- The first
- If any of these splits result in
s2
being a scrambled version ofs1
, we returnTrue
.
- We try splitting the string
- Storing the Result:
- If no valid splits are found, we store
False
in the memo dictionary for the pair(s1, s2)
.
- If no valid splits are found, we store
- Initial Call:
- The function
isScramble
initiates the process by callingrecursive_check
with the full stringss1
ands2
.
- The function
This solution efficiently handles the problem constraints and ensures that redundant computations are avoided through memoization.
Solution in Javascript:
JavaScript
/**
* @param {string} s1
* @param {string} s2
* @return {boolean}
*/
var isScramble = function(s1, s2) {
// A memoization object to store results of subproblems
const memo = {};
/**
* Helper function that performs the recursive check
* @param {string} s1 - first string
* @param {string} s2 - second string
* @return {boolean} - whether s2 is a scrambled string of s1
*/
function recursiveCheck(s1, s2) {
// If both strings are equal, they are trivially scramble strings
if (s1 === s2) {
return true;
}
// If the lengths are different, they cannot be scramble strings
if (s1.length !== s2.length) {
return false;
}
// If the sorted characters of both strings are not equal,
// they cannot be scramble strings
if (s1.split('').sort().join('') !== s2.split('').sort().join('')) {
return false;
}
// If the pair (s1, s2) is already computed, return its result
if (memo.hasOwnProperty(s1 + '#' + s2)) {
return memo[s1 + '#' + s2];
}
const n = s1.length;
for (let i = 1; i < n; i++) {
// Check if there's a valid split that makes s1 and s2 scramble strings
if ((recursiveCheck(s1.substring(0, i), s2.substring(0, i)) &&
recursiveCheck(s1.substring(i), s2.substring(i))) ||
(recursiveCheck(s1.substring(0, i), s2.substring(n - i)) &&
recursiveCheck(s1.substring(i), s2.substring(0, n - i)))) {
memo[s1 + '#' + s2] = true;
return true;
}
}
memo[s1 + '#' + s2] = false;
return false;
}
// Initial call to the helper function with the full strings
return recursiveCheck(s1, s2);
};
Solution in Java:
Java
import java.util.HashMap;
import java.util.Map;
class Solution {
// Memoization map to store results of subproblems
private Map<String, Boolean> memo = new HashMap<>();
/**
* Main method to check if s2 is a scrambled string of s1
* @param s1 - first string
* @param s2 - second string
* @return boolean - whether s2 is a scrambled string of s1
*/
public boolean isScramble(String s1, String s2) {
// Base case: if both strings are equal
if (s1.equals(s2)) {
return true;
}
// Base case: if the lengths are different, they cannot be scramble strings
if (s1.length() != s2.length()) {
return false;
}
// Base case: if sorted characters of both strings are not equal, they cannot be scramble strings
if (!isAnagram(s1, s2)) {
return false;
}
// Check if the result is already computed and stored in memoization map
String key = s1 + "#" + s2;
if (memo.containsKey(key)) {
return memo.get(key);
}
int n = s1.length();
for (int i = 1; i < n; i++) {
// Check if there's a valid split that makes s1 and s2 scramble strings
if ((isScramble(s1.substring(0, i), s2.substring(0, i)) &&
isScramble(s1.substring(i), s2.substring(i))) ||
(isScramble(s1.substring(0, i), s2.substring(n - i)) &&
isScramble(s1.substring(i), s2.substring(0, n - i)))) {
memo.put(key, true);
return true;
}
}
memo.put(key, false);
return false;
}
/**
* Helper method to check if two strings are anagrams
* @param s1 - first string
* @param s2 - second string
* @return boolean - whether s1 and s2 are anagrams
*/
private boolean isAnagram(String s1, String s2) {
if (s1.length() != s2.length()) {
return false;
}
int[] count = new int[26];
for (char c : s1.toCharArray()) {
count[c - 'a']++;
}
for (char c : s2.toCharArray()) {
if (--count[c - 'a'] < 0) {
return false;
}
}
return true;
}
}
Solution in C#:
C#
using System;
using System.Collections.Generic;
public class Solution {
// Memoization dictionary to store results of subproblems
private Dictionary<string, bool> memo = new Dictionary<string, bool>();
/**
* Main method to check if s2 is a scrambled string of s1
* @param s1 - first string
* @param s2 - second string
* @return boolean - whether s2 is a scrambled string of s1
*/
public bool IsScramble(string s1, string s2) {
// Base case: if both strings are equal
if (s1 == s2) {
return true;
}
// Base case: if the lengths are different, they cannot be scramble strings
if (s1.Length != s2.Length) {
return false;
}
// Base case: if sorted characters of both strings are not equal, they cannot be scramble strings
if (!IsAnagram(s1, s2)) {
return false;
}
// Check if the result is already computed and stored in memoization dictionary
string key = s1 + "#" + s2;
if (memo.ContainsKey(key)) {
return memo[key];
}
int n = s1.Length;
for (int i = 1; i < n; i++) {
// Check if there's a valid split that makes s1 and s2 scramble strings
if ((IsScramble(s1.Substring(0, i), s2.Substring(0, i)) &&
IsScramble(s1.Substring(i), s2.Substring(i))) ||
(IsScramble(s1.Substring(0, i), s2.Substring(n - i)) &&
IsScramble(s1.Substring(i), s2.Substring(0, n - i)))) {
memo[key] = true;
return true;
}
}
memo[key] = false;
return false;
}
/**
* Helper method to check if two strings are anagrams
* @param s1 - first string
* @param s2 - second string
* @return boolean - whether s1 and s2 are anagrams
*/
private bool IsAnagram(string s1, string s2) {
if (s1.Length != s2.Length) {
return false;
}
int[] count = new int[26];
foreach (char c in s1) {
count[c - 'a']++;
}
foreach (char c in s2) {
if (--count[c - 'a'] < 0) {
return false;
}
}
return true;
}
}