1. 程式人生 > >(三)王道機試指南___日期類計算

(三)王道機試指南___日期類計算

算例1

  • 題目描述

  • 解題思路

①解決這類區間問題有一個統一的思想——把原區間問題統一到確定的區間問題,即把問題統一到特定的日期與一個原點(如0000年1 月1日)的天數差,然後將兩個天數差相減,這有一個巨大的好處,就是預處理,我們可以在程式真正開始處理輸入資料之前,預處理出所有日期與原點日期之間的天數差並儲存起來。當資料真正開始輸入時,我們只需要用O(1)的時間複雜度將儲存的資料讀出,稍加處理便能得到答案。(預處理是一種空間換時間的重要手段)

②閏年判斷:year%100!=0&&year%4==0 || year%400==0

  • 解題程式碼

#include <stdio.h>
#define ISYEAP(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][ISYEAP(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;
}
  • 注意點

①案例程式碼和自己寫的程式碼相比有很多的優點,比如巨集定義閏年判斷,DayOfMonth二維陣列的定義,儲存預處理的天數buf陣列的引入,以及整個思路的轉換就避免了很多閏年天數月數的計算,十分值得學習!!

記!輸入時在%d之間插入數字來讀取特定位數的數字,輸出時可以在數字前加0表示空位補0(%04d)

記!善於運用多維陣列儲存資料,例如月份、天數、星期

④buf陣列需要耗費大量記憶體,若在main函式中定義,容易導致棧溢位,因此凡是涉及此類需要開闢大量記憶體空間的情況,我們都必須在函式體外定義,即定義為全域性變數,或者在函式中使用malloc等函式動態申請變數空間

算例2

  • 題目描述

  • 解題思路

①利用上題中buf思想,由於星期是迴圈的,故可以用已知的日期星期號推輸入日期的星期號

  • 解題程式碼

#include <stdio.h>
#include <string.h>
#define ISYEAP(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][ISYEAP(Year)]) {
			Day = 1;
			Month++;
			if (Month > 12) {
				Month = 1;
				Year++;
			}
		}
	}
};
int buf[3001][13][32];
char monthName[13][20] = {
	"" ,
	"January" ,
	"February" ,
	"March" ,
	"April" ,
	"May" ,
	"June" ,
	"July" ,
	"August" ,
	"September" ,
	"October" ,
	"November" ,
	"December"
}; //月名每個月名對應下標1到12
char weekName[7][20] = {
	"Sunday" ,
	"Monday" ,
	"Tuesday" ,
	"Wednesday" ,
	"Thursday" ,
	"Friday" ,
	"Saturday"
}; //周名每個周名對應下標0到6
int main() {
	Date tmp;
	int cnt = 0;
	tmp.Day = 1;
	tmp.Month = 1;
	tmp.Year = 0;
	while (tmp.Year != 3001) {
		buf[tmp.Year][tmp.Month][tmp.Day] = cnt;
		tmp.nextDay();
		cnt++;
	} //以上與上題一致,預處理出每一天與原點日期的天數差
	int d, m, y;
	char s[20];
	while (scanf("%d%s%d", &d, s, &y) != EOF) {
		for (m = 1;m <= 12;m++) {
			if (strcmp(s, monthName[m]) == 0) {
				break; //將輸入字串與月名比較得出月數
			}
		}
		int days = buf[y][m][d] - buf[2012][7][16]; //計算給定日期與今日日期的天數間隔(注意可能為負)
			days += 1; //今天(2012.7.16)為星期一,對應陣列下標為1,則計算1經過days天后的下標
			puts(weekName[(days % 7 + 7) % 7]); //將計算後得出的下標用7對其取模,並且保證其為非負數, 則該下標即為答案所對應的下標, 輸出即可
	}
	return 0;
}
  • 注意點

①關於日期、月份數的儲存可以使用陣列,而不是switch-case