# Leetcode 第284场周赛题解

# Problem A - 找出数组中的所有 K 近邻下标 (opens new window)

# 方法一:暴力

暴力检查每个位置是否符合要求。

  • 时间复杂度 O(NK)\mathcal{O}(NK)
  • 空间复杂度 O(N)\mathcal{O}(N)
参考代码(Python 3)
class Solution:
    def findKDistantIndices(self, nums: List[int], key: int, k: int) -> List[int]:
        ans = []
        n = len(nums)
        for i in range(n):
            good = False
            for j in range(max(0, i - k), min(n, i + k + 1)):
                if nums[j] == key:
                    good = True
                    break
            if good:
                ans.append(i)
        return ans
1
2
3
4
5
6
7
8
9
10
11
12
13

# 方法二:分别找出左边和右边最近的 key

我们可以先从左向右,再从右向左进行两次遍历,从而找出每个位置左边和右边最近的 key,并判断是否满足条件。

  • 时间复杂度 O(N)\mathcal{O}(N)
  • 空间复杂度 O(N)\mathcal{O}(N)
参考代码(Python 3)
class Solution:
    def findKDistantIndices(self, nums: List[int], key: int, k: int) -> List[int]:
        n = len(nums)
        good = [False] * n
        last_key = -1
        for i in range(n):
            if nums[i] == key:
                last_key = i
            if last_key != -1 and i - last_key <= k:
                good[i] = True
        last_key = -1
        for i in range(n - 1, -1, -1):
            if nums[i] == key:
                last_key = i
            if last_key != -1 and last_key - i <= k:
                good[i] = True
        return [i for i in range(n) if good[i]]    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Problem B - 统计可以提取的工件 (opens new window)

# 方法一:模拟

逐个检查工件,判断是否每个部分都已经裸露出来。

  • 时间复杂度 O(N+M)\mathcal{O}(N+M),其中 NN 为工件数,MM 为挖掘数。
  • 空间复杂度 O(N+M)\mathcal{O}(N+M)
参考代码(Python 3)
class Solution:
    def digArtifacts(self, n: int, artifacts: List[List[int]], dig: List[List[int]]) -> int:
        d = set(map(tuple, dig))
        return len([1 for r1, c1, r2, c2 in artifacts if all((r, c) in d for r in range(r1, r2 + 1) for c in range(c1, c2 + 1))])
1
2
3
4

# Problem C - K 次操作后最大化顶端元素 (opens new window)

# 方法一:分类讨论

本题的边界情况较多,需要细致地进行讨论。

  • 如果 k=0k=0,也即不进行任何操作,那么顶端元素必然为 nums[0]nums[0]
  • 如果 n=1n=1,也即只有一个元素,同时操作次数为奇数次,那么我们必然只能按照“删除——放入——删除”这样的序列进行操作,因此最后一次操作必然是删除,此时栈必定为空
  • 如果 k<nk<n,我们可以有两种选择
    • 先删除 k1k-1 次,然后将这 k1k-1 个元素中的最大值放回
    • 删除 kk 次,剩下第 k+1k + 1 个元素在栈顶
  • 如果 k=nk=n,我们只能删除 k1k - 1 次然后将其中的最大值放回。我们不能删除 kk 次,因为这样会导致栈为空。
  • 如果 k>nk>n,我们总能够取到所有元素中的最大值
    • k=n+1k=n+1,我们可以先删除所有元素,然后放回最大值
    • k=n+2k = n + 2,我们可以先删除所有元素,然后先放回非最大值的任意一个值,再放回最大值(注意 n=1n=1kk 为奇数的情形已经在前面排除了)
    • k=n+2m+1k = n + 2m + 1,我们可以先按照 k=n+1k=n+1 的方式操作,再将最大值删除并放回 mm
    • k=n+2m+2k = n + 2m + 2,我们可以先按照 k=n+2k=n+2 的方式操作,再将最大值删除并放回 mm 次。

综上,我们可以写出代码。

  • 时间复杂度 O(N)\mathcal{O}(N)
  • 空间复杂度 O(1)\mathcal{O}(1)
参考代码(Python 3)
class Solution:
    def maximumTop(self, nums: List[int], k: int) -> int:
        if k == 0:
            return nums[0]
        
        n = len(nums)
        if n == 1 and k % 2 == 1:
            return -1
        
        if k < n:
            return max(nums[k], max([0] + nums[:k - 1]))
        
        if k == n:
            return max(nums[:k - 1])
        
        return max(nums)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Problem D - 得到要求路径的最小带权子图 (opens new window)

# 方法一:在原图和反图上求最短路径

考虑从 src1src2 分别前往 dest 的最短路径。显然,这一路径最后总有一部分是重合的。也即,存在一个中间点 M,使得我们从 src1dest 的最短路径为 src1--M--destsrc2dest 的最短路径为 src2--M--dest。当然,M 有可能与 src1src2dest 中的某个点重合。

注意到,此时 src1--Msrc2--MM--dest 这三段路径没有重复的边。所以,此时这三段路程之和就等于子图的边权和。

我们现在需要尝试找到 M 点。显然,M 应该满足:

dist[src1][M]+dist[src2][M]+dist[M][dest]=min(dist[src1][V]+dist[src2][V]+dist[V][dest])dist[src1][M] + dist[src2][M] + dist[M][dest] = \min(dist[src1][V] + dist[src2][V] + dist[V][dest])

其中 dist[i][j]dist[i][j] 表示从 iijj 的最短距离。

因此,我们在原图上,以 src1src2 为源点求两次单源最短路径,再在反图(所有边反向形成的图)上以 dest 为源点求一次单源最短路径,就可以得到我们需要的答案。

  • 时间复杂度 O(ElogV)\mathcal{O}(E\log V)。需要注意的是,下面代码的复杂度实际上会略高于这一理论值,因为在某一元素已经入队但距离再次被更新时,并没有逐出原先在队列中的元素。
  • 空间复杂度 O(E+V)\mathcal{O}(E+V)
参考代码(C++)
using ll = long long;

const ll INF = 1e12;

class Solution {
    vector<ll> dijkstra(vector<vector<pair<int, int>>> &adj, int s) {
        int n = adj.size();
        priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq;
        vector<ll> dis(n, INF);
        dis[s] = 0;
        pq.emplace(0, s);
        
        while (!pq.empty()) {
            auto [d, u] = pq.top();
            pq.pop();
            if (d > dis[u])
                continue;
            
            for (auto [v, w] : adj[u]) {
                if (d + w < dis[v]) {
                    dis[v] = d + w;
                    pq.emplace(dis[v], v);
                }
            }
        }
        
        return dis;
    }
    
public:
    long long minimumWeight(int n, vector<vector<int>>& edges, int src1, int src2, int dest) {
        vector<vector<pair<int, int>>> adj(n), rev(n);
        for (auto &e : edges) {
            adj[e[0]].emplace_back(e[1], e[2]);
            rev[e[1]].emplace_back(e[0], e[2]);
        }
        
        auto da = dijkstra(adj, src1);
        auto db = dijkstra(adj, src2);
        auto dt = dijkstra(rev, dest);
        
        ll ans = INF;
        for (int i = 0; i < n; ++i)
            ans = min(ans, da[i] + db[i] + dt[i]);
        
        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
46
47
48