两个来自JAVAEYE的题目

假设有这样一种字符串,它们的长度不大于 26 ,而且若一个这样的字符串其长度为 m ,则这个字符串必定由 a, b, c ... z 中的前 m 个字母构成,同时我们保证每个字母出现且仅出现一次。比方说某个字符串长度为 5 ,那么它一定是由 a, b, c, d, e 这 5 个字母构成,不会多一个也不会少一个。嗯嗯,这样一来,一旦长度确定,这个字符串中有哪些字母也就确定了,唯一的区别就是这些字母的前后顺序而已。

 

现在我们用一个由大写字母 A 和 B 构成的序列来描述这类字符串里各个字母的前后顺序:

如果字母 b 在字母 a 的后面,那么序列的第一个字母就是 A (After),否则序列的第一个字母就是 B (Before);
如果字母 c 在字母 b 的后面,那么序列的第二个字母就是 A ,否则就是 B;
如果字母 d 在字母 c 的后面,那么 …… 不用多说了吧?直到这个字符串的结束。

这规则甚是简单,不过有个问题就是同一个 AB 序列,可能有多个字符串都与之相符,比方说序列“ABA”,就有“acdb”、“cadb”等等好几种可能性。说的专业一点,这一个序列实际上对应了一个字符串集合。那么现在问题来了:给你一个这样的 AB 序列,问你究竟有多少个不同的字符串能够与之相符?或者说这个序列对应的字符串集合有多大?注意,只要求个数,不要求枚举所有的字符串。 

最后总结一下吧:

实际上这个问题的算法直接做是 O(n^3) 的,做些优化之后可以到 O(n^2)。这里的关键是只考虑可能性而不要去想具体如何插入:请在脑子里面建一张三角形的表,每一行对应于 AB 字符串中的一个字符,每一行中第 i 个元素代表了到目前为止最后一个字母放在第 i 个位置的可能性个数。比方说:

 

代码
 
  1. A:            0      1  
  2. B:       1       1      0  
  3. A:   0       1       2       2  
<script type="text/javascript">render_code();</script>

 

这里第一行 A ,当前最后一个字母是 b ,b 放在第 0 个位置的可能性个数是 0 ,放在第 1 个位置的可能性个数是 1 ;第二行 B ,当前最后一个字母是 c ,放在第 0、1 两个位置的可能性个数都是 1 ,放在最后一个位置的可能性是 0 …… 依此类推。很容易可以看出,如果当前行的次序是 A ,那么放在第 i 个位置的可能性就是上一行从 0 到 i-1 位置的可能性个数之和,否则就是从 i 到最后一个位置的可能性个数之和。逐步向下做,直到用完输入的 AB 字符串即可,最后的总数就是对上述表格最后一行求个和。这里每一行中各个列的可能性是没有重叠的,因为最后一个字母的位置各不相同。

复杂度么,这个表格一共有 O(n^2) 项,每一格构造最多花 O(n) ,所以总复杂度自然是 O(n^3) ,稍做优化可以把计算每一格的开销降到 O(1) ,复杂度可以降到 O(n^2) 。这里给个参考实现 —— C++ 写的,不熟悉的朋友只能说见谅了,好在还算简单,没用什么特殊的东西:

 

代码
 
  1. int count(const string& AB)   
  2. {   
  3.   vector<int> prev_line;   
  4.   vector<int> current_line;   
  5.   
  6.   prev_line.push_back(1);   
  7.   for( string::const_iterator it=AB.begin();   
  8.        it!=AB.end();   
  9.        ++it) {   
  10.   
  11.     current_line.clear();   
  12.     current_line.resize(prev_line.size()+10);   
  13.   
  14.     if( *it == 'A' ) {   
  15.       int possibility = 0;   
  16.       for(size_t i=0; i<prev_line.size(); ++i) {   
  17.         current_line[i] = possibility;   
  18.         possibility += prev_line[i];   
  19.       }   
  20.       current_line.back()=possibility;   
  21.     }   
  22.     else if( *it == 'B' ) {   
  23.       int possibility = 0;   
  24.       for(size_t i=prev_line.size(); i>0; --i) {   
  25.         current_line[i] = possibility;   
  26.         possibility += prev_line[i-1];   
  27.       }   
  28.       current_line[0]=possibility;   
  29.     }   
  30.     else {   
  31.       assert(false);  // invalid input   
  32.     }   
  33.     prev_line.swap(current_line);   
  34.   }   
  35.   
  36.   int result = 0;   
  37.   for( vector<int>::iterator it=prev_line.begin();   
  38.        it!=prev_line.end();   
  39.        ++it ) {   
  40.     result += *it;   
  41.   }   
  42.   return result;   
  43. }  
<script type="text/javascript">render_code();</script>

 

其中 vector 是 C++ 标准库的成员,实际上就是变长数组。我用 prev_line 和 current_line 分别记录上述三角形表格的前一行和当前行,最后对最后一行求一个总和。

有一个整数n,写一个函数f(n),返回0到n之间出现的"1"的个数。比如f(13)=6,现在f(1)=1,问下一个最大的f(n)=n的n是什么?

为什么f(13)=6, 因为1,2,3,4,5,6,7,8,9,10,11,12,13.数数1的个数,正好是6.

