LeetCode第177場周賽(Weekly Contest 177)解題報告

這週週日的周賽,大家強的離譜,一堆人AK,完了完了,掉分之旅。最後一題主要是思維題,即分析好情況,其實很容易解決,奈何等我有想法的時候,已經快要結束沒時間寫代碼了,藍瘦。

第一題:模擬(或者python調用函數庫)。

第二題:DFS 或者 並查集。

第三題:暴力枚舉。

第四題:思維 + 貪心。

詳細題解如下。


1.日期之間隔幾天(Number Of Days Between Two Dates)

          AC代碼(C++)

2. 驗證二叉樹(Validate Binary Tree Nodes)

          AC代碼(方法一  DFS  C++)

          AC代碼(方法二  並查集  C++)

3.最接近的因數(Closest Divisors)

          AC代碼(C++)

4.形成三的最大倍數(Largest Multiple of Three)

          AC代碼(C++)


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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章