太长了,装不下,分两页。
解法三:
解法二占用的空间已经很小了,但是时间复杂度还是太高。另外每次输出的循环里都有一个判断是否为”11”的。从汇编的角度讲,这个是很讨厌的:一条判断语句就代表比较语句、标志位的改变、跳转语句等;如果将其改为位操作的话能提高不少效率。具体算法如下:
void NonRecursive2( char* source, int N )
{
BitSet B[2*N] = {0} ;
for (; B <= {11111...} ; B ++ )
{
std::string strTemp ;
bit c = 0;
for ( int i=0; i < N; i++ )
{
//if there is one {11}, after {11} XOR {11}, we get 00
//negate it to one bit, we get 1
//c then becomes 1
c |= !( B[2*i, 2*i+1] XOR {11} ) ;
}
if ( c != 1 )
{
int indexX = 0 ;
int indexY = 0 ;
for ( int i=0; i < N; i++)
{
indexX = source[i] - 48 ;
indexY = B[2*i, 2*i+1] ;
strTemp += g_telephone[ indexX ][ indexY ] ;
}
std::cout<< strTemp<<std::endl;
}
}
}
时间复杂度:时间复杂度还是O(N*4^N)。但是此改进能大大减少常数系数,可能会有几倍的速度提高(我没试过,有兴趣的试一试)。
空间复杂度: O(N)。
解法四:
进一步考虑问题,问题的关键主要集中于如何降低时间复杂度。为什么时间复杂度里会出现系数N?是因为我们每次都遍历一遍数组打印结果。可是真的有必要每次都把长度为2N的数组遍历一遍么?考虑最简单的情况,假设一个2N的数组的值为全零,这时候加1,这时我们发现该数组的前2N-1位都没变,只有最后一位变了;也就是说,我们如果将前(N-1)个字符缓存,只需要加上最后一个字符就行了。
问题就转化为,找到自右向左第一个零出现的位置;零的左边原样输出,右边变化。具体算法如下:
void NonRecursive3( char* source, int N )
{
BitSet B[2*N] = {0} ;
std::string strLeft = "";
int zeroPos = N ;
for (; B <= {11111...} ; B ++ )
{
std::string strTemp = strLeft;
bit c = 0;
for ( int i=0; i < zeroPos; i++ )
{
//if there is one {11}, after {11} XOR {11}, we get 00
//negate it to one bit, we get 1
//c then becomes 1
c |= !( B[2*i, 2*i+1] XOR {11} ) ;
}
if ( c != 1 )
{
int indexX = 0 ;
int indexY = 0 ;
for ( int i=0; i < zeroPos; i++)
{
indexX = source[i] - 48 ;
indexY = B[2*i, 2*i+1] ;
strTemp += g_telephone[ indexX ][ indexY ] ;
}
std::cout<< strTemp<<std::endl;
}
//find the first zero position from right to left
zeroPos = FindFirstZero(B) ;
//return the unchanged right part
strLeft = Split(strTemp, zeroPos) ;
}//for
}
时间复杂度:时间复杂度的计算有点复杂。考虑这样一个组合问题,有2N个位置,前k个位置每次固定,剩下的(2N-k)个位置看做是需要变化的位置,则对于给定前k个位置的情况,复杂度为(2N-k)/2。而考虑前k个位置的情况,总共有2^k种可能。所以总的时间复杂度是(2^1*(2N-1)/2 + 2^2*(2N-2)/2 + … + 2^(2N-1)*1/2)。等差比数列,用高中的数学算出来是(4^N-2N),感兴趣的可以重新算一遍看我有没有算错。
空间复杂度: O(N)。
解法五:
最后一种方法,是考虑到用2bits来表示三种字符,”11”那一位就浪费了。如果想进一步节省空间的话,可以用位数组表示3^N个数,用除数和余数来表示位置和字符信息。
举个简单的例子,比如输入是”12”,用B[4]来表示可能的字符串。之所以取长度为4是因为需要表示3^2=9个数,2^3=8不够表示。接下来要做的就是二进制与三进制的转化。比如00112 = 00013。
二进制的乘法和除法可以参考微机原理的书,我现在只记得乘法的处理方式:B[N]*3 = B[N]<<1 + 1。具体算法如下:
void NonRecursive4( char* source, int N )
{
//use 3 as dimension, the largest number will not extend 2*N
//use a structure to automatically malloc B
BitSet B1 = {1} ;
for ( int i=0; i<N; i++ )
{
//find the largest position
B1 = B1<<1 + B1 ;
}
BitSet B[B1.getLength] = {0} ;
std::string strLeft = "";
int zeroPos = B1.getLength ;
for (; B <= B1 ; B ++ )
{
std::string strTemp = strLeft;
int indexX = 0 ;
int indexY = 0 ;
BitSet B2 = B[0..zeroPos] ;
int i = 0
while ( B2 != 0 )
{
indexX = source[i++] - 48 ;
indexY = B2 % 3 ;
strTemp += g_telephone[ indexX ][ indexY ] ;
B2 = B2/3 ;
}
std::cout<< strTemp<<std::endl;
//find the first zero position from right to left
zeroPos = FindFirstZero(B) ;
//return the unchanged right part
strLeft = Split(strTemp, zeroPos) ;
}//for
}
时间复杂度:没高兴算,应该也是O(N*3^N)量级的。
空间复杂度: O(N)。