题目还蛮好懂的,没有生僻的知识。如果是经常编程的童鞋,第一反应就是,这道题靠的是分析推理。
来个实例分析: abba
(1)划分
假设字符串长度为len , 划分方式有 1+(len-1) , 2+(len-2) , 3+(len-3) ....(len-1)+1 , 对于abba , 有 a+bba , ab+ba , abb+a三种;
(2)组合(先不去除重复出现的情况)
任何一种划分方式,对于subtrain1,它可以颠倒,也可以不颠倒,对于subtrain2,也是可以颠倒,可以不颠倒,之后,组装顺序可以是 subtrain1+subtrain2 , 也可以是subtrain2+subtrain1 ,所以,对于每一种划分方式,最多有 8 种不同的组装方式。
对于 a+bba : a+bba , a+abb , bba+a , bba+a
对于 ab+ba : ab+ba , ab+ab , ba+ba , ba+ab , ba+ab , ab+ab , ba+ba , ab+ba
对于 abb+a : abb+a , bba+a , a+abb , a+bba
去除重复之后,就是6种。
到了这一步,我们有两种方法,1.模拟,把所有可能的字符串都求出来,再去重 2.推导,用数学方法直接求解。
如果采用第一种方法,先确定方案是否可行。对于长度为72的输入字符串,最多有 71*8=568种输出,可以接受。因此,接下来用第一种方法。由于要去重,因此联想到集合的三个性质:确定性,互异性,无序性,OK,就是STL SET了。
上代码
#include<set> #include<string> #include<iostream> using namespace std; /* * 功能:反转字符串 * 输入:s:待反转字符串 * 输出:原地反转的字符串 */ void reverse( string & s ) { char t ; int len = s.length(); for(int i =0;i < len/2 ;i++) { // swap t = s[i] ; s[i] = s[len-1-i]; s[len-1-i] = t; } } /* * 功 能:输入一个字符串,输出所有可能的最终组合字符串 * 输 入:s : 字符串 * 输 出:int:所有可能的组合字符串的数目 * 思 路:采用set数据结构,模拟整个过程 */ int Process( const string s ) { // Data structure set <string> S; string subtrain1 , subtrain2 ; string tmp ; // 第一步,划分字符串 int len = s.length(); for(int i=0 ; i <= len-2 ; i++) { subtrain1 = s.substr( 0 , i+1 ); subtrain2 = s.substr( i+1 , len-i-1 ); // 存储subtrain1 subtrain2的原始值,防止在处理过程中篡改 string subtrain1_original = subtrain1; string subtrain2_original = subtrain2; /* 第二步,不同的组合方式 */ // subtrain1 不反转 , subtrain2 不反转 tmp = subtrain1 + subtrain2 ; S.insert( tmp ); tmp = subtrain2 + subtrain1 ; S.insert( tmp ); // subtrain1 反转 , subtrain2 不反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain1 ); tmp = subtrain1 + subtrain2 ; S.insert( tmp ); tmp = subtrain2 + subtrain1 ; S.insert( tmp ); // subtrain1 不反转 , subtrain2 反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain2 ); tmp = subtrain1 + subtrain2 ; S.insert( tmp ); tmp = subtrain2 + subtrain1 ; S.insert( tmp ); // subtrain1 反转 , subtrain2 反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain2 ); reverse( subtrain1 ); tmp = subtrain1 + subtrain2 ; S.insert( tmp ); tmp = subtrain2 + subtrain1 ; S.insert( tmp ); } // 输出 return S.size(); } int main() { int Case ; string input ; cin>>Case; while( Case-- ) { cin>> input ; int rs = Process( input ); printf("%d\n",rs ); } return 0; }
这说明,即便使用了传说中的set (底层是一棵红黑树,插入需要O(log(n))),仍然太慢了。出于速度考虑,我祭出了Hash。
(1)设计Hash函数
Hash = ∑( s[i] * (i+1) ) , i=0,1,....(length-1)
(2)解决冲突
拉链法
Hash版 代码: Accepted
#include<string> #include<iostream> using namespace std; /* Node 定义,用于拉链法 */ struct Node { string data ; Node * next ; }; int Num_nodes ; // Node数目,作为最终输出 int const MAXN = 1000; // Hash表的大小 Node * Hash[MAXN] ={NULL}; // Hash 表 /* * 功能:将一个字符串s插入Hash表 * 输入:s:待插入字符串 * 输出:无 */ void InsertHash( string s ) { // 第一步,计算 Hash key int len = s.length(); int key = 0 ; // hash key for( int i = 0 ; i < len ; i++) { key += s[i] * (i+1) ; } // 第二步,确定插入位置 int loc = key % MAXN ; Node * p = Hash[ loc ] ; Node * q ; // 临时使用的指针 q = NULL ; if( NULL == p ) // 直接插入 { q = new Node ; q->data = s ; q->next = NULL ; Hash[ loc ] = q ; // 同时,Num_nodes加一 Num_nodes++; } else // 需要判断该字符串s是否已经存在 { // 遍历该链表,以查看s是否存在 while( p != NULL ) { // 已经存在,直接返回 if( p->data == s ) return; q = p ; p = p->next; } // s不存在于该条链表中 q->next = new Node; q->next->data = s; q->next->next = NULL; // 同时,Num_nodes++ Num_nodes++; } } /* * 功能:重置Hash表 * 输入:无 * 输出:无 * 说明:为了防止内存泄漏,手动地清理所有内存 */ void ResetHash() { Node * p , * q ; // 临时使用 p = q = NULL; Num_nodes = 0; // 清零 for( int i = 0 ; i < MAXN ; i++ ) { p = Hash[ i ] ; if( NULL == p ) continue; while( p != NULL ) { q = p->next ; // 释放内存 free( p ); p = q; } Hash[i] = NULL; // 重置Hash表 } } /* * 功能:反转字符串 * 输入:s:待反转字符串 * 输出:原地反转的字符串 */ void reverse( string & s ) { char t ; int len = s.length(); for(int i =0;i < len/2 ;i++) { // swap t = s[i] ; s[i] = s[len-1-i]; s[len-1-i] = t; } } /* * 功 能:输入一个字符串,输出所有可能的最终组合字符串 * 输 入:s : 字符串 * 输 出:int:所有可能的组合字符串的数目 * 思 路:采用set数据结构,模拟整个过程 */ int Process( const string s ) { // Data structure string subtrain1 , subtrain2 ; string tmp ; // 第一步,划分字符串 int len = s.length(); for(int i=0 ; i <= len-2 ; i++) { subtrain1 = s.substr( 0 , i+1 ); subtrain2 = s.substr( i+1 , len-i-1 ); // strore original value , avoid change of value string subtrain1_original = subtrain1; string subtrain2_original = subtrain2; /* 第二步,不同的组合方式 */ // subtrain1 不反转 , subtrain2 不反转 tmp = subtrain1 + subtrain2 ; InsertHash( tmp ); tmp = subtrain2 + subtrain1 ; InsertHash( tmp ); // subtrain1 反转 , subtrain2 不反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain1 ); tmp = subtrain1 + subtrain2 ; InsertHash( tmp ); tmp = subtrain2 + subtrain1 ; InsertHash( tmp ); // subtrain1 不反转 , subtrain2 反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain2 ); tmp = subtrain1 + subtrain2 ; InsertHash( tmp ); tmp = subtrain2 + subtrain1 ; InsertHash( tmp ); // subtrain1 反转 , subtrain2 反转 subtrain1 = subtrain1_original ; subtrain2 = subtrain2_original ; reverse( subtrain2 ); reverse( subtrain1 ); tmp = subtrain1 + subtrain2 ; InsertHash( tmp ); tmp = subtrain2 + subtrain1 ; InsertHash( tmp ); } // 输出 return Num_nodes; } int main() { int Case ; string input ; cin>>Case; while( Case-- ) { cin>> input ; int rs = Process( input ); printf("%d\n",rs ); // 重置Hash ResetHash(); } return 0; }