LeetCode第180场周赛(Weekly Contest 180)解题报告

哎,我也太菜了。其实题目还是算不难,但是自己还是脑子转的慢。第三题的二叉树方面的,是关于“二叉搜索树”的构造(在之前我还不会),因此,我直接跳到第四题,但是第四题想一个小时,思路大致方向对了,但是具体细节还是有些问题,比赛结束前 2 分钟才完整知道如何做,但是已经来不及敲代码。 

掉分了,掉了30分左右,哭泣。自己还是太菜了,赛后学习了一下“二叉搜索树”相关的,其实很简单。继续学习,继续努力吧。

(这次周赛,全球的参赛人数,一万多人,可怕。。。)

第一题:暴力。

第二题:模拟 + 暴力。

第三题:二叉搜索树,中序遍历 + 二叉搜索树的构造。

第四题:贪心(枚举 + 优先队列)。

详细题解如下。


1.矩阵中的幸运数(Lucky Numbers In A Matrix)

          AC代码(C++)

2. 设计一个支持增量操作的栈(Design A Stack With Increment Operation)

          AC代码(C++)

3.将二叉搜索树变平衡(Balance A Binary Search Tree)

          AC代码(C++)

4.最大的团队表现值(Maximum Performance Of A Team)

          AC代码(C++)


LeetCode第180场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-180/


1.矩阵中的幸运数(Lucky Numbers In A Matrix)

题目链接

https://leetcode-cn.com/problems/lucky-numbers-in-a-matrix/

题意

给你一个 m * n 的矩阵,矩阵中的数字 各不相同 。请你按 任意 顺序返回矩阵中的所有幸运数。

幸运数是指矩阵中满足同时下列两个条件的元素:

  • 在同一行的所有元素中最小
  • 在同一列的所有元素中最大

示例 1:

输入:matrix = [[3,7,8],[9,11,13],[15,16,17]]
输出:[15]
解释:15 是唯一的幸运数,因为它是其所在行中的最小值,也是所在列中的最大值。

提示:

  • m == mat.length
  • n == mat[i].length
  • 1 <= n, m <= 50
  • 1 <= matrix[i][j] <= 10^5
  • 矩阵中的所有元素都是不同的

 

解题思路

 

根据这道题的数据范围,我们可以枚举矩阵的每一个元素,然后判断这个元素,是不是这一行的最小,这一列的最大。如果是,那么这个元素就是幸运数,那么添加进答案。

那么循环次数为 50 * 50 * (50 + 50),不会超时

 

AC代码(C++)

class Solution {
public:
    vector<int> luckyNumbers (vector<vector<int>>& matrix) {
        vector<int> ans;
        int n = matrix.size(), m = matrix[0].size();
        
        for(int i = 0;i < n; ++i)
        {
            for(int j = 0;j < m; ++j)
            {
                bool flag = true;
                int cur = matrix[i][j];
                
                for(int k = 0;k < m; ++k)
                {
                    if(k == j) continue;
                    if(cur > matrix[i][k])
                    {
                        flag = false;
                        break;
                    }
                }
                
                for(int k = 0;k < n; ++k)
                {
                    if(k == i) continue;
                    if(cur < matrix[k][j])
                    {
                        flag = false;
                        break;
                    }
                }
                
                if(flag) ans.push_back(cur);
            }
        }
        return ans;
    }
};

 


2. 设计一个支持增量操作的栈(Design A Stack With Increment Operation)

题目链接

https://leetcode-cn.com/problems/design-a-stack-with-increment-operation/

题意

请你设计一个支持下述操作的栈。

实现自定义栈类 CustomStack :

  • CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量,栈在增长到 maxSize 之后则不支持 push 操作。
  • void push(int x):如果栈还未增长到 maxSize ,就将 x 添加到栈顶。
  • int pop():返回栈顶的值,或栈为空时返回 -1 。
  • void inc(int k, int val):栈底的 k 个元素的值都增加 val 。如果栈中元素总数小于 k ,则栈中的所有元素都增加 val 。

示例 1:

