# Leetcode 第56场双周赛题解

# Problem A - 统计平方和三元组的数目 (opens new window)

# 方法一:暴力+预处理

本题数据范围较小,因此我们可以暴力枚举两个数(a,ba,b或者a,ca,c都可以)。为了运行速度更快,可以预处理打表。

  • 预处理时间复杂度O(N2)\mathcal{O}(N^2),之后每次调用时间复杂度O(1)\mathcal{O}(1)
  • 空间复杂度O(N)\mathcal{O}(N)
参考代码(C++)
const int N = 255;

bool inited = false;
int ans[N];

void init() {
    inited = true;
    int cnt = 0;
    for (int c = 2; c < N; ++c) {
        for (int a = 1; a * a * 2 <= c * c; ++a) {
            int b = int(sqrt(c * c - a * a));
            if (a * a + b * b == c * c)
                cnt += 2;
        }
        ans[c] = cnt;
    }
}

class Solution {
public:
    int countTriples(int n) {
        if (!inited)
            init();
        
        return ans[n];
    }
};
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 B - 迷宫中离入口最近的出口 (opens new window)

# 方法一:宽度优先搜索

标准宽搜,注意判断入口在边界位置的情况。

  • 时间复杂度为O(RC)\mathcal{O}(RC)
  • 空间复杂度O(RC)\mathcal{O}(RC)
