LeetCode第 30 場雙週賽題解
5177. 轉變日期格式
思路:按照空格劃分出年月日,年已經是數字形式,不需要做處理。月是英文單詞的縮寫,需要對應到相應的數字。
對於日,只需要把結尾的英文序數詞去掉,只留下數字即可。 這裡要注意月和日可能是個位數,這種情況下要在前面補一個0.
class Solution { string months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; public: string reformatDate(string date) { string day, month, year, res; int split = 0; //用雙指標劃分出字串 while(isdigit(date[split])) { day += date[split]; ++split; } if(day.size() < 2) { //如果是個位數,在前面補一個0 day = '0' + day; } while(date[split] != ' ') { //指標繼續移動,劃分下一個字串(月) ++split; } ++split; while(date[split] != ' ') { month += date[split]; ++split; } for(int i = 0; i < 12; ++i) { //把月份的單詞對應到相應的數字上 if(months[i] == month) { month = to_string(i + 1); } } if(month.size() < 2) { //如果是個位數,在前面補一個0 month = '0' + month; } ++split; year = date.substr(split); //剩下的部分就是年 //printf("%s-%s-%s", year.c_str(), month.c_str(), day.c_str()); res = year + '-' + month + '-' + day; return res; } };
這題劃分字串也可以不用雙指標,用stringstream可以直接以空格為分隔符劃分三個字串。
程式碼如下:
class Solution { string months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; public: string reformatDate(string date) { stringstream ss(date); //用一個stringstream劃分date string day, month, year, temp, res; //temp表示每一個劃分出的字串 while(getline(ss, temp, ' ')) { //使用空格作為分隔符 if(isdigit(temp[0]) && !isdigit(temp.back())) { //如果某個劃分出的字串temp第一個字元是數字而最後一個不是,則說明現在劃分出的字串是日 while(!isdigit(temp.back())) { //刪掉末尾的英文序數詞 temp.pop_back(); } day = temp; if(day.size() < 2) { //如果是個位數,在前面補個0 day = '0' + day; } } else if(!isdigit(temp[0]) && !isdigit(temp.back())) { //如果某個字串第一個字元和末尾都不是數字,說明現在劃分出的字串是月 for(int i = 0; i < 12; ++i) { if(months[i] == temp) { month = to_string(i + 1); //把英文單詞轉化成對應的數字 break; } } if(month.size() < 2) { //如果是個位數,在前面補個0 month = '0' + month; } } else if(isdigit(temp[0]) && isdigit(temp.back())) { //首末都是數字,說明是年,不作處理 year = temp; } } res = year + '-' + month + '-' + day; return res; } };
這題用python做會方便很多:
class Solution: def reformatDate(self, date: str) -> str: day, month, year = date.split(' ') Months = {"Jan":"01", "Feb":"02", "Mar":"03", "Apr":"04", "May":"05", "Jun":"06", "Jul":"07", "Aug":"08", "Sep":"09", "Oct":"10", "Nov":"11", "Dec":"12"} month = Months[month] day = day[:2] if day[1].isdigit() else '0' + str(day[0]) return '-'.join([year, month, day])
5445. 子陣列和排序後的區間和
這題題意是給定一個數組,求出他的所有子陣列的和的可能,再對子陣列的和排序組成新陣列,
對於新陣列中下標從left到right(left和right給定)的陣列再進算和並返回。
要計算所有子陣列和,最簡單的想法是列舉所有子陣列的起點和終點,再計算這一段的元素和。
同時列舉起點和終點,再在起點和終點裡計算和需要O(n^3)的複雜度。超時。
計運算元陣列和,常見的方法是用一個字首和陣列來優化時間。
額外用一個數組preSum表示每個元素的字首和,字首和就是從陣列第一個元素到當前元素的和。
比如preSum[3]就是nums[0] + nums[1] + nums[2] + nums[3]。
這樣做的好處就是,我們枚舉了起點i和終點j之後,要計算這一段子陣列的和不需要逐個相加,
可以直接用preSum[j] - preSum[i]得到子陣列和,這樣時間就壓縮到了O(n^2).
程式碼如下:
class Solution {
typedef __int64_t bignum; //這題資料很大,我們用__int64_t
bignum mod = 1e9 + 7; //返回結果要對1e9 + 7取餘
public:
int rangeSum(vector<int>& nums, int n, int left, int right) {
int size = nums.size();
vector<bignum> preSum(size);
preSum[0] = nums[0];
for(int i = 1; i < size; ++i) {
preSum[i] = preSum[i - 1] + nums[i]; //對所有元素計算字首和
}
vector<bignum> sums; //sums陣列儲存所有的子陣列和
for(int i = 0; i < size; ++i) {
sums.push_back(preSum[i]); //每一個字首和都是一個子陣列和
for(int j = i + 1; j < size; ++j) {
sums.push_back(preSum[j] - preSum[i]); //列舉所有起點和終點,計運算元陣列的和
}
}
//for(auto x : sums) cout << x << ' ';
sort(sums.begin(), sums.end());
bignum res = 0;
for(int i = left - 1; i <= right - 1; ++i) {
res += sums[i];
}
return res % mod;
}
};
最後一個for迴圈也可以這麼寫:
for(int i = left - 1; i <= right - 1; ++i) {
res += sums[i];
if(res >= mod) {
res -= mod;
}
}
return res;
5446. 三次操作後最大值與最小值的最小差
這是一道智商題,如果能發現規律就很好做。
要求出三次操作後最大值和最小值的差的最小值。
我們就要讓三次操作之後最大值和最小值儘可能接近,也就是儘可能讓最大值小一點,最小值大一點。
所以三次操作都是對陣列的最大值(或者前兩個或前三個最大值)或者對陣列的最小值(或者前兩個或前三個最小值)進行操作。
這一點很好理解,如果對一箇中間元素進行操作,則對於最大值和最小值的差並無影響。
由於只有三次操作,且都是對於最小或最大的幾個數進行操作,所以一共只有四種情況:
-
三次操作將最小的三個數改為第四小的數。 修改最小的三個數,修改後的值一定是改為第四小的數,
因為這樣才會讓最大值和最小值(原來的第四小的值)的差最小。 -
兩次操作將最小的兩個數改為第三小的數,一次操作將最大的數改為第二小的數。 這也是一種縮小最大值和最小值的差的方法。
-
一次操作將最小的數改為第二小的數,兩次操作將最大的兩個數改為第三大的數。
-
三次操作將最大的三個數改為第四大的數。
最終的結果,就是上述四種情況的最小值!
注意,如果一共只有四個數或者更少,直接返回0即可,因為肯定可以讓最大值和最小值的差相等。
程式碼如下:
class Solution {
public:
int minDifference(vector<int>& nums) {
int size = nums.size();
if(size <= 4) {
return 0;
}
sort(nums.begin(), nums.end());
int res = INT_MAX;
for(int i = 0; i <= 3; ++i) { //列舉四種情況
res = min(res, nums[size - 4 + i] - nums[i]); //更新修改後的最大值和最小值的差的最小值
}
return res;
}
};
5447. 石子游戲 IV
題意是給定石頭數量,alice和bob每次都只能拿平方數個石頭,如果某次操作某人拿走了所有的石頭,則另一個玩家輸。
典型DP問題: 用一個數組dp表示石頭數和alice是否穩贏的關係,比如dp[i]表示有i個石頭時alice是否穩贏(是返回true,否返回false)。
-
顯然,當i是0時,alice是輸得,因為題目說了沒有石頭時,無法操作的玩家輸。
-
當i是一個平方數時,aclie穩贏,因為只要alice把石頭全部拿走,bob就輸了。
-
對於其他情況,就需要判斷從當前石頭裡拿走所有可以拿的平方數個石頭的方案裡,對方是否一定輸。
只要有一種拿平方數的方案裡,對方一定輸,就可以返回true,否則,遍歷完所有可以拿的方案對方都不輸,就返回false。
程式碼如下:
class Solution {
public:
bool winnerSquareGame(int n) {
vector<bool> dp(n + 1); //dp[i]表示有i個石頭時alice是否穩贏
for(int i = 0; i <= n; ++i) { //從小到大遞推計算dp陣列
for(int j = 1; j * j + i <= n; ++j) { //列舉可以拿的石頭數,j * j是當前拿的石頭數
if(dp[i] == false) { //如果只有i個石頭時必輸
dp[j * j + i] = true; //那麼有j * j + i個石頭的時候就必贏,因為alice可以拿走j * j個石頭,這樣bob就必輸
}
}
}
return dp[n]; //返回有n個石頭時alice是否贏
}
};