日期差值 - 九度教程第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;
}
这段代码还有三个值得我们注意的地方。
- 在保存某个特定日期与原点日期的天数差时,使用了三维数组,用年、月、日分别表示该数组下标,这便将日期本身与其存储地址联系了起来,这样在存取时不必再为查找其所在的存储地址而大费周章,而只需要直接利用它的年月日数字即可找到我们保存的值。将数据本身与数据存储地址联系起来,这是Hash的基本思想,也是Hash的一种基本方式。只不过这里的Hash不会产生冲突,并不需要为解决其冲突而另外下功夫。
- 该例程的输入采用了某种技巧。因为题面规定用一个连续的八位数来代替日期,使用%4d来读取该八位数的前四位并赋值给代表年的变量,同理使用%2d%2d来读取其它后四位并两两赋值给月日。这种利用在%d之间插入数字来读取特定位数的数字的技巧值得利用。
- 将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;
}