输出和为n的所有的连续自然数序列
输出和为n的所有的连续自然数序列
如 n = 9:
9
4 5
2 3 4
《编程之美》的题目(2.21只考加法的面试题),去年曾经写过本题的代码,后来不知道把代码放哪里了。按以前的思路,重写了下代码,写完后翻了下书,结果发现与书上要求的还不一样。
假设,拆分成的数列为:m, m+1, … m+k-1
则 n = (m + m + k - 1) * k / 2 或 2*n = (2*m + k - 1) * k
显然就是求2*n的所有因子,最简单的方法就是暴力搜索:
对公式2*n = (2*m + k - 1) * k 进行转变,可得下面几种方法:
方法一:
2*m + k – 1 = 2 * n / k
从k = 1 开始判断 k 是否是 2 * n的因子,当 k > 2 * n / k 时终止
(由于2*m + k – 1和 k奇偶性相反,输出结果前还要判断k与2 * n / k是否奇偶性相反)
循环次数约为:sqrt(2*n)
方法二:
m = (n – k * (k - 1) / 2) / k > 0
从k = 1 开始判断 k 是否是(n – k * (k - 1) / 2)的因子,当m <= 0时终止
循环次数约为:sqrt(2*n)
上面两种方法,效率相差不大,并且都不高,对 n = 2^30,这种不能拆分的数,要进行大量的计算。注意到2*m + k – 1和 k奇偶性相反,可以先将2 * n的所有质因子2提取出来,假设总共有t个2,则 2 * n = 2^t * nn(nn为奇数),问题转为求nn的所有因子,假设nn = a * b (a <= b),由于 2 * m + k – 1 一定大于k,由2 * n = 2^t * a * b可得两组2*n的两组因子(2^t * a, b) 和 (a, 2^t * b) 这两组数中,较小的那个就是k。采用这种方法的好处是,对偶数能极大的减少计算,特别是对n = 2^30,可以不进行任何除法计算。
对 nn求因子,可以采用上面的方法一和方法二,于是得到方法三和方法四。
方法三:
2 * n = 2^t * nn 奇数 nn = i * j (i <= j) -> j = nn / i
从i = 1开始(每次递增2),判断i是否是nn的因子,当 j > i 时终止。
循环次数约为:sqrt(nn) / 2
方法四:
2 * n = 2^t * nn 奇数 nn = i * (i + j) (j >= 0) -> j = (nn – i * i) / i >= 0
从i = 1开始(每次递增2),判断i是否是(nn – i * i) / i的因子,当 j < 0时终止。
循环次数约为:sqrt(nn) / 2
方法五:
既然是求所有的因子,那么最好的方法当然是利用质因子进行组合。
下面的代码存在一些需要优化的地方,比如存在重复计算、防止编译器对n / i 和n % i进行两次除法计算。如果数不是太大,可以缓存所有的质因子。
另见: 输出自然数n的所有因子。
(下面的代码,要将函数参数改为unsigned的话,需要将i=1的情况单独列出来讨论,即可保证计算过程中不会发生溢出。)
void output(int beg, int len) { printf("%d-%d\n", beg, beg + len - 1); }
inline void output2(int i, int j) { output((i - j + 1) / 2u, j); }
//方法三
void seq3(int n)
{ //2*n = (2*m+k-1)*k //m, m+1, ... m+k-1
if (n <= 0) return; //
unsigned count = 1;
while (n % 2u == 0) { n /= 2u; ++count; } //去除质因子2,设f = 2^x
for (unsigned i = 1; ;i += 2) { //获取nn的所有因子 nn = i * j 且 i <= j
unsigned j = n / i;
if (i > j) break;
if (n % i) continue;
output2(j << count, i); //k=i 2m+k-1=j*2*f
if (i == j) break;
unsigned t = i << count;
if (t > j) output2(t, j);
else output2(j, t);
}
printf("\n");
}
//方法四
void seq4(int n)
{ //2*n = (2*m+k-1)*k //m, m+1, ... m+k-1
if (n <= 0) return; //
unsigned count = 1;
while (n % 2u == 0) { n /= 2u; ++count; } //去除质因子2,设f = 2^x
n -= 1 * 1;
for (unsigned i = 1; n >= 0; n -= 4 * i + 4, i += 2) { //获取nn的所有因子
if (n % i == 0) { // nn = (i+a)*i -> a = (nn - i * i) / i
unsigned j = n / i + i;
output2(j << count, i); //k=i 2m+k-1=j*2*f
if (i == j) continue;
unsigned t = i << count;
if (t > j) output2(t, j);
else output2(j, t);
}
}
printf("\n");
}
//方法五
unsigned gn = 0; //值为 2 * n
static void factor(unsigned n, unsigned k = 1, unsigned beg = 3)
{
assert(n & 1);
assert(beg & 1);
if (n == 1) {
unsigned t = ::gn / k;
if (t > k) output2(t, k);
else output2(k, t);
return;
}
assert(n >= beg);
for (unsigned i = beg, count = 0; ;i += 2) {
//if (n % i) {
// if (n / i >= i) continue;
// factor(1, k, n); // n是质数
// factor(1, k * n, n);
// return;
//}
if (i > n / i) {
factor(1, k); // n是质数,
factor(1, k * n);
return;
}
if (n % i) continue;
++count;
n /= i;
while (n % i == 0) { ++count; n /= i; }
for (unsigned j = 0, f = k; j <= count; ++j, f *= i)
factor(n, f, i + 2);
return;
}
}
void seq(int n)
{ //2*n = (2*m+k-1)*k //m, m+1, ... m+k-1
if (n <= 0) return;
::gn = n * 2u;
while (n % 2u == 0) { n /= 2u;} //去除质因子2
factor(n);
printf("\n");
}
作者: flyinghearts
出处: http://www.cnblogs.com/flyinghearts/
本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。