输入:
["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"]
[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]]
输出:
[null,null,null,2,null,null,null,null,null,103,202,201,-1]
解释:
CustomStack customStack = new CustomStack(3); // 栈是空的 []
customStack.push(1);                          // 栈变为 [1]
customStack.push(2);                          // 栈变为 [1, 2]
customStack.pop();                            // 返回 2 --> 返回栈顶值 2,栈变为 [1]
customStack.push(2);                          // 栈变为 [1, 2]
customStack.push(3);                          // 栈变为 [1, 2, 3]
customStack.push(4);                          // 栈仍然是 [1, 2, 3],不能添加其他元素使栈大小变为 4
customStack.increment(5, 100);                // 栈变为 [101, 102, 103]
customStack.increment(2, 100);                // 栈变为 [201, 202, 103]
customStack.pop();                            // 返回 103 --> 返回栈顶值 103,栈变为 [201, 202]
customStack.pop();                            // 返回 202 --> 返回栈顶值 202,栈变为 [201]
customStack.pop();                            // 返回 201 --> 返回栈顶值 201,栈变为 []
customStack.pop();                            // 返回 -1 --> 栈为空,返回 -1

提示:

  • 1 <= maxSize <= 1000
  • 1 <= x <= 1000
  • 1 <= k <= 1000
  • 0 <= val <= 100
  • 每种方法 increment,push 以及 pop 分别最多调用 1000 次

 

解题思路

 

要求模拟一个 栈,然后实现对应的方法。对应的几个要求中,比较要考虑,就是 对栈中前 k 个元素,都累加一个值,如果这个 k 很大,同时这个方法的调用次数很多,那么直接暴力累加是会超时的。但是根据数据范围,我们发现,如果用暴力累加,最多循环次数为 1000 * 1000,不会超时。

因此,这道题就可以是,简单的模拟一个栈,那么就可以使用一个数组,同时一个变量,记录此时数组中的个数(最后一个元素,就是栈顶)。

 

AC代码(C++)

const int MAXN = 1e3 + 10;

class CustomStack {   // 类的实现
public:
    
    int nums[MAXN];  // 类中,实际上用数组模拟栈
    int len;         // 栈的当前大小
    int maxSize;     // 栈的最大长度
    
    CustomStack(int maxSize) {   // 类的构造函数,用于初始化,那就是初始化,最大长度和当前长度
        this->len = 0;
        this->maxSize = maxSize;
    }
    
    void push(int x) {
        if(len < maxSize)  // 判断,如果已经容纳不下,就不能进栈
        {
            nums[len++] = x;
        }
    }
    
    int pop() {    // 返回栈顶元素
        if(len == 0) return -1;
        else
        {
            return nums[--len];
        }   
    }
    
    void increment(int k, int val) {  // 暴力累加即可
        if(len < k)
        {
            for(int i = 0;i < len; ++i) nums[i] += val;
        }
        else
        {
            for(int i = 0;i < k; ++i) nums[i] += val;
        }
    }
};

// 一般类的示例,使用
/**
 * Your CustomStack object will be instantiated and called as such:
 * CustomStack* obj = new CustomStack(maxSize);
 * obj->push(x);
 * int param_2 = obj->pop();
 * obj->increment(k,val);
 */

 


3.将二叉搜索树变平衡(Balance A Binary Search Tree)

题目链接

https://leetcode-cn.com/problems/balance-a-binary-search-tree/

题意

给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。

如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的 。

如果有多种构造方法,请你返回任意一种。

示例 1:

示例有图,具体看链接

输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。

提示:

  • 树节点的数目在 1 到 10^4 之间。
  • 树节点的值互不相同,且在 1 到 10^5 之间。

 

解题分析

实际上,问的是,将一个二叉搜索树,变成“平衡”二叉搜索树。何为平衡,根据题目,也就是,对于一个节点,左右两个子树的高度(深度)之差不超过1。

那么其实,就是,我们要构造的二叉树,是一个对于每一个节点,尽量都要,左右节点都存值,这样子就可以保证这个了。

但是,由于这个是一个二叉搜索树,那么如何构造二叉搜索树呢?

对于二叉搜索树,要求是,对于每一个节点而言,左节点比其小,右节点比大(同时每一个节点都满足这个),也就是说,对于一个节点而言,它的左子树,都比其小,右子树都比其大。

所以,如果我们有一个已排序的数来构造,那么就是对于每一个节点,我们存储这个数组的中间值,左子树存储这个数组的左边范围,右子树存储这个数组的右边范围。然后递归下去,直到叶节点即可。

所以,这道题,就是,先要遍历原来的二叉搜索树,然乎得到其排好序的数组(根据二叉搜索树的性质,我们利用 中序遍历 即可得到已排序的数组),然后就是递归构造一个二叉搜索树即可。

这道题,就是对一个二叉搜索树的 遍历 与 构造 考察。(我也是赛后才会的,哈哈哈)

 

