哎,我也太菜了。其实题目还是算不难,但是自己还是脑子转的慢。第三题的二叉树方面的,是关于“二叉搜索树”的构造(在之前我还不会),因此,我直接跳到第四题,但是第四题想一个小时,思路大致方向对了,但是具体细节还是有些问题,比赛结束前 2 分钟才完整知道如何做,但是已经来不及敲代码。
掉分了,掉了30分左右,哭泣。自己还是太菜了,赛后学习了一下“二叉搜索树”相关的,其实很简单。继续学习,继续努力吧。
(这次周赛,全球的参赛人数,一万多人,可怕。。。)
第一题:暴力。
第二题:模拟 + 暴力。
第三题:二叉搜索树,中序遍历 + 二叉搜索树的构造。
第四题:贪心(枚举 + 优先队列)。
详细题解如下。
1.矩阵中的幸运数(Lucky Numbers In A Matrix)
2. 设计一个支持增量操作的栈(Design A Stack With Increment Operation)
3.将二叉搜索树变平衡(Balance A Binary Search Tree)
4.最大的团队表现值(Maximum Performance Of A Team)
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 <= 10001 <= x <= 10001 <= k <= 10000 <= 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^5speed.length == nefficiency.length == n1 <= speed[i] <= 10^51 <= efficiency[i] <= 10^81 <= 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;
}
};

775

被折叠的 条评论
为什么被折叠?



