Skip to main content

扰乱字符串

题目

使用下面描述的算法可以扰乱字符串 s 得到字符串 t:

  1. 如果字符串的长度为 1, 算法停止
  2. 如果字符串的长度大于 1, 执行下述步骤:
    • 在一个随机下标处将字符串分割成两个非空的子字符串. 即, 如果已知字符串 s, 则可以将其分成两个子字符串 xy, 且满足 s = x + y.
    • 随机决定是要交换两个子字符串还是要保持这两个子字符串的顺序不变. 即, 在执行这一步骤之后, s 可能是 s = x + y 或者 s = y + x.
    • xy 这两个子字符串上继续从步骤一开始递归执行此算法.

给你两个长度相等的字符串 s1s2, 判断 s2 是否是 s1 的扰乱字符串. 如果是, 返回 true; 否则, 返回 false.

提示:
  • s1.length == s2.length
  • 1 <= s1.length <= 30
  • s1s2 由小写英文字母组成
示例
输入: s1 = "great", s2 = "rgeat"
输出: true
解释: s1 上可能发生的一种情形是:
"great" --> "gr/eat" // 在一个随机下标处分割得到两个子字符串
"gr/eat" --> "gr/eat" // 随机决定: 保持这两个子字符串的顺序不变
"gr/eat" --> "g/r / e/at" // 在子字符串上递归执行此算法。两个子字符串分别在随机下标处进行一轮分割
"g/r / e/at" --> "r/g / e/at" // 随机决定: 第一组交换两个子字符串, 第二组保持这两个子字符串的顺序不变
"r/g / e/at" --> "r/g / e/ a/t" // 继续递归执行此算法, 将 "at" 分割得到 "a/t"
"r/g / e/ a/t" --> "r/g / e/ a/t" // 随机决定: 保持这两个子字符串的顺序不变
算法终止, 结果字符串和 s2 相同, 都是 "rgeat"
这是一种能够扰乱 s1 得到 s2 的情形, 可以认为 s2 是 s1 的扰乱字符串, 返回 true
输入: s1 = "abcde", s2 = "caebd"
输出: false
输入: s1 = "a", s2 = "a"
输出: true

题解

一开始看到随机, 还煞有介事的写了一堆 Math.random, 实际上可以理解为人家让你找出所有 s1 变换成扰乱字符串的形态, 只要有一个与 s2 相同, 就证明 s2s1 的扰乱字符串.

最朴素的是使用暴力递归, 中止条件要保证两个子字符串的长度相等, 且值相等. 然后分割 s1s2 两个字符串, 由于 s2 可以被组装成 x + y 的形式, 也可能被组装成 y + x 的情况, 都需要考虑到.

87-is-scramble

/**
* @param {string} s1
* @param {string} s2
* @return {boolean}
*/
var isScramble = function (s1, s2) {
const dfs = (s1, s2) => {
if (s1.length !== s2.length) return false
if (s1 === s2) return true

const n = s1.length
for (let i = 1; i < n; i++) {
// 按索引 i 切割 s1 字符串
const x = s1.slice(0, i), // 黄色区域
y = s1.slice(i) // 蓝色区域

// 按索引 i 切割 s2 字符串, 不需要交换
const a = s2.slice(0, i), // 黄色区域
b = s2.slice(i) // 蓝色区域

// 按索引 i 切割 s2 字符串, 需要交换
const c = s2.slice(0, n - i), // 蓝色区域
d = s2.slice(n - i) // 黄色区域

if ((dfs(x, a) && dfs(y, b)) || (dfs(x, d) && dfs(y, c))) return true
}

return false
}

return dfs(s1, s2)
}