# Leetcode 第211场周赛题解

# Problem A - 两个相同字符之间的最长子字符串 (opens new window)

我们可以记录每一个字母最早出现和最后出现的位置,然后枚举字母即可。

  • 时间复杂度O(S)O(|S|)
  • 空间复杂度O(C)O(C),其中CC为字母表的大小,本题中C=26C=26
参考代码(C++)
class Solution {
public:
    int maxLengthBetweenEqualCharacters(string s) {
        vector<int> L(26, -1), R(26, -1);
        for (int i = 0; i < s.size(); ++i) {
            int c = s[i] - 'a';
            if (L[c] == -1)
                L[c] = i;
            R[c] = max(R[c], i);
        }
        int ans = -1;
        for (int i = 0; i < 26; ++i)
            if (R[i] != -1 && R[i] > L[i])
                ans = max(ans, R[i] - L[i] - 1);
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Problem B - 执行操作后字典序最小的字符串 (opens new window)

在本题的数据范围内,可以枚举所有可能的操作结果,从中选择最小的那一个。关键是:如何枚举?

首先考虑轮转操作。对于一个长度为NN的字符串,每次轮转bb个位置,等价于轮转g=GCD(N,b)g=GCD(N,b)个位置。所以,我们只需要以gg为步长进行轮转的枚举即可。

接下来考虑增加操作。如果gg是偶数,那么不管怎么轮转,我们只能对下标为奇数的位置进行增加操作;否则,我们也可以对下标为偶数的位置进行增加操作。根据这两种情况,枚举奇数和偶数位置的增加操作的次数即可。因为每一位是[0,9][0,9]之间的数,而1a91\leq a\leq9,所以我们只需要枚举操作[0,9][0,9]次的情形。

  • 时间复杂度O(D2S2)O(D^2|S|^2),本题中D=10D=10
  • 空间复杂度O(S)O(|S|)
参考代码(C++)
class Solution {
    int gcd(int x, int y) {
        return y == 0 ? x : gcd(y, x % y);
    }
public:
    string findLexSmallestString(string s, int a, int b) {
        int n = s.size();
        string ans = s;
        string t = s + s;
        int g = gcd(n, b);
        for (int i = 0; i < n; i += g) {
            string p = t.substr(i, n);
            for (int j = 0; j <= 9; ++j) {
                int th = g % 2 == 0 ? 0 : 9;
                for (int k = 0; k <= th; ++k) {
                    string q(p);
                    for (int t = 1; t < n; t += 2)
                        q[t] = '0' + (q[t] - '0' + a * j) % 10;
                    for (int t = 0; t < n; t += 2)
                        q[t] = '0' + (q[t] - '0' + a * k) % 10;
                    ans = min(ans, q);
                }
            }
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Problem C - 无矛盾的最佳球队 (opens new window)

本题的数据范围显然不可能支持我们进行所有子集的枚举。我们希望找到一种顺序,使得我们在进行选择时,总是不会发生冲突。

我们可以将所有队员按照年龄升序进行排序,年龄相同时,则按照分数升序进行排序。排序之后,我们可以进行动态规划。令dp[i]dp[i]表示最后一个队员是第ii个队员时的最大分数(这里的ii是重新排序后的编号)。我们只需要在[0,i1][0,i-1]的范围内枚举上一个队员即可。这里,如果上一个队员的分数不超过当前队员的分数,就可以进行转移。

为什么这样的枚举一定是合法的呢?因为我们的最大分数总是在最后一个队员处取得(对于相同年龄的,我们是按照分数升序排序的,所以分数较高的一定在更后面),同时第ii个队员的年龄不小于之前任意队员的年龄,所以只要第ii个队员的分数大于等于之前的分组中最后一个队员的分数,就一定可以将第ii个队员加入到组里,从而得到一个以第ii个队员为最后一名队员的新的组。

  • 时间复杂度O(N2)O(N^2)
  • 空间复杂度O(N)O(N)
参考代码(C++)
class Solution {
public:
    int bestTeamScore(vector<int>& scores, vector<int>& ages) {
        int n = scores.size();
        vector<int> order(n);
        for (int i = 0; i < n; ++i)
            order[i] = i;
        sort(order.begin(), order.end(), [&](int i, int j){
            return ages[i] < ages[j] || (ages[i] == ages[j] && scores[i] < scores[j]);
        });
        vector<int> dp(n);
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            int idx = order[i];
            dp[i] = scores[idx];
            for (int j = 0; j < i; ++j) {
                int last = order[j];
                if (scores[last] <= scores[idx])
                    dp[i] = max(dp[i], dp[j] + scores[idx]);
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Problem D - 带阈值的图连通性 (opens new window)

因为NN最大可以到1000010000,我们显然不能枚举所有的边来构造这个图。不妨先把阈值条件放在一边,假设PPAABB的一个公因数,那么显然PP也是PPAA的公因数,同时也是PPBB的公因数。因此,我们就可以不连接(A,B)(A,B),而是连接(P,A)(P,A)(P,B)(P,B)。也就是说,我们可以枚举因子,然后将其所有的倍数与这一因子之间连边。

对于连通性,我们则可以借助并查集这一数据结构来实现近似O(1)O(1)时间复杂度的连边和查询操作。

如何把阈值考虑进来呢?很简单,只需要从threshold+1threshold+1开始枚举因子即可。因为连边操作是近似O(1)O(1)时间复杂度的,所以在threshold=0threshold=0的情况下,枚举因子并连边的时间复杂度为i=1NNi=O(NlogN)\sum_{i=1}^N\frac{N}{i}=O(N\log N)

因为每次查询都是近似O(1)O(1)时间复杂度的,因此完成QQ次查询的时间复杂度为O(Q)O(Q)

  • 时间复杂度O(NlogN+Q)O(N\log N+Q)
  • 空间复杂度O(N)O(N)
参考代码(C++)
class UnionFind {
  int n;
  vector<int> parent, size;

public:
  UnionFind(int n) {
    this->n = n;
    parent = vector<int>(n);
    size = vector<int>(n, 1);
    for (int i = 0; i < n; ++i)
      parent[i] = i;
  }

  int find(int idx) {
    if (parent[idx] == idx)
      return idx;
    return parent[idx] = find(parent[idx]);
  }

  void connect(int a, int b) {
    int fa = find(a), fb = find(b);
    if (fa != fb) {
      if (size[fa] > size[fb]) {
        parent[fb] = fa;
        size[fa] += size[fb];
      } else {
        parent[fa] = fb;
        size[fb] += size[fa];
      }
    }
  }
};

class Solution {
public:
    vector<bool> areConnected(int n, int threshold, vector<vector<int>>& queries) {
        UnionFind uf(n + 1);
        for (int i = threshold + 1; i <= n; ++i)
            for (int j = 2 * i; j <= n; j += i)
                uf.connect(i, j);
        vector<bool> ans;
        for (auto &q : queries) {
            int u = q[0], v = q[1];
            int fu = uf.find(u), fv = uf.find(v);
            ans.emplace_back(fu == fv);
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49