leetcode 2021 春季团队编程大赛

作者: | 更新日期:

这一次题不难,但是时间不够了。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

零、背景

2021年04月10日,参加了 lleetcode 2021 春季编程大赛团队赛。

总的来说,这次比赛题都还好,但是我们剩一道题有想法,时间不够了。

最终排名 37 名,还不错的,和 11 名的奖品都一样。

一、蓄水

题意:给 n 个水桶的初始容量,以及 n 个水池的目标容量。

现在有两个操作:
1)选择一个水桶,容量加1。
2)将所有水桶接满水,倒入到相同编号的水池中。
问至少操作几次,可以把水池倒满水。

思路:简单分析一下,可以发现肯定是先不断进行操作1,然后不断进行操作2。
因为假设有一个操作1在操作2后面,那将操作1放到前面后,对应的水池可以倒入更多的水。

顺序确定了,加下来就是选择如何贪心了。

枚举操作2,目标容量除以操作次数,就知道每个水桶操作1至少需要操作多少次。
复杂度:O(100 * n)

int storeWater(vector<int>& bucket, vector<int>& vat) {
    if(accumulate(vat.begin(), vat.end(), 0) == 0){
        return 0;
    }
    int n = vat.size();
    int ans = 0x3f3f3f3f;
    for (int i = 10000; i >= 1; i--) {
        int tmp = i;
        for (int j = 0; j < n; j++) {
            if (vat[j] == 0) continue;
            int need = vat[j] / i;
            if (need * i < vat[j]) need++;
            tmp += max(0, need - bucket[j]);
        }
        ans = min(ans, tmp);
    }
    return ans;
}

从操作1贪心的话,需要每次找到操作2最大的水桶,然后进行一次操作1。

可以使用优先队列来维护每个水桶操作2的次数,这样就可以不断的贪心选择操作1。
复杂度:O(100 * n * log(100))
这样做会超时。

优化:上面的贪心之所以会超时,是因为贪心进行操作1之后,这个水桶的操作2次数可能没变化。
没变化意味着这次贪心之后,下次的最大值还是自己。

例如水池大小是 10,水桶大小是 6,需要两次操作2。
贪心选择1后,水桶大小是 7,依旧需要两次操作2。
再次贪心选择1后,水桶大小是 8,还是需要两次操作2。

所以可以直接计算出需要多少次,使得操作2的次数减一。
复杂度:O(100 * log(n))

代码比较长,这里就不贴了,大家可以去原文看源代码。

二、二叉树染色

题意:对一个二叉树进行染色,要求连续节点最多可以染 k 个。
问怎么染色,可以使得染色的节点值之和最大。

思路:动态规划题。
状态定义:当前子树最多可以染色 k 个颜色的最优值。

状态转移分几种情况。

1)当前节点不染色,则两个子树最多染色的个数重置。
2)当前节点染色,枚举左右儿子的染色数的答案,求最大枚举值。

class Solution {
    map<pair<TreeNode*, int>, int> m;
    int K;
    int dfs(TreeNode* root, int k){
        if(!root) return 0;
        
        pair<TreeNode*, int> p = {root, k};
        if(m.count(p)){
            return m[p];
        }
        
        int ans = dfs(root->left, K) + dfs(root->right, K); // 当前不染色
        for(int l=0;l < k;l++){
            int r = k - 1 - l;
            int tmp = dfs(root->left, l) + dfs(root->right, r) + root->val;
            ans = max(ans, tmp);
        }
        return m[p] = ans;
    }
public:
    int maxValue(TreeNode* root, int k) {
        K = k;
        return dfs(root, K);
    }
};

三、电动车游城市

题意:有n个城市,给你城市间的距离以及每个城市充电桩的充电速度。
问一个特斯拉电车,从起点到终点最少需要多少时间。

思路:动态规划题。
状态定义:f(n, v) 电车在城市n且有电量 v时,到达终点的最短距离。

考虑到城市间存在环,无法写状态转移方程。
所以需要使用搜索来做这道题。

搜索的时候,相同状态如果遇到更优值,则加入队列继续搜索。
最坏情况下,每次队列都是最差值,最多重复入队 n 次。
复杂度:O(n * n * v)

优化:队列改成优先队列。
这样每次都是路程最短的状态出队,避免无效状态重复入队。
复杂度:O(n * v)

四、最多牌组数

题意:给 n 个数字,我们可以选三个相同的数字,也可以选三个连续的数字。
问最多可以选出多少组。

思路:动态规划。

普通状态:f(n, use1, use2) 代表第 n 个位置被顺子占用了 use1 个位置,第 n-1 个位置被顺子占用了 use2 个位置的最优值。

状态转移:枚举第 n 个位置的所有顺子,求最优值。
复杂度:O(n^3)

贪心优化:如果第 n 个位置的顺子超过或等于 3 个,每 3 个顺子等价与 3 个组连续的相同数字。
复杂度:O(n * 3 * 3)

五、最小矩形面积

题意:给 n 条直线,问所有交点可以使用与坐标轴垂直的矩形覆盖起来。
求最小的矩形面积。

思路:想要矩形最小,肯定是矩形的四条边恰好覆盖最外层的交点。
所以问题转化为了求上下左右四面的最大值交点。
这里不妨来求 y 坐标轴的最大值。

由于输入的直线 y = kx + b 中的 k 大于 0,所以直线都是从左下到右上的。

我们枚举一条直线,可以发现与这条直线夹角最小的直线,交掉会最远。
所以可以按斜率 k 对所有直线排序,如果斜率相同,则按偏移量 b 排序。

这样,只需要计算相邻两条直线的交点,求最大值即可。

潜在的问题:假设斜率 k1 < k2 < k3
会不会由于 b 的关系,使得k1 & k3 的交点比 k1 & k2 更优呢?
纸上画画图,发现,假设存在这种情况,我们会发现此时k2 & k3 会比 k1 & k3 更优。
所以即使存在这种情况也不会影响最优答案。

六、守卫城堡

题意:给一个 2*N 的地图,地图上有若干障碍物、若干怪兽出生地、若干传送点、一个城堡和若干空地。
若送点之间可以任意传送,问最少在空地添加几个障碍物才能阻止怪兽到达城堡。

思路:本来想枚举所有情况,却发现情况太多无法枚举。
然后我想到,可以构造出一个图来,然后使用图论解决这个问题。

若干怪兽出生地可以缩点为一个点,称为出发点。
多个传送点可以缩点为一个特殊的空地。
城堡称为目的点。

根据传送点与出发点、目的点的关系,分四种情况。

第一种:传送点同时与出发点和目的地挨着,则没有答案。
第二种:传送点只与出发点挨着,此时传送点和出发点可以缩点。
第三种:传送点只与目的地挨着,此时传送点和目的地可以缩点。
第四种:传送点是独立的,则传送点互连的顶点之间可以分别加一个边,然后删除传送点。

这样处理之后,就是一个简单的图,从起点到终点可能有若干路径。
我们需要找到删除哪些顶点,可以使得起点到达终点不连通。

这个就是最大流问题,队友敲了一个最大流,改了改就过了。
我不懂最大流,就不过多介绍了。

七、最后

这次比赛题都不难,时间再多半个小时最后一题应该就可以通过了,不过无所谓了。

总的来看这次比赛
第一题枚举或贪心
第二题动态规划
第三题广搜
第四题动态规划
第五题贪心
第六题图论最大流

除了最后一题,其他题我都会做。
最后一题看其他人的代码也有动态规划的,后面再研究下怎么动态规划怎么做。

你参加这次比赛了吗?
都做出了哪些题,分别都是怎么做的?

加油,算法人。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越