AC代码(C++)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> vals;

    void dfs(TreeNode* root)
    {
        // 中序遍历,对于某个节点而言,先左节点 - 节点 - 右节点
        if(root == NULL) return;
        dfs(root->left);
        vals.push_back(root->val);
        dfs(root->right);
    }

    TreeNode* productBST(int l, int r)
    {
        // 二叉搜索树构造,中间值作为节点值,两边作为两个子树的取值
        if(l > r) return NULL;

        int mid = (l + r) / 2;
        TreeNode* newRoot = new TreeNode(vals[mid]);
        newRoot->left = productBST(l, mid - 1);
        newRoot->right = productBST(mid + 1, r);
        return newRoot;  
    }

    TreeNode* balanceBST(TreeNode* root) {
        vals.clear();
        dfs(root);
        int l = 0, r = vals.size() - 1;
        return productBST(l, r);
    }
};

 


4.最大的团队表现值(Maximum Performance Of A Team)

题目链接

https://leetcode-cn.com/problems/maximum-performance-of-a-team/

题意

公司有编号为 1 到 n 的 n 个工程师,给你两个数组 speed 和 efficiency ,其中 speed[i] 和 efficiency[i] 分别代表第 i 位工程师的速度和效率。请你返回由最多 k 个工程师组成的 ​​​​​​最大团队表现值 ,由于答案可能很大,请你返回结果对 10^9 + 7 取余后的结果。

团队表现值 的定义为:一个团队中「所有工程师速度的和」乘以他们「效率值中的最小值」。

示例 1:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 2
输出:60
解释:
我们选择工程师 2(speed=10 且 efficiency=4)和工程师 5(speed=5 且 efficiency=7)。他们的团队表现值为 performance = (10 + 5) * min(4, 7) = 60 。

示例 2:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 3
输出:68
解释:
此示例与第一个示例相同,除了 k = 3 。我们可以选择工程师 1 ,工程师 2 和工程师 5 得到最大的团队表现值。表现值为 performance = (2 + 10 + 5) * min(5, 4, 7) = 68 。

示例 3:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 4
输出:72

提示:

  • 1 <= n <= 10^5
  • speed.length == n
  • efficiency.length == n
  • 1 <= speed[i] <= 10^5
  • 1 <= efficiency[i] <= 10^8
  • 1 <= k <= n

 

解题分析

 

这道题,因为是要求最大值,同时也不是一个动态规划的问题,所以一开始想到的是贪心。

那么对于这道题,其实可以看成是,如果当我们假设当前 固定效率值,认为当前效率值是最小的,那么为了使得,这个团队表现值,是最大,那么我们就从,比这个效率值(>=)的那么工程师中,找到最大的 k 个速度(如果不够 k 个,那就是全取了)。

因此,我们变成了,考虑对 效率值来取,那么现在的想法就是,我们已经按照效率值对工程师进行排序。

那么问题现在变成,是要从最大效率到最小效率,还是从最小效率到最大效率枚举。

  • 假如是从最小效率开始,那么我们如果在最小效率得到了 k 个最大的速度,那么当我们往第二小的效率过去的时候,那么此时,要去掉当前效率对应的速度,剩下的去找 k 个最大。那么我们无法由之前的 k 个最大,很快的得到 下一个时候的 k 个最大(因为有可能,当前效率对应的速度,也是 k 个最大速度中的一个,但是我们无法很好的进行判断)
  • 假如从最大效率开始。因为是从最大效率开始,那么一开始能取的速度,是不够 k 个的,因此对于很大的效率值,本来满足 >= 这个效率值的工程师数量就不够 k 个,那么我们就可以直接取完。
    • 那么当我们已经有了 k 个最大的速度。那么对于下一个效率,因为是考虑,从大到小,那么对于当前这个效率对应的速度,与之前 的 k 个最大,再选出 k 个最大。那么我们就可以是,找出之前 k 个最大的中的最下值,和这一个值进行比较。1)如果之前 k 个最大值中的那个最小值还是大,那么目前的 k 个最大值保持。2)如果之前 k 个最大值中的那个最小值还是 小了,那么由当前的这个 速度 顶替,得到 k 个最大值。
    • 所以,我们如果从,最大效率到最小效率开始,那么,前面的 k 个最大值,要得到 目前的 k 个最大值,之间是有关系的,这也很方便我们处理
    • 那么由于 k 个最大值,我们总是要取其中的最小值,而且这 k 个值,是会变化的(因为小的可能出去,加进来更大的),那么要动态得到一些数中的最小值,可以使用 “优先队列”,其存取复杂度都是 O(logk)。
    • 那么这样子,我们对于每一个可能的效率进行枚举,然后动态得到 k 个最大值,那么时间复杂度是 O(n * logk),根据数据范围,是可以的。

 