我把c的代码贴出来!

他计算到4000000000,用的是剪枝。

 

代码
 
  1. #include "stdafx.h"  
  2.   
  3. #include <windows.h>   
  4. #include <stdlib.h>   
  5.   
  6. int f(int n);   
  7. int count1(int n);   
  8. int cal(unsigned int number,int nwei,int count1,unsigned int ncount);   
  9.   
  10. int gTable[10];   
  11. const unsigned int gMAX = 4000000000L;   
  12.   
  13. int main(int argc, char* argv[])   
  14. {   
  15.   int i;   
  16.   unsigned int n=1;   
  17.   unsigned int ncount = 0;   
  18.   int nwei = 0;   
  19.   int ncount1;   
  20.   
  21.   /*if(argc>1)  
  22.   {  
  23.     n = atoi(argv[1]);  
  24.     ncount = f(n);  
  25.     printf("f(%d) = %d/n",n,ncount);  
  26.   }*/  
  27.   
  28.   int beginTime=GetTickCount();   
  29.   //init gTable   
  30.   for(i=0;i<10;++i)   
  31.   {   
  32.     n *= 10;   
  33.     gTable[i] = f(n-1);   
  34.   }   
  35.   
  36.   n=0;   
  37.   nwei = 0;   
  38.   ncount1 = 0;   
  39.   while(n<gMAX)   
  40.   {   
  41.     unsigned int temp;   
  42.        
  43.     temp = 1;   
  44.       
  45.     ncount =cal(n,nwei,ncount1,ncount);   
  46.     for(i=0;i<nwei;++i)   
  47.       temp *= 10;   
  48.     n += temp;   
  49.     if( (n/temp)/10 == 1)   
  50.       ++nwei;   
  51.     ncount1 = count1(n);   
  52.   }   
  53.   
  54.   int endTime=GetTickCount();   
  55.   endTime-=beginTime;   
  56.   
  57.   printf("time: %d ms/n",endTime);   
  58. return 0;   
  59. }   
  60.   
  61. int f(int n)   
  62. {   
  63.   int ret = 0;   
  64.   int ntemp=n;   
  65.   int ntemp2=1;   
  66.   int i=1;   
  67.   while(ntemp)   
  68.   {   
  69.     ret += (((ntemp-1)/10)+1) * i;   
  70.     if( (ntemp%10) == 1 )   
  71.     {   
  72.       ret -= i;   
  73.       ret += ntemp2;   
  74.     }   
  75.     ntemp = ntemp/10;   
  76.     i*=10;   
  77.     ntemp2 = n%i+1;   
  78.   }   
  79.   return ret;   
  80. }   
  81.   
  82. int count1(int n)   
  83. {   
  84.   int count = 0;   
  85.   while(n)   
  86.   {   
  87.     if( (n%10) == 1)   
  88.       ++count;   
  89.     n /= 10;   
  90.   }   
  91.   return count;   
  92. }   
  93.   
  94. int cal(unsigned int number,int nwei,int count1,unsigned int ncount)   
  95. {   
  96.   int i,n=1;   
  97.   unsigned int maxcount;   
  98.   if(nwei==0)   
  99.   {   
  100.     ncount += count1;   
  101.     if(number == ncount)   
  102.     {   
  103.       printf("f(%d) = %d /n",number,number);   
  104.     }   
  105.     return ncount;   
  106.   }   
  107.   for(i=0;i<nwei;++i)   
  108.     n *= 10;   
  109.   maxcount = ncount + gTable[nwei-1];   
  110.   maxcount += count1*n;   
  111.   if(ncount > (number +  (n-1)) )   
  112.   {   
  113.    return maxcount;   
  114.   }   
  115.   if(maxcount < number)   
  116.   {   
  117.     return maxcount;   
  118.   }   
  119.   n /= 10;   
  120.   for(i=0;i<10;++i)   
  121.   {   
  122.     if(i==1)   
  123.        ncount = cal(number+i*n,nwei-1,count1+1,ncount);   
  124.     else  
  125.        ncount = cal(number+i*n,nwei-1,count1,ncount);   
  126.   }   
  127.     return ncount;   

  128. 我们将数x表示成 head*10(n)+tail;
    如:x=1234 , 则表示为 1* 10(3) + 234;
    f(1234) = 1 * f(99) + 235 + f(234);
    不用我解释吧
    234 = 2 * 10(2) + 34;
    f(234) = 2 * f(99) + 100 + f(34);
    从1到234,有两次 f(99) ,从 100 到 199 共有100个1, 从 200到234 的 1的总和 = f(34)

    可以得出:
    对于数x,
    如果x<10
    f(x) = x>=1 ? 1 : 0;
    如果head = 1
    f(x) = head * f(10(n) - 1) + f(tail) + tail+1
    如果head > 1
    f(x) = head * f(10(n) - 1) + f(tail) + 10(n);

    根据这个公式我们可以迅速的求出f(x),而如果用getCountOfOne(long number),只能得到某数中1的个数,对于从1到n的值,必须使用循环相加的方法,如果计算,123456789的值的话,就需要循环 123456789次,即便剪枝,也不能减少多少循环次数, 而用这个公式,写个递归方法,可以很快的算出答案。

    但仍然有个问题:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值