# Leetcode 第59场双周赛题解

# Problem A - 使用特殊打字机键入单词的最少时间 (opens new window)

# 方法一:贪心

每次要么正转,要么倒转,取较小的一种转法即可。

  • 时间复杂度O(S)\mathcal{O}(|S|)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(C++)
class Solution {
public:
    int minTimeToType(string word) {
        int now = 0, ans = 0;
        for (char c : word) {
            int ch = c - 'a';
            ans += 1 + min((ch - now + 26) % 26, (now - ch + 26) % 26);
            now = ch;
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12

# Problem B - 最大方阵和 (opens new window)

# 方法一:贪心

我们发现,对两个负元素操作,可以消去两个负号;每次对两个一正一负的元素进行操作,就可以将负号移动位置。所以有如下结论:

  • 如果一开始有偶数个负号,那么我们可以经过若干次操作消去所有负号;如果一开始有奇数个负号,那么最后可以只剩下一个负号。
  • 如果最后剩下一个负号,我们可以将这个负号移动到任意位置。因此,我们应当将其移动到绝对值最小的位置。

那么就可以很容易地求得答案。

  • 时间复杂度为O(RC)\mathcal{O}(RC)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码(C++)
class Solution {
public:
    long long maxMatrixSum(vector<vector<int>>& matrix) {
        int n = matrix.size(), m = matrix[0].size();
        long long ans = 0, lo = LLONG_MAX, neg = 0;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {
                ans += abs(matrix[i][j]);
                lo = min(lo, (long long)abs(matrix[i][j]));
                if (matrix[i][j] < 0)
                    neg++;
            }
        if (neg % 2 == 1)
            ans -= lo * 2;
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Problem C - 到达目的地的方案数 (opens new window)

# 方法一:Dijkstra算法

在标准Dijkstra算法基础上加一个waysways数组记录方案数即可。

  • 时间复杂度O(ElogE)\mathcal{O}(E\log E)(因为使用priority_queue没有移除无效元素)。
  • 空间复杂度O(V+E)\mathcal{O}(V+E)
参考代码(C++)
using ll = long long;
const ll MOD = 1e9 + 7;

class Solution {
public:
    int countPaths(int n, vector<vector<int>>& roads) {
        vector<vector<pair<int, int>>> adj(n);
        for (auto &road : roads) {
            int u = road[0], v = road[1], t = road[2];
            adj[u].emplace_back(v, t);
            adj[v].emplace_back(u, t);
        }
        
        vector<ll> dis(n, LLONG_MAX), ways(n);
        dis[0] = 0;
        ways[0] = 1;
        priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq;
        pq.emplace(0, 0);
        while (!pq.empty()) {
            auto [t, u] = pq.top();
            pq.pop();
            if (t > dis[u])
                continue;
            for (auto [v, w] : adj[u]) {
                if (t + w < dis[v]) {
                    dis[v] = t + w;
                    ways[v] = 0;
                    pq.emplace(t + w, v);
                }
                if (t + w == dis[v])
                    ways[v] = (ways[v] + ways[u]) % MOD;
            }
        }
        
        return ways[n - 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

# Problem D - 划分数字的方案数 (opens new window)

# 方法一:动态规划

本题较难。

容易想到O(N3)\mathcal{O}(N^3)的动态规划:用dp[i][j]dp[i][j]表示到第ii个位置,最后一个数字的长度为jj时的方案数,则其对应的上一个数的结尾应该是iji-j,我们就可以枚举dp[ij][k]dp[i-j][k]kjk\le j。其中,k<jk<j的部分可以直接加上(因为前面的数字长度较短,所以一定更小),但k=jk=j时,我们需要判断两个数字的大小关系。

利用前缀和的方法可以将k<jk<j的部分优化到O(1)\mathcal{O}(1)时间,但此时整体的复杂度还是O(N3)\mathcal{O}(N^3),因为最坏情况下,所有数字都相同(比如3500个9),则我们对于每一个k=jk=j的情形都需要进行O(N)\mathcal{O}(N)时间的比较。

如何优化比较呢?这里,我们可以进行一次预处理的动态规划来加速比较。

c[i][j]c[i][j]表示第一个串从ii开始,第二个串从jj开始,且满足第一个串不大于第二个串的最长长度。显然c[i][j]c[i][j]可以由c[i+1][j+1]c[i+1][j+1]转移得到。

求得c[i][j]c[i][j]之后,我们在进行比较时就只需要判断c[i2j][ij]jc[i-2j][i-j]\ge j是否成立即可。

  • 时间复杂度O(S2)\mathcal{O}(|S|^2)
  • 空间复杂度O(S2)\mathcal{O}(|S|^2)
参考代码(C++)
using ll = long long;
const ll MOD = 1e9 + 7;

class Solution {
public:
    int numberOfCombinations(string num) {
        if (num[0] == '0')
            return 0;
        
        int n = num.size();
        vector<vector<ll>> dp(n + 1, vector<ll>(n + 1));
        dp[1] = vector<ll>(n + 1, 1);
        dp[1][0] = 0;
        
        vector<vector<int>> c(n, vector<int>(n));
        for (int len = 1; len <= n; ++len)
            for (int i = n - 1 - len; i >= 0; --i) {
                if (num[i] < num[i + len])
                    c[i][i + len] = n - i - len;
                else if (num[i] == num[i + len]) {
                    if (i + len == n - 1)
                        c[i][i + len] = 1;
                    else
                        c[i][i + len] = c[i + 1][i + len + 1] + 1;
                }
            }
        
        auto cmp = [&](int l, int r, int len) {
            if (l < 0)
                return false;
            return c[l][r] >= len;
        };
        
        for (int i = 2; i <= n; ++i) {
            dp[i][i] = 1;
            for (int j = 1; j < i; ++j) {
                if (num[i - j] == '0')
                    continue;
                if (cmp(i - 2 * j, i - j, j))
                    dp[i][j] = (dp[i][j] + dp[i - j][j]) % MOD;
                else
                    dp[i][j] = (dp[i][j] + dp[i - j][j - 1]) % MOD;   
            }
            for (int j = 1; j <= n; ++j)
                dp[i][j] = (dp[i][j] + dp[i][j - 1]) % MOD;
        }
        
        return dp[n][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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50