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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值