87. 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":

    great
   /    \
  gr    eat
 / \    /  \
g   r  e   at
           / \
          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".

    rgeat
   /    \
  rg    eat
 / \    /  \
r   g  e   at
           / \
          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".

    rgtae
   /    \
  rg    tae
 / \    /  \
r   g  ta  e
       / \
      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.

这个问题可以用DP方法解决,思路如下:

先分析情况:

1.字符串长度为1, 若s1 == s2 , true

2.字符串长度为2, 如s1=“xy", 那么s2="xy“或”xy"为true

3.字符串长度为n,s1=XY, 那么s2=XY / YX返回true

采用一个三维数组,例如vec[1][i][j] 代表长度为1,s1下标为 i ,s2下标为 j 时是否符合scamble 


class Solution {
public:
    bool isScramble(string s1, string s2) {
        size_t len_s1 = s1.length();
        size_t len_s2 = s2.length();
        
        if(len_s1 != len_s2)
            return false;
        
        vector<vector<vector<bool>>> vec(len_s1+1, vector<vector<bool>>(
                                        len_s1, vector<bool>(len_s1, false)));
        for(int i=0; i<len_s1; ++i)
            for(int j=0; j<len_s1; ++j)
                vec[1][i][j] = s1[i] == s2[j];
        
        for(int n=2; n<=len_s1; ++n){    //从2开始,因为长度为1之前已经计算过。
            for(int i=0; i<=len_s1-n; ++i){
                for(int j=0; j<=len_s1-n; ++j){
                    for(int k=1; k<n; ++k){
                         if(vec[k][i][j] && vec[n-k][i+k][j+k]
                            || vec[k][i][j+n-k] && vec[n-k][i+k][j]){
                            vec[n][i][j] = true;
                            break;
                        }
                    }
                }
            }
        }
        return vec[len_s1][0][0];
    }
};


n代表当前字符串长度,n从1开始一直到最大值len_s1,自底向上。

我们发现,对于i和j进行双重循环遍历,将会自底向上求得当前长度为n时的字符串是否为Scramble,如果是,那么回break循环,break只能跳出一重循环。当我们的n为len_s1时,此时 i 和 j 只可能为0,这时当前字符串长度为len_s1,它的一部分vec[1][i][j] 和vec[len_s1-1][i][j] (即vec[n-1][i][j]))在之前由底向上已经计算过,所以vec[len_s1][0][0] = true,返回。

更多算法:

#include <iostream>
#include <vector>
#include <string>
#include <assert.h>
#include <map>
#include <unordered_map>

class solution1 {
#ifdef _USE_METHOD_1_
//methon 1
public:
	bool is_scramble1(std::string s1, std::string s2) {
		return is_scramble(s1.begin(), s1.end(), s2.begin());
	}
private:
	typedef std::string::iterator iterator;
	bool is_scramble(iterator first1, iterator last1, iterator first2){
		auto length = std::distance(first1, last1);
		auto last2 = std::next(first2, length);

		if(length == 1)
			return *first1 == *first2;

		for(int i=0; i<length; ++i)
			if(is_scramble(first1, first1+i, first2) && is_scramble(first1+i, last1, first2+i)
		    	|| is_scramble(first1, first1+i, last2-i) && is_scramble(first1+i, last1, first2))
					return true;
		return false;
	}
#endif
#ifdef _USE_METHOD_2_
//method 2, DP, O(n^4)
public:
    bool is_scramble2(std::string s1, std::string s2) {
		size_t len_s1 = s1.length();
		size_t len_s2 = s2.length();
	
		if(len_s1 != len_s2)
			return false;
	
		std::vector<std::vector<std::vector<bool> > > 
		vec(len_s1+1, std::vector<std::vector<bool> >(len_s1, std::vector<bool>(len_s1, false)));
	
		for(int i=0; i<len_s1; ++i)
			for(int j=0; j<len_s1; ++j)
				vec[1][i][j] = s1[i] == s2[j];
	
		for(int n=2; n<=len_s1; ++n){
			for(int i=0; i<=len_s1-n; ++i){	
				for(int j=0; j<=len_s1-n; ++j){
					for(int k=1; k<n; ++k){
						if(vec[k][i][j] && vec[n-k][i+k][j+k]
							|| vec[k][i][j+n-k] && vec[n-k][i+k][j]){
							vec[n][i][j] = true;
							break;
						}
	
					}
				}	
			}
		}
	
		return vec[len_s1][0][0];
	}
#endif
#ifdef _USE_METHOD_3_
//method 3, recursion + prune
public:
	bool is_scramble(std::string s1, std::string s2) {
		return is_scramble(s1.begin(), s1.end(), s2.begin());
	}
private:
	typedef std::string::iterator iterator;
	bool is_scramble(iterator first1, iterator last1, iterator first2){
		auto length = std::distance(first1, last1);
		auto last2 = std::next(first2, length);

		if(length == 1)
			return *first1 == *first2;

		int A[26];   //why array not out of range, only for 26 characters
		std::fill(A, A+26, 0);
		for(int i=0; i<length; ++i){ 
			++A[*(first1+i)-'a'];
			std::cout<<(*(first1+i)-'a')<<' ';
		}
		for(int i=0; i<length; ++i)
			--A[*(first2+i)-'a'];
		for(int i=0; i<26; ++i)
			if(A[i] != 0)
				return false;

		for(int i=0; i<length; ++i)
			if(is_scramble(first1, first1+i, first2) && is_scramble(first1+i, last1, first2+i)
		    	|| is_scramble(first1, first1+i, last2-i) && is_scramble(first1+i, last1, first2))
					return true;
		return false;
	}
#endif
//method 4, recursion + cache(map)
public:
	bool is_scramble(std::string s1, std::string s2) {
		cache.clear();
		return is_scramble(s1.begin(), s1.end(), s2.begin());
	}
private:
	typedef std::string::const_iterator iterator;
	std::map<std::tuple<iterator, iterator, iterator>, bool> cache;   //using map to cache

