日期差值 - 九度教程第6题

这篇博客探讨了如何计算两个日期之间的天数差,源自九度教程第6题。文章介绍了预处理日期差值以优化计算效率的方法,特别强调了闰年的判断逻辑和代码实现中的关键点,如使用三维数组作为哈希存储日期差,以及处理输入数据的技巧。此外,还提到了避免栈溢出的全局变量使用策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日期差值 - 九度教程第6题

题目

时间限制:1 秒 内存限制:32 兆 特殊判题:否
题目描述:
有两个日期,求两个日期之间的天数,如果两个日期是连续的我们规定他们之间的天数为两天
输入:
有多组数据,每组数据有两行,分别表示两个日期,形式为YYYYMMDD
输出:
每组数据输出一行,即日期差值
样例输入:
20110412
20110422
样例输出:
11
来源:
2009年上海交通大学计算机研究生机试真题

该例题考察了日期类问题中最基本的问题——求两个日期间的天数差,即求分别以两个特定日期为界的日期区间的长度。解决这类区间问题有一个统一的思想——把原区间问题统一到起点确定的区间问题上去。
在该例中,不妨把问题统一到特定日期与一个原点时间(如0000年1月1日)的天数差,当要求两个特定的日期之间的天数差时,只要将它们与原点日期的天数差相减,便能得到这两个特定日期之间的天数差(必要时加绝对值)。这样做有一个巨大的好处——预处理。
可以在程序真正开始处理输入数据之前,预处理出所有日期与原点日期之间的天数差并保存起来。当数据真正开始输入时只需要用O(1)的时间复杂度将保存的数据读出,稍加处理便能得到答案。
值得一提的是,预处理也是空间换时间的重要手段(保存预处理所得数据所需的内存来换取实时处理所需要的时间消耗)。

注意

