58同城面试遇到的问题,判断一个正整数n是否可以由连续正整数求和而来。我没回答上来,面试官给了提示,回来自己想想,觉得有两种方法:
- 模拟法:用一个在数轴上的滑动窗(即一个正整数区间),来模拟求解过程。具体来说,如果滑动窗内整数的和小于n,则滑动窗右边界向右移动,如果滑动窗内整数的和大于n,则滑动窗左边界向右移动,如果滑动窗内整数的和等于n,则n可以由滑动窗内的整数求和表示,程序终止。
- 数学分析法:可以证明(证明在本文最后),如果n可以表示为:n=(2*m+1)*2k, m, k>=0; 则m>0时,n可以由连续正整数求和而来,m=0时,n不可以由连续正整数求和而来。实际上,正整数中,只有2的各次方不可以由连续正整数求和而来,如n=1,2,4,8,16,32... ;另一方面,只要n含有大于1的奇数因子,都可以由连续正整数求和而来。
下面是代码:
View Code
1 #include <iostream> 2 using namespace std; 3 4 //模拟法 5 bool valid(int n){ 6 if(n<=1) return false; 7 //定义滑动窗的参数,first: 滑动窗左边界,last: 滑动窗右边界,sum: 滑动窗内整数的和 8 int first, last, sum; 9 //参数初始化 10 first=last=sum=1; 11 do{ 12 //n>sum, 则滑动窗右边界向右移动 13 if(n>sum) { 14 last++; 15 sum+=last; 16 } 17 //n<sum, 则滑动窗左边界向右移动 18 else if(n<sum) { 19 sum-=first; 20 first++; 21 } 22 //n==sum, 循环结束,返回true 23 else 24 return true; 25 } 26 while(first!=last); //如果滑动窗只含一个整数,循环结束,返回false 27 return false; 28 } 29 30 //数学分析法 31 bool valid2(int n){ 32 while(n%2==0) n/=2; 33 return n>1; 34 } 35 36 int main() 37 { 38 for(int i=1;i<=1000;i++) 39 if(!valid(i)) 40 cout<<i<<endl; 41 for(int i=1;i<=1000;i++) 42 if(!valid2(i)) 43 cout<<i<<endl; 44 return 0; 45 }
程序的运行结果就不贴出来了,有兴趣的话,可以自己试试。下面来证明前面提到的一个结论:如果n可以表示为:n=(2*m+1)*2k, m, k>=0; 则m>0时,n可以由连续正整数求和而来,m=0时,n不可以由连续正整数求和而来。
证明:假设n可以由正整数区间[k1,k2],k2>k1>0内的整数求和而来,即
- n=(k1+k2)(k2-k1+1)/2=(2*m+1)*2k, m, k>=0, k2>=k1>0; 即
- (k1+k2)(k2-k1+1)=(2*m+1)*2k+1
容易证明,(k1+k2)和(k2-k1+1)奇偶性不一致,即一奇一偶。如果(k1+k2)为奇数,可以有(m=0时,只有)
- k1+k2=2*m+1;
- k2-k1+1=2k+1;
求得k1=m+1-2k;k2=m+2k;同理如果(k2-k1+1)为奇数,可以有(m=0时,只有)
- k2-k1+1=2*m+1;
- k1+k2=2k+1;
求得k1=2k-m;k2=m+2k;
看上去可以得到两个区间[2k-m, m+2k]和[m+1-2k, m+2k](实际上m>0时,最后得到的区间可能更多),但是就这两个区间区间而言,不会同时有效。
来看看最后的结论,m=0时,两个区间都无效,说明此时,n不可以由正整数区间[k1,k2],k2>k1>0内的整数求和而来。
0<m<2k 时,[2k-m, m+2k]有效;m>=2k 时,[m+1-2k, m+2k]有效。即m>0, n至少可以由一个正整数区间[k1,k2],k2>k1>0内的整数求和而来。
证明结束!
最后,不足之处,不吝赐教!