[leetcode] 周赛 211

🏆 比赛题目:https://leetcode-cn.com/circle/discuss/luvHfG/

两个相同字符之间的最长子字符串

题目:5543. 两个相同字符之间的最长子字符串

开始理解错题意了,结果提交了 2 次错误答案 🤒️ 。

用一个 map 记录字符出现的第一次位置即可。时间 \(O(n)\),空间 \(O(1)\)

class Solution {
public:
    int maxLengthBetweenEqualCharacters(string s) 
    {
        int maxval = -1;
        unordered_map<char, int> m;
        int len = s.length();
        for (int i=0; i<len; i++)
        {
            if (m.count(s[i]) == 1)
                maxval = max(maxval, i-m[s[i]]-1);
            else
                m[s[i]] = i;
        }
        return maxval;
    }
};

带阈值的图连通性

题目:5128. 带阈值的图连通性

这题看出来是考察并查集的,但是第一次写是通过穷举图中的任意 2 个点是否联通实现的,时间复杂度是 \(O(N^2)\) ,超时了。

class Solution {
public:
    vector<int> root;
    vector<bool> res;
    vector<bool> areConnected(int n, int threshold, vector<vector<int>>& queries) 
    {
        root.resize(n + 1, -1);
        for (int i=1; i<=n; i++)
        {
            for (int j=i+1; j<=n; j++)
            {
                if (check(n, threshold, j, i))
                    merge(i,j);
            }
        }
        for (auto &v: queries)
            res.push_back(find(v[0]) == find(v[1]));
        return res;
    }
    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y) root[y] = x;
    }
    int find(int x)
    {
        return root[x] == -1 ? x : (root[x] = find(root[x]));
    }
    int gcd(int a, int b)  // require a >= b
    {
        return (b == 0) ? a : gcd(b, a % b);
    }
    bool check(int n, int threshold, int x, int y)
    {
        return gcd(x, y) > threshold;
    }
};

然后也考虑了一下怎么优化,我的第一直觉是从「倍数」关系入手,对于 i 节点,我们只需要看它与 j=2*i, 3*i, 4*i, ... 是否联通。但是仔细一想,不对劲 🤨 ,比如 threshold = 2 时,i=6, j=9 这 2 个节点也是联通的,这么做不就忽略这种情况吗?

但其实并没有。

因为 gcd(6, 9) = 3 ,所以其实 6 和 9 在并查集当中通过 3 连起来了。所以通过循环 i=3, j=6,9,... 时,我们已经把这种情况考虑了。

只需要改一下内层循环:

for (int i=1; i<=n; i++)
    for (int j=i+i; j<=n; j+=i)
        if (check(n, threshold, j, i))
            merge(i, j);

实际上 i 可以从 threshold 开始。

空间复杂度 \(O(n)\) 。下面看时间复杂度分析。

首先,双重循环的复杂度是:

\[\sum_{i=1}^{n}\frac{n}{i} = n \sum_{i=1}^{n}\frac{1}{i} \]

问题来了,这里有一个调和级数(真忘记了当时上算法课有没有学过 🤒️ ),但是高数里边判断是否收敛有个叫积分判别法的东西。所以有(这里的等号并不严谨):

\[\sum_{i=1}^{n}\frac{1}{i} = \int_{1}^{n} \frac{1}{x}dx = \ln{n} \]

所以双重循坏复杂度是 \(O(n \log n)\),此外,每次循环还包含一次并查集操作,所以总的时间复杂度是 \(O(n \log n \cdot \alpha(n))\) .

无矛盾的最佳球队

题目:5545. 无矛盾的最佳球队

这 TM 应该是最难的一道题了,看完题解的我还是一脸懵逼。

首先排序,年龄小的在前,同年龄分数小的在前。

那么对于 data[i] ,在其之前的人中(即下面的 data[j] ),跟他没有矛盾的条件是:

  • 同一年龄
  • 分数小于等于 data[i] 的分数

定义 dp[i] 是在区间 [0, i] 上选取人员,且选中人员 i 时最大分数。

转移方程:

\[dp[i] = max(score[i], score[i] + dp[j]), 0 \le j < i \]

有点类似于 LIS ,即最长上升子序列。

class Node 
{
public:
    int score, age;
    Node(int s, int a): score(s), age(a){}
    bool operator < (const Node &n) const
    {
        return age < n.age || (age == n.age && score < n.score);
    }
};
class Solution {
public:
    int bestTeamScore(vector<int>& scores, vector<int>& ages) 
    {
        int n = scores.size();
        vector<Node> data;
        for (int i=0; i<n; i++)
            data.push_back(Node(scores[i], ages[i]));
        sort(data.begin(), data.end());
        vector<int> dp(n);
        int maxval = data[0].score;
        for (int i=0; i<n; i++)
        {
            dp[i] = data[i].score;
            for (int j=0; j<i; j++)
            {
                if (data[j].age == data[i].age || data[j].score <= data[i].score)
                    dp[i] = max(dp[i], dp[j] + data[i].score);
            }
            maxval = max(maxval, dp[i]);
        }
        return maxval;
    }
};

还有一题是:5544. 执行操作后字典序最小的字符串,都说穷举(我也想到了),但就是写不出来,我是傻逼。

就这样吧,不要太难为自己 2333 。

posted @ 2020-10-18 17:26  sinkinben  阅读(117)  评论(0编辑  收藏  举报