# Leetcode 第54场双周赛题解

# Problem A - 检查是否区域内所有整数都被覆盖 (opens new window)

# 方法一:暴力

本题数据范围较小,因此我们可以暴力枚举范围内的每一个数,然后判断是否有一个区间将其包含。

  • 时间复杂度O(NM)\mathcal{O}(NM),其中M=RL+1M=R-L+1
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(Python 3)
class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        for i in range(left, right + 1):
            found = False
            for l, r in ranges:
                if l <= i <= r:
                    found = True
                    break
            if not found:
                return False
        return True
1
2
3
4
5
6
7
8
9
10
11

# 思考:

对于更大的数据范围,应当如何做?

# Problem B - 找到需要补充粉笔的学生编号 (opens new window)

# 方法一:贪心

每一个轮次一共会消耗chalki\sum chalk_i支粉笔。因此,我们首先将kk对这一总和取余。

接下来,我们依次为学生分配粉笔,直到找到第一个需要补充粉笔的学生。

  • 时间复杂度为O(N)\mathcal{O}(N)

  • 空间复杂度O(1)\mathcal{O}(1)

参考代码(Python 3)
class Solution:
    def chalkReplacer(self, chalk: List[int], k: int) -> int:
        s = sum(chalk)
        k %= s
        for i, num in enumerate(chalk):
            if k < num:
                return i
            k -= num
        return -1
1
2
3
4
5
6
7
8
9

# 思考:

如果每个学生有一个需要粉笔的上限(取到这个上限之后这个学生就不参与后续的轮次),应当如何做?

# Problem C - 最大的幻方 (opens new window)

# 方法一:前缀和+暴力

计算出每行和每列的前缀和后,从大到小,暴力枚举所有的正方形区域,判断其是否是幻方。

  • 时间复杂度O(N2M2)\mathcal{O}(N^2M^2)
  • 空间复杂度O(NM)\mathcal{O}(NM)
参考代码(C++)
class Solution {
public:
    int largestMagicSquare(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> rowsum(n, vector<int>(m + 1));
        vector<vector<int>> colsum(m, vector<int>(n + 1));
        for (int i = 0; i < n; ++i)
            for (int j = 1; j <= m; ++j)
                rowsum[i][j] = rowsum[i][j - 1] + grid[i][j - 1];
        for (int j = 0; j < m; ++j)
            for (int i = 1; i <= n; ++i)
                colsum[j][i] = colsum[j][i - 1] + grid[i - 1][j];
        
        int k = min(m, n);
        for (int s = k; s >= 2; --s) {
            for (int i = 0; i + s <= n; ++i)
                for (int j = 0; j + s <= m; ++j) {
                    int sum = rowsum[i][j + s] - rowsum[i][j];
                    bool good = true;
                    for (int p = 1; p < s; ++p)
                        if (rowsum[i + p][j + s] - rowsum[i + p][j] != sum) {
                            good = false;
                            break;
                        }
                    if (!good)
                        continue;
                    int dsum1 = 0, dsum2 = 0;
                    for (int p = 0; p < s; ++p) {
                        if (colsum[j + p][i + s] - colsum[j + p][i] != sum) {
                            good = false;
                            break;
                        }
                        dsum1 += grid[i + p][j + p];
                        dsum2 += grid[i + p][j + s - 1 - p];
                    }
                    if (dsum1 != sum || dsum2 != sum)
                        good = false;
                    if (good)
                        return s;
                }
        }
        
        return 1;
    }
};
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

# Problem D - 反转表达式值的最少操作次数 (opens new window)

# 方法一:栈+动态规划

一道经典的表达式处理问题,我们使用操作符栈+操作数栈的经典双栈方式来进行模拟。

注意这里的“操作数”实际上是一个二元的状态(p,q)(p, q),其中pp代表将当前“操作数”对应的范围变为00所需的最小操作次数,qq代表将当前的“操作数”对应的范围变为11所需的最小操作次数。

显然,单个的00对应于状态(0,1)(0,1),而单个的11对应于状态(1,0)(1,0)

本题中,除括号外所有运算符优先级相同,需要从左到右进行运算,因此我们每得到一个新的“操作数”(这里既包括由单个的0011带来的“操作数”,也包括)导致的出栈情形——对于上一层来说,这一层带来了一个新的“操作数”),就应当在上一个操作符不为(时将当前的“操作数”与上一个“操作数”进行一次“运算”,合并为一个新的“操作数”。

下面我们需要考虑如何实现“运算”。

假设待合并的两个状态为(p1,q1)(p_1,q_1)(p2,q2)(p_2,q_2)

如果当前操作符为&,则:

  • 我们如果要得到00,只需要有一边为00,代价为min(p1,p2)\min(p_1,p_2)

  • 我们如果要得到11,需要左右两边同时为11,代价为q1+q2q_1+q_2;或者将操作符变为|,同时只需要左右有一边为11,代价为min(q1,q2)+1\min(q_1,q_2)+1

如果当前操作符为|,则:

  • 我们如果要得到00,需要左右两边同时为00,代价为p1+p2p_1+p_2;或者将操作符变为&,同时只需要左右有一边为00,代价为min(p1,p2)+1\min(p_1,p_2)+1
  • 我们如果要得到11,只需要有一边为11,代价为min(q1,q2)\min(q_1,q_2)

这样我们就实现了操作数之间的运算。

所有操作执行完毕后,我们的操作数栈将只包含一个元素。这个元素必定包含一个零值(对应于表达式原本的值)和一个非零值。而这个非零值就是我们要寻找的答案。

总的复杂度为:

  • 时间复杂度O(S)\mathcal{O}(|S|)
  • 空间复杂度O(S)\mathcal{O}(|S|)
参考代码(Python 3)
class Solution:
    def minOperationsToFlip(self, expression: str) -> int:
        states = []
        ops = []
                
        for c in expression:
            if c in '01)':
                if c == '0':
                    states.append((0, 1))
                elif c == '1':
                    states.append((1, 0))
                else:
                    assert(ops[-1] == '(')
                    ops.pop()
                    
                if len(ops) > 0 and ops[-1] != '(':
                    op = ops.pop()
                    p2, q2 = states.pop()
                    p1, q1 = states.pop()
                    if op == '&':
                        states.append((min(p1, p2), min(q1 + q2, 1 + min(q1, q2))))
                    else:
                        states.append((min(p1 + p2, 1 + min(p1, p2)), min(q1, q2)))
            else:
                ops.append(c)
        
        return max(states[-1])
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