	bool is_scramble(iterator first1, iterator last1, iterator first2){
		auto length = std::distance(first1, last1);
		auto last2 = std::next(first2, length);

		if(length == 1)
			return *first1 == *first2;

		for(int i=0; i<length; ++i)
			if(get_or_update(first1, first1+i, first2) && get_or_update(first1+i, last1, first2+i)
		    	|| get_or_update(first1, first1+i, last2-i) && get_or_update(first1+i, last1, first2))
					return true;
		return false;
	}
	bool get_or_update(iterator first1, iterator last1, iterator first2){
		auto key = make_tuple(first1, last1, first2);
		auto pos = cache.find(key);

		return pos != cache.end() ? pos->second : (cache[key] = is_scramble(first1, last1, first2));
	}
};

//method 5, recursion+cache(unordered_map)
typedef std::string::const_iterator iterator;
typedef std::tuple<iterator, iterator, iterator> key;

namespace std
{

template <> struct hash<key> {   
	size_t operator()(const key& x) const {
		::iterator first1, last1, first2;
		std::tie(first1, last1, first2) = x;

		int result = *first1;
		result = result * 31 + *last1;
		result = result * 31 + *first2;
		result = result * 31 + *(std::next(first2, std::distance(first1, last1)));
		return result;
	}
};

}

class solution {
public:
	std::unordered_map<key, bool> cache;
	
	bool is_scramble(std::string s1, std::string s2) {
		cache.clear();
		return is_scramble(s1.begin(), s1.end(), s2.begin());
	}
private:
	bool is_scramble(iterator first1, iterator last1, iterator first2){
		auto length = std::distance(first1, last1);
		auto last2 = std::next(first2, length);

		if(length == 1)
			return *first1 == *first2;

		for(int i=0; i<length; ++i)
			if(get_or_update(first1, first1+i, first2) && get_or_update(first1+i, last1, first2+i)
		    	|| get_or_update(first1, first1+i, last2-i) && get_or_update(first1+i, last1, first2))
					return true;
		return false;
	}
	bool get_or_update(iterator first1, iterator last1, iterator first2){
		auto key = make_tuple(first1, last1, first2);
		auto pos = cache.find(key);

		return pos != cache.end() ? pos->second : (cache[key] = is_scramble(first1, last1, first2));
	}
};

int main()
{
	std::string s1 = "great";
	std::string s2 = "rgeat";

	solution sl;
	assert(sl.is_scramble(s1, s2));
	
	return 0;
}

unorder_map为什么选用31做哈希,原因如下:

A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

D.并且31只占用5bits,相乘造成数据溢出的概率较小。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值