scramble(字符串s) 算法:
s长度为1时结束。
s可以拆分成2部分x和y,s=x+y,
这两部分可以交换,也可以不交换,即 s = x+y 或 s = y+x.
上面scramble还会递归作用于x 和 y.
给出相同长度的字符串s1, s2, 问s2是否可通过scramble(s1)获得。
思路:
假设s1和s2的长度是n.
在scramble算法中,第1,字符串可以拆分成两部分,
那么可以拆分的点可以在任意下标处,
所以需要遍历s1的所有下标。
假设遍历下标为 i 时,s 拆分成 0 ~ i 和 i+1 ~ n-1两段。所以 i : 0 ~ n-2 (不是n-1 !)
第2,拆分的两部分 可交换可不交换。
假设在 i 处把s1分成两部分: s1[0 ~ i] 和 s1[i+1 ~ n-1] ,
如果不交换,需要进一步scramble s1[0 ~ i] 和 s2[0 ~ i], s1[i+1 ~ n-1] 和 s2[i+1 ~ n-1].
如果交换,需要进一步scramble s1[0 ~ i] 和 s2[i ~ n-1], s1[i+1 ~ n-1] 和 s2[0 ~ i-1].
同时,我们观察到,
s1能scramble成s2的前提是 它们的字母必须是一样的(不管顺序)。
因为scramble只负责拆分和交换,并不会改变字母,
如果字母不一样,则s1 scamble不成s2的.
所以可以用一个长度为26的数组统计每个字母出现的次数,
因为涉及到交换和不交换,
所以分别用一个cnt统计s1[0 ~ i]的字母,
cntF统计s2[0 ~ i]的字母,cntB统计s2[i+1 ~ n-1]的字母。
字母个数相同时,再进行scamble算法。
两个字符串相等时结束。
已经判断过的,用一个map保存起来,再遇到同样的s1和s2, 直接取结果。
注意s必须是拆分成2部分,因此 i < n-1, 而不是 i < n,如果 i = n-1, 会相当于只拆分成一部分(即s1本身,会陷入无限循环)。
class Solution {
HashMap<String, Boolean> map = new HashMap<>();
public boolean isScramble(String s1, String s2) {
String s = s1 + s2;
//相当于DP,记录已经判断过的s1和s2
if(!map.containsKey(s)) map.put(s, helper(s1, s2));
return map.get(s);
}
boolean helper(String s1, String s2) {
if(s1.equals(s2)) return true;
int[] cnt = new int[26]; //s1从前往后字母计数
int[] cntF = new int[26]; //s2从前往后字母计数,cntForward
int[] cntB = new int[26]; //s2从后往前字母计数,cntBackward
for(int i = 0; i < s1.length()-1; i++) { //遍历所有的分割点
int j = s2.length()-1-i;
cnt[s1.charAt(i)-'a']++; //统计s1的0~i段字母个数
cntF[s2.charAt(i)-'a']++; //统计s2的0~i段(前半段)字母个数
cntB[s2.charAt(j)-'a']++; //统计s2的i~n-1段(后半段)字母个数
if(Arrays.equals(cnt, cntF)) { //s2的0~i段与s1的0~i段是否字母个数一致(相当于不swap)
if(isScramble(s1.substring(0,i+1), s2.substring(0,i+1)) &&
isScramble(s1.substring(i+1), s2.substring(i+1))) return true;
}
//不要用else if!不然会漏掉交换后的验证
if(Arrays.equals(cnt, cntB)){ //s2的i~n-1段与s1的0~i段是否字母个数一致(相当于swap)
if(isScramble(s1.substring(0,i+1), s2.substring(j)) &&
isScramble(s1.substring(i+1), s2.substring(0,j))) return true;
}
}
return false;
}
}