九度OJ-题目1354:和为S的连续正数序列

题目链接地址:

九度OJ-题目1354:和为S的连续正数序列


题目描述:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输入:
输入有多组数据。
每组数据仅包括1个整数S(S<=1,000,000)。如果S为负数时,则结束输入。

输出:
对应每组数据,若不存在和为S的连续正数序列,则输出“Pity!”;否则,按照开始数字从小到大的顺序,输出所有和为S的连续正数序列。每组数据末尾以“#”号结束。

样例输入:
4
5
100
-1

样例输出:
Pity!
#
2  3
#
9  10  11  12  13  14  15  16
18  19  20  21  22
#


解题思路:

求和等于S的连续正数序列,等价于找一个公差为1和为S的等差数列。于是我想到了用等差数列求和公式:(a1 + an) * n / 2 = S,但是这里有a1,an,n三个变量,于是我就卡在这里了。。。

后来上作者的博客 程序员面试题精选100题(26)-和为n连续正数序列[算法] 看了一下,由于实在想不到好的算法了,就采纳了作者的算法。
算法一
和等于S的连续正数序列至少有两个元素,从序列[1,2]开始遍历。将序列和与S进行比较,分以下3种情况进行讨论:
(1)如果连续序列 [begin,…,end] 的和大于S,就将begin踢出该序列;
(2)如果连续序列 [begin,…,end] 的和小于S,就将end+1加入到该序列构成新的连续序列 [begin,…,end,end + 1];
(3)如果连续序列 [begin,…,end]的和等于S,就输出当前序列的和,并且将begin踢出该序列;
(4)重复(1),(2),(3)步骤,因为连续正数序列至少有两个元素,所以begin的最大值是S / 2。
举个栗子,如果S = 5,则求和等于S的连续正数序列的过程如下:
1)令begin = 1,end = 2,此时连续序列是[1,2],序列和是3,因为3 < S,所以将end的下一个元素3加入到当前序列中,新的连续序列是[1,2,3];
2)当前连续序列[1,2,3]的和是6,因为6 > S,所以将元素1踢出当前连续序列,新的连续序列是[2,3];
3) 当前连续序列[2,3]的和是5,因为5 = S,所以[2,3]是满足题意的连续序列,输出该序列后,将2踢出序列,新的连续序列是[3];
4) 当前连续序列[3]的begin是3,因为3 > S/2,所以终止遍历操作。
AC代码如下:

// 第一种解法
#include<stdio.h>
 
/**
*  打印和等于S的连续序列
*  @param begin  连续序列的开始值
*  @param end  连续序列的终止值
*  @return void
*/
void printContinueSequence(int begin,int end)
{
    int i = begin;
    printf("%d",i);
    for(i = begin + 1;i <= end;i++)
    {
        printf(" %d",i);
    }
    printf("\n");
}
 
/**
*  获取和等于S的连续序列
*  @param S  用户输入的S
*  @return numberOfSequence 和等于S的连续序列的个数
*/
int getContinueSequenceEqualsS(int S)
{
  int numberOfSequence = 0;
  int begin = 1;                // 连续序列的起点
  int end = 2;                  // 连续序列的终点
  int middle = S/2;             // 因为连续序列的长度最少为2,而begin + end <= S,所以begin最大的值为S/2
  int sum = begin + end;        // sum是连续序列[begin,end]的和
  while(begin <= middle)
  {
      if(sum == S)          // 如果当前序列和等于S,则输出连续序列[begin,end]
      {
          printContinueSequence(begin,end);
          sum = sum - begin;
          begin++;
          numberOfSequence++;
      }
      else if(sum < S)      // 如果当前的连续序列和小于S,则将end的下一个元素添加到sum中
      {
          end++;
          sum += end;
      }
      else                  // 如果当前的连续序列和大于S,则将begin踢出连续序列
      {
          sum = sum - begin;
          begin++;
      }
  }
  return numberOfSequence;
}
 
int main()
{
    int S;
    int number;
    while(EOF != scanf("%d",&S) && S >= 0)
    {
       number = getContinueSequenceEqualsS(S);
       if(0 == number)
       {
           printf("Pity!\n");
       }
       printf("#\n");
    }
    return 0;
}
 
/**************************************************************
    Problem: 1354
    User: blueshell
    Language: C++
    Result: Accepted
    Time:1060 ms
    Memory:1020 kb
****************************************************************/

算法二
后来在论坛讨论区 【1354】和为s的连续正数序列 看了3楼的解法后,顿时感觉到了算法的魅力所在。
下面简单介绍一下大牛的算法:
假设和为S的连续序列为[a1,a2,……,an],则可以的到S = a1+a2+…+an,
即S = (a1 + an) * n / 2,这又是等差数列的求和公式,我之前卡在这个公式的原因是没有想到an = a1 + n – 1 这个公式。
由S = (a1 + an) * n / 2和an = a1 + n – 1可以推导出
S = (a1 + a1 + n - 1) * n / 2
===> 2 * a1 + n – 1 = 2 * S / n
===> 2 * a1 = 2S/n – n + 1
===> a1 = (2S/n – n + 1) / 2
因为a1是正整数,而当a1是正整数时,可以得知(2S/n – n + 1) / 2和2S/n也都是正整数。
因为2*a1 > 0,所以2S/n – n + 1 > 0 ===> 2S/n – n >= 0 ===> n * n <= 2S
===> n <= sqrt(2S),
因为和等于S的连续正数序列至少有两个元素,所以n >= 2。
因此算法的流程就是:依次遍历每一个n(2 <= n <= sqrt(2S)),如果n的值能够满足2S/n和a1 = (2S/n – n + 1) / 2同时为正整数,则以a1为首元素,长度为n的连续正数序列的和就等于S。
AC代码如下:

// 第二种解法
#include<stdio.h>
#include<math.h>
 
/**
* 打印和等于S的连续序列
* @param begin  连续序列的开始值
* @param end  连续序列的终止值
* @return void
*/
void printContinueSequence(int begin,int end)
{
    int i = begin;
    printf("%d",i);
    for(i = begin + 1;i <= end;i++)
    {
        printf(" %d",i);
    }
    printf("\n");
}
 
/**
* 获取和等于S的连续序列
* @param S  用户输入的S
* @return numberOfSequence  和等于S的连续序列的个数
*/
int getContinueSequenceEqualsS(int S)
{
   int numberOfSequence = 0;
   int n;       // 和等于S的连续序列的长度
   int a1;      // 和等于S的连续序列的第1项
   for(n = sqrt(2 * S); n >= 2; n--) // n越大,a1越小,因此n的初始值为sqrt(2 * S),最终值为2
   {
     if((2 * S) % n == 0)  // 保证2S / n是整数
     {
         if(0 == (2 * S / n - n + 1) % 2)  // 保证a1是整数
         {
           a1 = (2 * S / n - n + 1) / 2;
           numberOfSequence++;
           printContinueSequence(a1,a1 + n - 1);
         }
     }
   }
   return numberOfSequence;
}
 
int main()
{
    int S;
    int number;
    while(EOF != scanf("%d",&S) && S >= 0)
    {
       number = getContinueSequenceEqualsS(S);
       if(0 == number)
       {
           printf("Pity!\n");
       }
       printf("#\n");
    }
    return 0;
}
 
/**************************************************************
    Problem: 1354
    User: blueshell
    Language: C++
    Result: Accepted
    Time:80 ms
    Memory:1032 kb
****************************************************************/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值