一道面试题的探讨(一)

 

问题:

手机上的按键每个数字对应多个字母,比如2,对应“abc”。若按下一串数字,长度为N,要求把所有可能的字母序列输出出来。比如“23”,就可能对应adaeafbdbebfcdcecf

 

解法一:

刚接触这个问题,感觉这非常简单。事实也是这样,只要你对递归足够熟的话,很容易给出一个递归算法。但是有一点要注意,有人因为每个数字对应三个字符,想从后往前用两次迭代的方式是行不通的。我当场没答出来的原因就是纠结于这个方法,迟迟没有给出结果。其实采用将前面的字符序列缓存的方式,很容易就给出结果了,以下是算法:

char g_telephone[10][3];

 

void printX(std::string strPrefix, char* source, int nLen, int x )

{             

                std::string strTemp ;

               

                if ( x <= nLen -1 )             

                {

                                int cTemp = source[x] - 48 ;

                                if ( cTemp <= 9 && cTemp >= 0 )

                                {

                                               

                                                strTemp+= strPrefix + g_telephone[cTemp][0] ;              

                                                printX(strTemp, source, nLen, x+1) ;                                      

 

                                               

                                                strTemp+= strPrefix + g_telephone[cTemp][1] ;              

                                                printX(strTemp, source, nLen, x+1) ;      

                               

                                                strTemp+= strPrefix + g_telephone[cTemp][2] ;              

                                                printX(strTemp, source, nLen, x+1) ;

                                }

 

                }

                else

                {

                                std::cout<< strPrefix.c_str() << std::endl ;

                }

 

 

}

 

int main(int argc, char** argv)

{

                //input, for simplicity, we only use it directly

                char source[] = "1234567" ;

 

                //length of the string, except '/0'

                int size = sizeof(source) / sizeof(char) - 1;

 

                printX( std::string(""), source, size,  0) ;

               

}

 

时间复杂度:考虑到函数迭代调用的压栈出栈只会增加复杂度的系数,复杂度还是由产生实际作用的打印输出语句决定;对于长度为N的输入串,肯定对应3^N的输出,所以复杂度为O(3^N)

空间复杂度:考虑迭代调用最深处的内存使用情况,每次迭代除了3个参数还有函数的返回地址,所以有16个字节;每层有一个strTemp的临时变量,长度分别为12…N。所以,总的内存消耗量为O(16*N+N*(N+1)/2)

 

其实,面试题到此就已经结束了。或许是对方对内存占用情况不敏感,或许因为我连递归的都没答出来就没继续问。从我实际的项目经验,在工程中能用非递归还是尽量用非递归;递归算法虽然简单易懂,容易想到,但往往是内存占用的瓶颈。因此,虽然面试早就结束,我还是在考虑非递归算法的实现。

非递归算法还是挺容易想到的,下面三个方法是在位数组上不断优化的结果,很可惜的是,我找到的最优解,虽然空间复杂度能降到O(N)的量级,但时间复杂度在O(4^N)的量级。

 

解法二:

考虑用两位来表示每个数字代表的三个字符,比如“2”对应于a,b,c;那么用00表示a01表示b10表示c11废弃不用。这样,我们用一个长度为2N的位数组B[2N]来表示长度为N的字符串,位数组唯一确定该字符串。为讨论方便,给出数字到字符的映射表,如下:

char g_telephone[10][3] =

{              //1

                {              '0','1','2'},

                //2

                {              'a','b','c'                },

                //3

                {              'd','e','f'                },

                //4

                {              'g','h','i' },

                //5

                {              'j','k','l'   },

                //6

                {              'm','n','o'},

                //7

                {              'p','q','r'                },                            

                //8

                {              's','t','u' },                                            

                //9

                {              'v','w','x'},

                //0

                {              'y','z',' '  }

};

长度为3的输入序列 “123”,如果对应的 B[1..6] = 000000, 则结果为 “0ad”;如果B[1..6] = 010101, 结果就是“0be”

对于长度为N的输入,将对应的B[2N]从全为0遍历到全为1,除去“11”的特殊情况,剩下的每个B[2N]将对应一个输出。直接给出算法:

void NonRecursive1( char* source, int N )

{

                BitSet B[2*N] = {0} ;

                for (; B <= {11111...} ; B ++ )

                {

                                std::string strTemp ;

                                int indexX = 0 ;

                                int indexY = 0 ;

 

                                for ( int i=0; i < N; i++)

                                {

                                                //if 00,01,10

                                                if ( B[2*i, 2*i+1] != {11} )

                                                {

                                                                indexX = source[i] - 48 ;

                                                                indexY = B[2*i, 2*i+1] ;

                                                                strTemp += g_telephone[ indexX ][ indexY ] ;

                                                }

                                                //otherwise, jump to the next iteration

                                                else

                                                {

                                                                strTemp = "" ;

                                                                break ;

                                                }

                                }

                                //output the results

                                if ( strTemp != "")

                                {

                                                std::cout<< strTemp<<std::endl;

                                }

                               

                }

}

 

时间复杂度:总的遍历次数为2^2N=4^N;每次输出有N次遍历,所以复杂度为O(N*4^N)。有人会有这个疑问循环判断条件” B <= {11111...}”,每次判断都需要遍历每位判断是否为1的。事实上一个比较好的解决方法时,在2N位外面增加一个进位,只要判断该进位是否为1就可判断是否已经大于{1111….}了。

空间复杂度:空间占用分析很简单,如果不考虑临时变量,内存占用就只有位数组的长度,占用的字节数为2N/8,所以是O(N)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值