Scramble String

描述

Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.

Below is one possible representation of s1 = "great":

  1. great
  2. / \
  3. gr eat
  4. / \ / \
  5. g r e at
  6. / \
  7. a t

To scramble the string, we may choose any non-leaf node and swap its two children.

For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".

  1. rgeat
  2. / \
  3. rg eat
  4. / \ / \
  5. r g e at
  6. / \
  7. a t

We say that "rgeat" is a scrambled string of "great".

Similarly, if we continue to swap the children of nodes "eat" and "at", it produces a scrambled string "rgtae".

  1. rgtae
  2. / \
  3. rg tae
  4. / \ / \
  5. r g ta e
  6. / \
  7. t a

We say that "rgtae" is a scrambled string of "great".

Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.

分析

首先想到的是递归(即深搜),对两个string进行分割,然后比较四对字符串。代码虽然简单,但是复杂度比较高。有两种加速策略,一种是剪枝,提前返回;一种是加缓存,缓存中间结果,即memorization(翻译为记忆化搜索)。

剪枝可以五花八门,要充分观察,充分利用信息,找到能让节点提前返回的条件。例如,判断两个字符串是否互为scamble,至少要求每个字符在两个字符串中出现的次数要相等,如果不相等则返回false。

加缓存,可以用数组或HashMap。本题维数较高,用HashMap,mapunordered_map均可。

既然可以用记忆化搜索,这题也一定可以用动规。设状态为f[n][i][j],表示长度为n,起点为s1[i]和起点为s2[j]两个字符串是否互为scramble,则状态转移方程为

  1. f[n][i][j]} = (f[k][i][j] && f[n-k][i+k][j+k])
  2. || (f[k][i][j+n-k] && f[n-k][i+k][j])

递归

  1. // Scramble String
  2. // 递归,会超时,仅用来帮助理解
  3. // 时间复杂度O(n^6),空间复杂度O(1)
  4. class Solution {
  5. public:
  6. bool isScramble(const string& s1, const string& s2) {
  7. return isScramble(s1.begin(), s1.end(), s2.begin());
  8. }
  9. private:
  10. typedef string::iterator Iterator;
  11. bool isScramble(Iterator first1, Iterator last1, Iterator first2) {
  12. auto length = distance(first1, last1);
  13. auto last2 = next(first2, length);
  14. if (length == 1) return *first1 == *first2;
  15. for (int i = 1; i < length; ++i)
  16. if ((isScramble(first1, first1 + i, first2)
  17. && isScramble(first1 + i, last1, first2 + i))
  18. || (isScramble(first1, first1 + i, last2 - i)
  19. && isScramble(first1 + i, last1, first2)))
  20. return true;
  21. return false;
  22. }
  23. };

动规

  1. // Scramble String
  2. // 动规,时间复杂度O(n^3),空间复杂度O(n^3)
  3. class Solution {
  4. public:
  5. bool isScramble(const string& s1, const string& s2) {
  6. const int N = s1.size();
  7. if (N != s2.size()) return false;
  8. // f[n][i][j],表示长度为n,起点为s1[i]和
  9. // 起点为s2[j]两个字符串是否互为scramble
  10. bool f[N + 1][N][N];
  11. fill_n(&f[0][0][0], (N + 1) * N * N, false);
  12. for (int i = 0; i < N; i++)
  13. for (int j = 0; j < N; j++)
  14. f[1][i][j] = s1[i] == s2[j];
  15. for (int n = 1; n

递归+剪枝

  1. // Scramble String
  2. // 递归+剪枝
  3. // 时间复杂度O(n^6),空间复杂度O(1)
  4. class Solution {
  5. public:
  6. bool isScramble(const string& s1, const string& s2) {
  7. return isScramble(s1.begin(), s1.end(), s2.begin());
  8. }
  9. private:
  10. typedef string::const_iterator Iterator;
  11. bool isScramble(Iterator first1, Iterator last1, Iterator first2) {
  12. auto length = distance(first1, last1);
  13. auto last2 = next(first2, length);
  14. if (length == 1) return *first1 == *first2;
  15. // 剪枝,提前返回
  16. int A[26]; // 每个字符的计数器
  17. fill(A, A + 26, 0);
  18. for(int i = 0; i < length; i++) A[*(first1+i)-'a']++;
  19. for(int i = 0; i < length; i++) A[*(first2+i)-'a']--;
  20. for(int i = 0; i < 26; i++) if (A[i] != 0) return false;
  21. for (int i = 1; i < length; ++i)
  22. if ((isScramble(first1, first1 + i, first2)
  23. && isScramble(first1 + i, last1, first2 + i))
  24. || (isScramble(first1, first1 + i, last2 - i)
  25. && isScramble(first1 + i, last1, first2)))
  26. return true;
  27. return false;
  28. }
  29. };

备忘录法

  1. typedef string::const_iterator Iterator;
  2. typedef tuple Key;
  3. // 定制一个哈希函数
  4. namespace std {
  5. template<> struct hash {
  6. size_t operator()(const Key & x) const {
  7. Iterator first1, last1, first2;
  8. tie(first1, last1, first2) = x;
  9. int result = *first1;
  10. result = result * 31 + *last1;
  11. result = result * 31 + *first2;
  12. result = result * 31 + *(next(first2, distance(first1, last1)-1));
  13. return result;
  14. }
  15. };
  16. }
  17. // Scramble String
  18. // 递归+unordered_map做cache,比map快
  19. // 时间复杂度O(n^3),空间复杂度O(n^3)
  20. class Solution {
  21. public:
  22. unordered_map cache;
  23. bool isScramble(const string& s1, const string& s2) {
  24. cache.clear();
  25. return isScramble(s1.begin(), s1.end(), s2.begin());
  26. }
  27. bool isScramble(Iterator first1, Iterator last1, Iterator first2) {
  28. auto length = distance(first1, last1);
  29. auto last2 = next(first2, length);
  30. if (length == 1)
  31. return *first1 == *first2;
  32. for (int i = 1; i < length; ++i)
  33. if ((getOrUpdate(first1, first1 + i, first2)
  34. && getOrUpdate(first1 + i, last1, first2 + i))
  35. || (getOrUpdate(first1, first1 + i, last2 - i)
  36. && getOrUpdate(first1 + i, last1, first2)))
  37. return true;
  38. return false;
  39. }
  40. bool getOrUpdate(Iterator first1, Iterator last1, Iterator first2) {
  41. auto key = make_tuple(first1, last1, first2);
  42. auto pos = cache.find(key);
  43. return (pos != cache.end()) ?
  44. pos->second : (cache[key] = isScramble(first1, last1, first2));
  45. }
  46. };

原文: https://soulmachine.gitbooks.io/algorithm-essentials/content/cpp/dp/scramble-string.html