我们以示例 2 为例子,解释上面的

speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 3
一开始,我们先对工程师进行排序,依据是,效率的从大到小(因为我们是效率从大到小判断),

s: 1 5 2 10 3 8
e: 9 7 5 4  3 2

效率 9,那么此时能选的,速度只有 1,因此这种情况,团队表现值:9。 答案为 9
效率 7,那么此时能选的速度有:1 5,团队表现值:42。 答案为 42
效率 5,那么此时能选的速度有:1 2 5,团队表现值:40。 答案为 42
效率 4,那么此时能选的速度有:1 2 5  和 新进来的 10,选出其中最大的 3 个,那么判断前面的 3 个中 1 < 10,所以此时的速度为:2 5 10,团队表现值:68。 答案为 68
效率 3,要选择的速度是:2 5 10 和当前进来的 3,所以此时的速度为:3 5 10,团队表现值:54。 答案为 68
效率 2,要选择的速度是:3 5 10 和当前进来的 8,所以此时的速度为:5 8 10,团队表现值:46。 答案为 68

因此,整个程序的流程:

1)对工程师进行排序,按照效率从大到小。

2)设 答案 = 0,然后此时判断的时候,分两种情况,一种是,选择的效率,选择的工程师人数 <= k,那么就全选。因此,对于前面 k 个情况的效率,我们的最大速度,都是全取完

3)然后从 第 k + 1 个 效率开始,我们取 k 个最大速度,是,要,对比,前面 k 个最大速度中的最小值,和当前进来的值。取出最大的 k 个。

其中,求,团队表现值得时候,是要求出,最大速度之和得,因此,我们可以用一个变量 sum ,记录前一个最大 k 个速度之和,那么对于当前的。如果还是保持,之前的 k 个最大,那么 sum 不变。如果 k 个最大中的最小被移除,那就是 sum = sum +  新进来 - 移除的。

可以有效的得到 k 个值中的最小值,我们使用了,优先队列。

还有一个问题,对于数据范围而言,得到的最大团队表现值 = n * speed[i] * efficiency[i] : 10^18,所以要用 long long来存储。

 

这道题,用的还是枚举贪心(枚举所有情况,得到所有情况中的最大值),但是进行了一些优化,同时使用了,优先队列。

 

AC代码(C++)

typedef long long LL;
const LL MOD = 1e9 + 7;
struct People  // 存工程师的数据
{
    LL s;
    LL e;
};

bool cmp(People a, People b)  // 对工程师进行排序,依据时,效率的从大到小
{
    return a.e > b.e;
}

class Solution {
public:
    int maxPerformance(int n, vector<int>& speed, vector<int>& efficiency, int k) {
        
        vector<People> man(n);
        for(int i = 0;i < n; ++i)
        {
            man[i].s = speed[i];
            man[i].e = efficiency[i];
        }
        sort(man.begin(), man.end(), cmp);
        
        priority_queue<LL, vector<LL>, greater<LL> > q;  // 优先队列,从小达到,也就是队列头是,最小值
        while(!q.empty()) q.pop();  // 队列的清空

        LL sum = man[0].s;
        LL ans = man[0].s * man[0].e;
        q.push(man[0].s);
        
        for(int i = 1;i < k; ++i)  // 前面 k 个效率情况,由于不够 k 个人,所以只要新进来,我们都要
        {
            sum += man[i].s;
            if(sum * man[i].e > ans) ans = sum * man[i].e;
            q.push(man[i].s);  // 存放进去 k 个值
        }

        for(int i = k;i < n; ++i)
        {
            if(man[i].s > q.top())  // 如果 新进来的值,比那个k 个最大中的最小还大,那么就顶替(否则,对于 sum 而言,是不会变化)
            {
                sum = sum - q.top() + man[i].s;   // 计算新的 sum
                q.pop();                          // 去掉最小的
                q.push(man[i].s);                 // 由于新的更大,所以进来
            }
    
            // 计算,这个效率是最小效率时的最大的团队表现值。
            if(sum * man[i].e > ans) ans = sum * man[i].e;
        }
        // 按照题目,取模返回
        return ans % MOD;
    }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值