這週週日的周賽,大家強的離譜,一堆人AK,完了完了,掉分之旅。最後一題主要是思維題,即分析好情況,其實很容易解決,奈何等我有想法的時候,已經快要結束沒時間寫代碼了,藍瘦。
第一題:模擬(或者python調用函數庫)。
第二題:DFS 或者 並查集。
第三題:暴力枚舉。
第四題:思維 + 貪心。
詳細題解如下。
1.日期之間隔幾天(Number Of Days Between Two Dates)
2. 驗證二叉樹(Validate Binary Tree Nodes)
4.形成三的最大倍數(Largest Multiple of Three)
LeetCode第177場周賽地址:
https://leetcode-cn.com/contest/weekly-contest-177
1.日期之間隔幾天(Number Of Days Between Two Dates)
題目鏈接
https://leetcode-cn.com/problems/number-of-days-between-two-dates/
題意
請你編寫一個程序來計算兩個日期之間隔了多少天。
日期以字符串形式給出,格式爲
YYYY-MM-DD
,如示例所示。示例 1:
輸入:date1 = "2019-06-29", date2 = "2019-06-30" 輸出:1
示例 2:
輸入:date1 = "2020-01-15", date2 = "2019-12-31" 輸出:15
提示:
- 給定的日期是
1971
年到2100
年之間的有效日期
解題思路
模擬,我們先找出兩個年份中的最小年份
然後分別計算,兩個日期到距離這一年有多少天,然後兩個天數作差的絕對值就是答案
幾個要點1)某個日期,先計算年份,隔了那幾年,注意要判斷某一年可能是閏年,閏年是 366天,非閏年是 265天。2)計算月份,算已經過了幾個月了,加上這些月的天數(注意閏年的二月是 29 天)。3)最後再加上 日即可。
這樣子就算出了一個日期到某一年(兩個日期中的最小年)的天數
(如果用python,是有一個函數庫可以調用的,就很快解決這個問題)
AC代碼(C++)
class Solution {
public:
int days[12] = {31, 28, 31, 30, 31,30, 31, 31,30,31,30,31};
int daysBetweenDates(string date1, string date2) {
int y1, m1, d1, y2, m2, d2;
// 先變爲數字
y1 = (date1[0] - '0') * 1000 + (date1[1] - '0') * 100 + (date1[2] - '0') * 10 + (date1[3] - '0');
m1 = (date1[5] - '0') * 10 + (date1[6] - '0');
d1 = (date1[8] - '0') * 10 + (date1[9] - '0');
y2 = (date2[0] - '0') * 1000 + (date2[1] - '0') * 100 + (date2[2] - '0') * 10 + (date2[3] - '0');
m2 = (date2[5] - '0') * 10 + (date2[6] - '0');
d2 = (date2[8] - '0') * 10 + (date2[9] - '0');
int tep = min(y1, y2);
int v1 = 0, v2 = 0;
// 計算第一個 日期 到 tep 的天數
// 先是年
for(int i = tep; i < y1; ++i){
if((i % 4 == 0 && i %100 != 0) || i % 400 == 0) v1 += 366;
else v1 += 365;
}
// 月
for(int i = 1;i < m1; ++i) {
if(i == 2) {
if((y1 % 4 == 0 && y1 %100 != 0) || y1 % 400 == 0) v1 += 29;
else v1 += 28;
}
else {
v1 += days[i - 1];
}
}
// 日
v1 += d1;
// 下面類似
for(int i = tep; i < y2; ++i){
if((i % 4 == 0 && i %100 != 0) || i % 400 == 0) v2 += 366;
else v2 += 365;
}
for(int i = 1;i < m2; ++i) {
if(i == 2) {
if((y2 % 4 == 0 && y2 %100 != 0) || y2 % 400 == 0) v2 += 29;
else v2 += 28;
}
else {
v2 += days[i - 1];
}
}
v2 += d2;
// 最後答案就是,兩個日期的差的絕對值
return abs(v1 - v2);
}
};
2. 驗證二叉樹(Validate Binary Tree Nodes)
題目鏈接
https://leetcode-cn.com/problems/validate-binary-tree-nodes/
題意
二叉樹上有 n 個節點,按從 0 到 n - 1 編號,其中節點 i 的兩個子節點分別是 leftChild[i] 和 rightChild[i]。
只有 所有 節點能夠形成且 只 形成 一顆 有效的二叉樹時,返回 true;否則返回 false。
如果節點 i 沒有左子節點,那麼 leftChild[i] 就等於 -1。右子節點也符合該規則。
注意:節點沒有值,本問題中僅僅使用節點編號。
示例 :所有示例具體看鏈接,有圖示
提示:
1 <= n <= 10^4
leftChild.length == rightChild.length == n
-1 <= leftChild[i], rightChild[i] <= n - 1
解題思路
方法一、DFS
根據題目要求的,返回 true的要求是,一個節點只能是一條子樹的,同時,只存在一個二叉樹,不能有多個。
因此想法,就是,利用DFS,從根節點 (0) 出發,走這顆樹的所有節點
1)如果發現已經有節點被走過了(說明這個節點,不是一個子樹的,也就是 示例 2 ,示例3 出現的情況),那就是 false
2)如果從根節點 0 走完了這棵樹,還發現還有點沒有走(那就是說明,不單單一顆樹,也就是示例 4 出現的情況),也是 false
方法二、並查集
方法一的要求是,我們知道某一個根節點(因爲要開始走,不能從葉節點開始走,這樣子會誤判),所以這就要求,0 一定是 根節點(雖然題目沒說,但是示例是這樣子的,因此方法一在這道題也是可以通過的)。
但是這樣子不完善,假如出現數據,0不是根節點,那方法一是會誤判的額。
因此還可以利用並查集,也就是將節點歸屬不同的集合。
1)出現環,或者,一個節點被不同子樹鏈接(也就是示例 2 和 示例 3)。那麼這就會出現,本來我們應該是讓當前節點 i,找到它的 左右節點 l 和 r,那這幾個節點本來應該是同一個集合的。
如果一開始,左右節點 l 和 r 的並查集父節點和 i 的父節點不同(說明這個節點是纔剛剛走到,也就是前面還沒走過),同時把這兩個子節點的父節點和 i 的父節點歸屬,表示同一個集合。
如果發現左右節點 l 和 r 的某個節點的並查集父節點和 i 的父節點 已經是相同了,說明 左右節點 l 和 r 中的某個 已經被走過了,那就是存在環,或者節點被不同子樹鏈接的情況(也就是 示例 2 ,示例3 出現的情況),那就是 false。
2)最後判斷所有集合個數(也就是二叉樹的個數),如果數量是 1,那就是 true,數量 > 1,說明不止一個二叉樹(也就是示例 4 出現的情況),也是 false。
利用並查集的方法,就不會要求,0 一定是根節點的情況。
並查集的具體學習,可以查看我的另一篇博客:並查集 詳細介紹
AC代碼(方法一 DFS C++)
class Solution {
public:
int vis[10010];
bool flag;
int N;
void dfs(int x, vector<int>& leftChild, vector<int>& rightChild){
if(flag == false) return;
vis[x] = 1;
int l = leftChild[x];
int r = rightChild[x];
if(l != -1) {
if(vis[l] == 1) {
flag = false;
return;
}
dfs(l, leftChild, rightChild);
}
if(r != -1) {
if(vis[r] == 1) {
flag = false;
return;
}
dfs(r, leftChild, rightChild);
}
}
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
N = n;
memset(vis, 0, sizeof(vis));
flag = true;
dfs(0, leftChild, rightChild);
// 如果走過一遍DFS後,發現是true,最後就判斷,是不是隻有一個二叉樹
if(flag){
for(int i = 0;i < n; ++i) {
if(vis[i] == 0){ // 如果還有沒走過的,說明不止一個二叉樹
flag = false;
break;
}
}
}
return flag;
}
};
AC代碼(方法二 並查集 C++)
const int MAXN = 1e4 + 10;
class Solution {
public:
int fa[MAXN];
// 找父節點
int findFa(int x) {
if(fa[x] != x)
fa[x] = findFa(fa[x]);
return fa[x];
}
// 將兩個合成一個集合
void Unio(int x, int y) {
x = findFa(x);
y = findFa(y);
if(x != y){
fa[y] = x;
}
}
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
for(int i = 0;i < n; ++i) fa[i] = i;
for(int i = 0;i < n; ++i) {
int l = leftChild[i], r = rightChild[i];
if(l != -1) {
if(findFa(l) == findFa(i)) return false; // 如果已經出現 == ,說明這個點之前走過,那就是 false
Unio(l, i); // 不然兩個結合成一個集合
}
if(r != -1) {
if(findFa(r) == findFa(i)) return false;
Unio(r, i);
}
}
// 計算集合的個數
int cnt = 0;
for(int i = 0;i < n; ++i)
if(findFa(i) == i)
++cnt;
return cnt == 1;
}
};
3.最接近的因數(Closest Divisors)
題目鏈接
https://leetcode-cn.com/problems/closest-divisors/
題意
給你一個整數 num,請你找出同時滿足下面全部要求的兩個整數:
- 兩數乘積等於 num + 1 或 num + 2
- 以絕對差進行度量,兩數大小最接近
你可以按任意順序返回這兩個整數。
示例 1:
輸入:num = 8 輸出:[3,3] 解釋:對於 num + 1 = 9,最接近的兩個因數是 3 & 3;對於 num + 2 = 10, 最接近的兩個因數是 2 & 5,因此返回 3 & 3 。
示例 2:
輸入:num = 123 輸出:[5,25]
提示:
- 1 <= num <= 10^9
解題分析
一開始的想法,就是找到所有的 num + 1 或者 num + 2 的因數,然後找出其中兩個因數作差絕對值最小的兩個因數,就是答案。
那麼根據題目的數據範圍,我們找因數的條件是 從 1 到 sqrt(num),那麼根據數據範圍,我們發現不會超時
所以直接暴力枚舉即可。
AC代碼(C++)
class Solution {
public:
vector<int> closestDivisors(int num) {
int a, b, diff = 1e9 + 2; // 初始化 diff 爲 最大值
int tep = num + 1;
for(int i = 1; i <= (int)sqrt(tep); ++i){
if(tep % i == 0) {
if(diff > abs(tep / i - i)) {
a = i, b = tep / i;
diff = abs(a - b);
}
}
}
tep = num + 2;
for(int i = 1; i <= (int)sqrt(tep); ++i){
if(tep % i == 0) {
if(diff > abs(tep / i - i)) {
a = i, b = tep / i;
diff = abs(a - b);
}
}
}
return {a, b};
}
};
4.形成三的最大倍數(Largest Multiple of Three)
題目鏈接
https://leetcode-cn.com/problems/largest-multiple-of-three/
題意
給你一個整數數組 digits,你可以通過按任意順序連接其中某些數字來形成 3 的倍數,請你返回所能得到的最大的 3 的倍數。
由於答案可能不在整數數據類型範圍內,請以字符串形式返回答案。
如果無法得到答案,請返回一個空字符串。
示例 1:
輸入:digits = [8,1,9] 輸出:"981"
示例 2:
輸入:digits = [8,6,7,1,0] 輸出:"8760"
示例 3:
輸入:digits = [1] 輸出:""
示例 4:
輸入:digits = [0,0,0,0,0,0] 輸出:"0"
提示:
1 <= digits.length <= 10^4
0 <= digits[i] <= 9
- 返回的結果不應包含不必要的前導零。
解題分析
這道題,是一道有關數學的思維題目:
首先,要懂幾個情況。
1)最大數,即,我們將出現的所有 0 - 9 的數字進行降序排序,即大的放在前面,小的放在後面,這樣子出來的數,就是最大數。
2)3的倍數:數字的每一位之和,如果是 3 的倍數,那麼這個數字也就是 3 的倍數
所以,我們先對數字的每一位求和(也就是 digits 數組中的元素之和,爲了驗證 3 的倍數
),同時對 digits 數組中出現的額 0 - 9 的每一個數字出現的次數進行統計(爲了後面輸出最大數)。
這裏要明白一個東西
對於求和 % 3 的餘數 是 1,我們就要把影響的元素去掉。有兩種可能:1)會出現一個數,本身就是 % 3 = 1,那就直接刪除這個數即可。2)如果都沒有本身就是 % 3 = 1的數。那我們不可能刪除 % 3 = 0 的數(本身就是 3 的倍數)。所以就考慮刪除 % 3 = 2 的數,那麼要刪除掉 兩個 餘數 爲 2 的數(這兩個餘數爲2 的數相加,也就是餘數爲 1)。
對於求和 % 3 的餘數 是 2的,我們分析也是,1)直接刪除一個% 3 = 2的數。2)沒有的話,刪除兩個 % 3 = 1 的數。
對於我們刪除,爲了使得結果最大
1)能刪除一個數,就不刪除兩個數
2)刪除的數,從餘數滿足要求的數中最小的刪,保證最大
餘數爲 1的數,可能有 1, 4, 7。餘數爲 2的數,可能有 2,5,8。(發現了也就是餘數爲 1,或者 2,就是以 1 或者 2開頭,隔 3 位)。
所以整個程序流程:
1)統計 digits
數組中每個元素(0 - 9) 的出現次數,同時求 digits 數組之和。
2)判斷餘數是 1 還是 2。
如果是 1,先考慮刪除一個餘數爲1。如果還是餘數1,(說明沒有餘數爲 1 的數可以刪除),那就刪除兩個 餘數爲 2 的數。
如果是 2,先考慮刪除一個餘數爲2。如果還是餘數2,(說明沒有餘數爲 2 的數可以刪除),那就刪除兩個 餘數爲 1 的數。
3)最後得到滿足要求(是 3 的倍數)的各個數字的保留情況(前面統計了出現次數,對應刪除的那就少掉幾次)。
4)爲了是最大數,我們從 9 開始 到 1(0比較特殊,後面再考慮),每個元素出現多少次,這個元素就放在前面幾位,比如 9 出現 2次,1 出現 3 次,那麼得到的結果是 99111。
5)最後考慮 0。如果我們前面發現得到的字符串還是 空字符串(也就是沒有 9 - 1) 的數字,同時,發現 0的個數有,那麼無論有幾個 0,我們輸出的都是 "0"。如果發現字符串不是空的,也就是前面不是前導零,那麼有幾個 0 ,就加在字符串後面。
AC代碼(C++)
class Solution {
public:
unordered_map<int, int> mp;
int sum = 0;
// 刪除餘數爲 m 的數,也就是 從小的考慮,所以是 m, m + 3, m + 3 + 3 ..
void del(int m){
for(int i = m;i < 9; i += 3){
if(mp[i]){ //有就刪除
sum -= i;
--mp[i];
return;
}
}
}
string largestMultipleOfThree(vector<int>& digits) {
mp.clear(), sum = 0;
for(auto d : digits){
sum += d;
++mp[d];
}
// cout << sum << endl;
// 刪除一個餘數爲 1的
if(sum % 3 == 1){
del(1);
}
// 還是餘數爲1,說明上一步沒有餘數爲1的數可以刪除。那就刪除餘數爲2 的 2個
if(sum % 3 == 1)
{
del(2), del(2);
}
// 當餘數爲 2的時候,也是一樣的考慮
if(sum % 3 == 2){
del(2);
}
if(sum % 3 == 2) {
del(1), del(1);
}
// 最後就是結果
string res = "";
for(int i = 9;i >= 1; --i){
// 從 9 開始,這個元素有幾個,就加幾個
while(mp[i] != 0){
char tep = i + '0';
res += tep;
--mp[i];
}
}
// 如果還是空字符串,同時 0 又有個數,那就是返回 0 (不然會有前導零)
if(res.size() == 0 && mp[0] > 0) return "0";
if(res.size() != 0)
{
while(mp[0] != 0){
res += '0';
--mp[0];
}
}
return res;
}
};