# Leetcode 第228场周赛题解

# Problem A - 生成交替二进制字符串的最少操作数 (opens new window)

我们或者将字符串变为0101...0101,或者将字符串变成1010...1010。所以,我们统计奇数和偶数位置上01的数目,然后选择两种方案中较优的那个即可。

  • 时间复杂度O(N)\mathcal{O}(N)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(Python 3)
class Solution:
    def minOperations(self, s: str) -> int:
        oz = 0
        oo = 0
        ez = 0
        eo = 0
        for i, c in enumerate(s):
            if i % 2 == 0:
                if c == '0':
                    ez += 1
                else:
                    eo += 1
            else:
                if c == '0':
                    oz += 1
                else:
                    oo += 1
        return min(oz + eo, ez + oo)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Problem B - 统计同构子字符串的数目 (opens new window)

找出每一个最长的由相同字符组成的子串,假设其长度为LL,则其对答案的贡献为L(L+1)2\frac{L(L+1)}{2}

注意最后的结果要取模。

  • 时间复杂度O(S)\mathcal{O}(|S|)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(Python 3)
class Solution:
    def countHomogenous(self, s: str) -> int:
        x = '$'
        cnt = 0
        ans = 0
        for c in s + '#':
            if c == x:
                cnt += 1
            else:
                ans += cnt * (cnt + 1) // 2
                x = c
                cnt = 1
        return ans % 1000000007
1
2
3
4
5
6
7
8
9
10
11
12
13

# Problem C - 袋子里最少数目的球 (opens new window)

可能第一反应会考虑每次贪心选取球最多的那个袋子将其分为两半。但本题的操作次数最多可以达到10910^9,逐次进行模拟显然是不现实的;另一方面,样例1(只有一个袋子,里面装着9个球,最多操作2次)也已经说明这一贪心策略是不对的。如果我们采用贪心方法,得到的结果会是[9][5,4][4,3,2][9]\rightarrow[5,4]\rightarrow[4,3,2],最后的代价是44,而按照样例中的方法[9][6,3][3,3,3][9]\rightarrow[6,3]\rightarrow[3,3,3],最后的代价是33

不妨考虑下面这个问题:

如果要求最后的代价不超过KK,我们最少需要操作多少次?

我们可以先考虑只有一个袋子的情形。假设袋子里有M>KM>K个球,为了使代价不超过KK,同时操作次数尽可能小,我们应当每次分出KK个球。这样,我们一共需要分M1K\lfloor\frac{M-1}{K}\rfloor次。

扩展到NN个袋子的情形:因为袋子之间没有相互影响,我们只要把每个袋子的结果累加起来即可,也即总共需要MiK\sum\lfloor\frac{M_i}{K}\rfloor次。

解决了这一问题,我们就可以利用二分答案的方法来求解原问题了。

  • 时间复杂度O(NlogMAXN)\mathcal{O}(N\log MAXN)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(Python 3)
class Solution:
    def minimumSize(self, nums: List[int], maxOperations: int) -> int:
        lo = 1
        hi = max(nums)
        while lo <= hi:
            mid = (lo + hi) // 2
            tot = 0
            for num in nums:
                tot += (num - 1) // mid
            if tot <= maxOperations:
                hi = mid - 1
            else:
                lo = mid + 1
        return lo
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Problem D - 一个图中连通三元组的最小度数 (opens new window)

# 方法一:暴力

数据范围N400N\leq400是一个典型的O(N3)\mathcal{O}(N^3)范围,所以直接暴力枚举三元组即可。

为了快速判断连通性,可以先把边集转为邻接矩阵。同时我们统计每个点的度数,方便最后计算总度数(对于一个连通三元组,总度数为三个点的总度数减去它们内部的度数,也就是2×3=62\times3=6)。

  • 时间复杂度O(N3)\mathcal{O}(N^3)
  • 空间复杂度O(N2)\mathcal{O}(N^2)
参考代码(C++)
class Solution {
public:
    int minTrioDegree(int n, vector<vector<int>>& edges) {
        vector<vector<bool>> d(n, vector<bool>(n));
        vector<int> deg(n);
        for (auto &e : edges) {
            d[e[0] - 1][e[1] - 1] = d[e[1] - 1][e[0] - 1] = true;
            deg[e[0] - 1]++;
            deg[e[1] - 1]++;
        }
        int ans = INT_MAX;
        for (int i = 0; i < n; ++i)
            for (int j = i + 1; j < n; ++j) {
                if (!d[i][j])
                    continue;
                for (int k = j + 1; k < n; ++k) {
                    if (d[i][k] && d[j][k]) 
                        ans = min(ans, deg[i] + deg[j] + deg[k] - 6);
                }
            }
        return ans == INT_MAX ? -1 : ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 方法二:给无向图定向

考虑更大的数据范围N2×105,Mmin(N(N1)2,2×105)N\leq2\times10^5,M\leq\min(\frac{N(N-1)}{2},2\times10^5)。此时上面的暴力解法显然不再成立。

一个可行的做法是给无向图定向。这里需要用到一些推导。

  • 我们把所有边的方向定为从度数小的点连向度数大的点(如果度数相等则可以任意连接,下面的参考代码中是从标号小的连向标号大的)。
  • 可以证明,此时任意点的出度不会超过2M\sqrt{2M}。因为如果一个点的出度超过了2M\sqrt{2M},则由于我们上面的规则,可知这些点的度数也都大于2M\sqrt{2M},从而这些点的总度数将超过2M2M,而这是不可能的。

有了这一保证,我们就可以逐个枚举第一个点uu,枚举其所有相邻点vv,然后枚举vv的所有相邻点ww,检查u,v,wu,v,w是否能构成连通三元组,然后更新答案。

总复杂度是多少呢?枚举uuvv的时间复杂度是O(M)\mathcal{O}(M);而对于每一对(u,v)(u,v),由于vv的出度不超过2M\sqrt{2M},所以枚举ww的时间复杂度是O(M)\mathcal{O}(\sqrt{M})

  • 时间复杂度O(M32)\mathcal{O}(M^{\frac{3}{2}})
  • 空间复杂度O(N+M)\mathcal{O}(N+M)
参考代码(C++)
class Solution {
public:
    int minTrioDegree(int n, vector<vector<int>>& edges) {
        vector<unordered_set<int>> d(n);
        for (auto &e : edges) {
            int u = e[0] - 1, v = e[1] - 1;
            d[u].insert(v), d[v].insert(u);
        }
        
        vector<vector<int>> adj(n);
        for (auto &e : edges) {
            int u = e[0] - 1, v = e[1] - 1;
            if (d[u].size() < d[v].size() || (d[u].size() == d[v].size() && u < v))
                adj[u].emplace_back(v);
            else
                adj[v].emplace_back(u);
        }

        int ans = INT_MAX;
        for (int u = 0; u < n; ++u)
            for (int v : adj[u])
                for (int w : adj[v])
                    if (d[u].count(w))
                        ans = min(ans, (int)(d[u].size() + d[v].size() + d[w].size() - 6));
        
        return ans == INT_MAX ? -1 : 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