参考代码(C++)
const int d[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
const int INF = 0x3f3f3f3f;

class Solution {
public:
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        int n = maze.size(), m = maze[0].size();
        int si = entrance[0], sj = entrance[1];
        vector<vector<int>> dis(n, vector<int>(m, INF));
        queue<pair<int, int>> q;
        q.emplace(si, sj);
        dis[si][sj] = 0;
        
        while (!q.empty()) {
            auto [ci, cj] = q.front();
            q.pop();
            if (dis[ci][cj] != 0 && (ci == 0 || ci == n - 1 || cj == 0 || cj == m - 1))
                return dis[ci][cj];
            for (int k = 0; k < 4; ++k) {
                int ni = ci + d[k][0], nj = cj + d[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m || maze[ni][nj] == '+' || dis[ni][nj] != INF)
                    continue;
                dis[ni][nj] = dis[ci][cj] + 1;
                q.emplace(ni, nj);
            }
        }
        
        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

# Problem C - 求和游戏 (opens new window)

# 方法一:分类讨论

首先统计出前半段的当前总和lsumlsum和问号数lcntlcnt,以及后半段的当前总和rsumrsum和问号数rcntrcnt

我们分情况讨论:

  • 如果问号的总数是奇数,则将由Alice进行最后一次操作。那么无论最后一次操作之前的情况如何,Alice至少有99种获胜的选择(因为能使得前后两段总和相等的选择至多有11种),因此Alice必胜。
  • 如果问号的总数是偶数,则:
    • 如果问号多的那一边总和也较大,则Alice总可以选择在问号多的那边放99,在问号少的那边放00;那么无论Bob如何操作,两段的大小关系都不会改变。因此这种情况下Alice也必胜。
    • 如果问号多的那一边总和较小:
      • 我们不妨考虑一种镜像策略,即当Alice在某一段写下一个数字时,Bob就在另一段写下相同的数字。这样重复若干次后,必然只有一段包含问号(并且问号数目应当为偶数)。假设此时问号有2k2k个,而两段之差为dd。对Bob来说,当且仅当d=9kd=9k时他有必胜策略,即当Alice写下nn时,Bob就写下9n9-n,这样最后就能使得两段相等。而如果d<9kd<9k,Alice每次都写99即可确保获胜;如果d>9kd>9k,Alice每次都写00即可确保获胜。小结:在采用镜像策略时,Bob当且仅当d=9kd=9k时必胜,其中dd为两段当前和的差的绝对值,kk为两段问号数的差的绝对值的一半,
      • 我们还需要说明d<9kd<9kd>9kd>9k时Alice有必胜策略。不难想到,对于d<9kd<9k,Alice可以首先在问号较少的那一半中写下一个00,之后Alice反过来采用镜像策略,这样到最后会在问号较多的那一半中剩下2k+12k+1个问号。之后Alice每次都写99,则到Bob最后一次操作之前,包含最后一个问号的这一半的总和已经超过了另一半,所以Bob必败,Alice必胜。同理,d>9kd>9k时,Alice可以在问号较少的那一半中先写下一个99,然后采用镜像策略,最后在剩下的那一半中每次都写00,同样可以保证必胜。

这样,我们就完成了所有情况的讨论。

  • 时间复杂度O(S)\mathcal{O}(|S|)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(C++)
class Solution {
public:
    bool sumGame(string num) {
        int n = num.size();
        int lsum = 0, rsum = 0, lcnt = 0, rcnt = 0;
        for (int i = 0; i < n / 2; ++i) {
            if (num[i] == '?')
                lcnt++;
            else
                lsum += num[i] - '0';
        }
        for (int i = n / 2; i < n; ++i) {
            if (num[i] == '?')
                rcnt++;
            else
                rsum += num[i] - '0';
        }
        
        if ((lcnt + rcnt) % 2 == 1)
            return true;
        
        if ((lcnt > rcnt && lsum >= rsum) || (lcnt < rcnt && lsum <= rsum))
            return true;
        
        int dcnt = abs(lcnt - rcnt), dsum = abs(lsum - rsum);
        return dsum != 9 * dcnt / 2;
    }
};
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

# Problem D - 规定时间内到达终点的最小花费 (opens new window)

# 方法一:Dijkstra算法

我们以(node,time)(node, time)为节点建立一张新图,然后以(0,0)(0,0)为源点运行一遍Dijkstra算法求出单源最短路径,最后的答案就是:

mintdis(n1,t)\min_t dis(n-1,t)

  • 时间复杂度O((N+M)TlogNT)\mathcal{O}((N+M)T\log NT)
  • 空间复杂度O(NT)\mathcal{O}(NT)
参考代码(C++)
const int INF = 0x3f3f3f3f;

class Solution {
public:
    int minCost(int maxTime, vector<vector<int>>& edges, vector<int>& passingFees) {
        int n = passingFees.size();
        vector<unordered_map<int, int>> adj(n);
        for (auto &edge : edges) {
            int u = edge[0], v = edge[1], w = edge[2];
            if (w > maxTime)
                continue;
            if (!adj[u].count(v) || adj[u][v] > w)
                adj[u][v] = w;
            if (!adj[v].count(u) || adj[v][u] > w)
                adj[v][u] = w;
        }
        
        int T = maxTime + 1;
        vector<int> dis(n * T + 1, INF);
        dis[0] = passingFees[0];
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
        pq.emplace(dis[0], 0);
        while (!pq.empty()) {
            auto [d, idx] = pq.top();
            pq.pop();
            if (d > dis[idx])
                continue;
            int u = idx / T, t = idx % T;
            for (auto [v, w] : adj[u]) {
                if (t + w < T) {
                    int nxt = v * T + t + w;
                    if (d + passingFees[v] < dis[nxt]) {
                        dis[nxt] = d + passingFees[v];
                        pq.emplace(dis[nxt], nxt);
                    }
                }
            }
        }
        int ans = INF;
        for (int t = 0; t < T; ++t)
            ans = min(ans, dis[(n - 1) * T + t]);
        
        return ans == INF ? -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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 方法二:动态规划

dp[node][time]dp[node][time]表示在timetime时刻到达nodenode的最小花费,显然有两种转移方式:

  • nodenode原地不动,(node,time)(node,time+1)(node,time)\rightarrow(node,time+1),不需要额外花费
  • 走边(v,cost)(v,cost)去与nodenode相邻的vv(node,time)(v,time+cost)(node,time)\rightarrow(v,time+cost),需要花费vv的通行费

最后的答案就是dp[n1][maxTime]dp[n-1][maxTime]

  • 时间复杂度O((N+M)T)\mathcal{O}((N+M)T)
  • 空间复杂度O(NT)\mathcal{O}(NT)
参考代码(C++)
const int INF = 0x3f3f3f3f;

class Solution {
public:
    int minCost(int maxTime, vector<vector<int>>& edges, vector<int>& passingFees) {
        int n = passingFees.size();
        vector<unordered_map<int, int>> adj(n);
        for (auto &edge : edges) {
            int u = edge[0], v = edge[1], w = edge[2];
            if (w > maxTime)
                continue;
            if (!adj[u].count(v) || adj[u][v] > w)
                adj[u][v] = w;
            if (!adj[v].count(u) || adj[v][u] > w)
                adj[v][u] = w;
        }
        
        vector<vector<int>> dp(n, vector<int>(maxTime + 1, INF));
        dp[0][0] = passingFees[0];
        for (int t = 0; t < maxTime; ++t)
            for (int i = 0; i < n; ++i) {
                if (dp[i][t] == INF)
                    continue;
                dp[i][t + 1] = min(dp[i][t + 1], dp[i][t]);
                for (auto [v, w] : adj[i]) {
                    if (t + w <= maxTime)
                        dp[v][t + w] = min(dp[v][t + w], dp[i][t] + passingFees[v]);
                }
            }
        
        return dp[n - 1][maxTime] == INF ? -1 : dp[n - 1][maxTime];
    }
};
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