闰年:四年一闰,百年不闰,四百年又闰
逻辑语言:(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
当逻辑表达式为true时,其为闰年;反之则不是闰年。闰年并不严格的按照四年一次的规律出现,在某种情况下也可能出现两个相邻闰年相隔八年的情况(如1896年与1904年)。

代码

#include <stdio.h>
#include <math.h>
#define ISLEAPYEAP(x) (x%100!=0 && x%4 == 0) || x%400==0 ? 1 : 0
// 定义宏判断是否是闰年,方便计算每月天数

int dayOfMonth[13][2] = {
    0,0,
    31,31,
    28,29,
    31,31,
    30,30,
    31,31,
    30,30,
    31,31,
    31,31,
    30,30,
    31,31,
    30,30,
    31,31
}; //预存每月的天数,注意二月配合宏定义作特殊处理

struct Date { //日期类,方便日期的推移
    int Day;
    int Month;
    int Year;
    void nextDay() { //计算下一天的日期
        Day ++;
        if (Day > dayOfMonth[Month][ ISLEAPYEAP(Year) ]) { //若日数超过了当月最大日数
            Day = 1;
            Month ++; //进入下一月
            if (Month > 12) { //月数超过12
                Month = 1;
                Year ++; //进入下一年
            }
        }
    }
};

int buf[5001][13][32]; //保存预处理的天数

int Abs(int x) { //求绝对值
    return x < 0 ? -x : x;
}

int main () {
    Date tmp;
    int cnt = 0; //天数计数
    tmp.Day = 1;
    tmp.Month = 1;
    tmp.Year = 0; //初始化日期类对象为0年1月1日

    while(tmp.Year != 5001) { //日期不超过5000年
        buf[tmp.Year][tmp.Month][tmp.Day] = cnt; //将该日与0年1月1日的天数差保存起来
        tmp.nextDay(); //计算下一天日期
        cnt ++; //计数器累加,每经过一天计数器即+1,代表与原点日期的间隔又增加一天
    }

    int d1 , m1 , y1;
    int d2 , m2 , y2;
    while (scanf ("%4d%2d%2d",&y1,&m1,&d1) != EOF) {
        scanf ("%4d%2d%2d",&y2,&m2,&d2); //读入要计算的两个日期
        printf("%d\n",Abs(buf[y2][m2][d2] - buf[y1][m1][d1]) + 1); //用预处理的数据计算两日期差值,注意需对其求绝对值
    }
    return 0;
}

这段代码还有三个值得我们注意的地方。

  1. 在保存某个特定日期与原点日期的天数差时,使用了三维数组,用年、月、日分别表示该数组下标,这便将日期本身与其存储地址联系了起来,这样在存取时不必再为查找其所在的存储地址而大费周章,而只需要直接利用它的年月日数字即可找到我们保存的值。将数据本身与数据存储地址联系起来,这是Hash的基本思想,也是Hash的一种基本方式。只不过这里的Hash不会产生冲突,并不需要为解决其冲突而另外下功夫。
  2. 该例程的输入采用了某种技巧。因为题面规定用一个连续的八位数来代替日期,使用%4d来读取该八位数的前四位并赋值给代表年的变量,同理使用%2d%2d来读取其它后四位并两两赋值给月日。这种利用在%d之间插入数字来读取特定位数的数字的技巧值得利用。
  3. 将buf[5001][13][32]这个相对比较耗费内存的数组定义成全局变量。由于需要耗费大量的内存,若在main函数(其它函数也一样)之中定义该数组,其函数所可以使用的栈空间将不足以提供如此庞大的内存,出现栈溢出,导致程序异常终止。所以凡是涉及此类需要开辟大量内存空间的情况,都必须在函数体外定义,即定义为全局变量。或者在函数中使用malloc等函数动态申请变量空间。
//将malloc得到的内存首地址通过函数的返回值返回到主函数。
#include <stdio.h>
#include <malloc.h>
#include <string.h>
char* test()
{
    char *p;
    p = (char*)malloc(10 * sizeof(char));
    strcpy(p, "123456789" );
    return p;
}
void main()
{
    char *str = NULL ;
    str = test();
    printf("%s\n", str);
    free(str);
}
//malloc函数是一种分配长度为num_bytes字节的内存块的函数,
//可以向系统申请分配指定size个字节的内存空间。malloc的全称是memory allocation,动态内存分配
//函数返回的类型是void*类型。void*表示未确定的类型。
//C,C++规定,void* 类型可以通过类型转换强制转换为任何其它类型的指针。
//使用完申请的内存后需要用free(*p)释放内存并且将指针P=NULL,防止野指针;

其他解法

#include<iostream>
#include<cmath>
#include <cstdlib>
using namespace std;
 
int main()
{
    int month[12] = {31,28,31,30,31,30,31,31,30,31,30,31};    //月份数组
    int s1 = 0,s2 = 0;
    while (cin>>s1>>s2)
    {
        int n = 0;    //相隔天数
        int a,b,c,d,e,f;
        int y = 0;    //标记相隔年数
        int m = 0;    //标记相隔月数
        a = s1 / 10000;    //第一个年份
        b = s2 / 10000;    //第二个年份
        c = (s1 % 10000) / 100;        //第一个月份
        d = (s2 % 10000) / 100;        //第二个月份
        e = (s1 % 10000) % 100;        //第一个天数
        f = (s2 % 10000) % 100;        //第二个天数 
        y = abs(b - a);
        
        if (y == 0)    //两年在同一年
        {
            m = abs(d - c);
            if (m == 0)    //两月在同一月
            {
                n = abs(f - e) + 1;
            }
            else    //两月不在同一月
            {
                if (b % 4 == 0  && b % 100 != 0 || b % 400 == 0)    //当前年是闰年
                {
                    month[1] = 29;
                    for (int i = c; i < d; i++)
                    {
                        n = n + month[i - 1];
                    }
                    n = n - e + 1 + f;    //计算间隔天数
                }
                else    //当前年不是闰年
                {
                    month[1] = 28; 
                    for (int i = c; i < d; i++)
                    {
                        n = n + month[i - 1];
                    }
                    n = n - e + 1 + f;
                }
            }
        }
        else    //两年不在同一年
        {
            int j = 0;    //闰年个数
            for(int i = a + 1; i < b; i ++)
            {
                if (i % 4 == 0  && i % 100 != 0 || i % 400 == 0)    //判断闰年
                {
                    j ++;
                }
            }
            n = abs(b - a - 1) * 365 + j;    //相隔年数转化为天数
            
            if (a % 4 == 0  && a % 100 != 0 || a % 400 == 0)    //起始年为闰年
            {
                month[1] = 29;
                for(int i = c; i <= 12; i ++)
                {
                    n = n + month[i - 1];
                }
                n = n - e + 1;
            }
            else    //起始年不是闰年
            {
                month[1] = 28;
                for(int i = c; i <= 12; i ++)
                {
                    n = n + month[i - 1];
                }
                n = n - e + 1;
            }
            if (b % 4 == 0  && b % 100 != 0 || b % 400 == 0)    //结束年为闰年
            {
                month[1] = 29;
                for(int i = 1; i < d; i ++)
                {
                    n = n + month[i - 1];
                }
                n = n + f;
            }
            else    //结束年不是闰年
            {
                month[1] = 28;
                for(int i = 1; i < d; i ++)
                {
                    n = n + month[i - 1];
                }
                n = n + f;
            }
        }
        cout<<n<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值