Leetcode第284场周赛

绪论

最近发现Leetcode每周的周赛难度挺适合我的,而且时间也比较友好(不像Codeforces每次都是半夜)。所以连续参加了三周的周赛。这次才想起来应该记录一下自己的参赛历程。一方面是总结经验,另一方面有了记录就更有动力去提升,去参加下一次比赛。
在这里插入图片描述

题目分析

题目链接:https://leetcode-cn.com/contest/weekly-contest-284/
还是往常一样四道题,难度依次提升。

A:找出数组中的所有 K 近邻下标

简单模拟,对于每一个key,其附近的2k+1个元素都是合法的,需要注意处理重复。为此我们用一个last变量进行记录上一次记录的最后一个元素的位置。

namespace A {
    class Solution {
    public:
        vector<int> findKDistantIndices(vector<int>& nums, int key, int k) {
            vector<int> ret;
            int n = nums.size();
            int last = -1;
            auto add = [&](int l, int r) {
                r = min(r, n - 1);
                l = max(l, last + 1);
                for (; l <= r; ++l) ret.push_back(l);
                last = r;
            };
            for (int i = 0; i < n; ++i) {
                if (nums[i] == key) {
                    add(i - k, i + k);
                }
            }
            return ret;
        }
    };
}

B:统计可以提取的工件

也是简单模拟,因为数据量比较小,所以无脑做就可以。如果数据量比较大可能是一道比较有意思的题。

namespace B {
    class Solution {
    public:
        int digArtifacts(int n, vector<vector<int>>& artifacts, vector<vector<int>>& dig) {
            vector<vector<bool>> vis(n, vector<bool>(n));
            for (auto &pr : dig) {
                vis[pr[0]][pr[1]] = true;
            }

            int x0, x1, y0, y1;
            int ans = 0;
            auto check = [&](auto &&tool) -> bool {
                x0 = tool[0]; y0 = tool[1]; x1 = tool[2]; y1 = tool[3];
                for (int i = x0; i <= x1; ++i) {
                    for (int j = y0; j <= y1; ++j) {
                        if (!vis[i][j]) return false;
                    }
                }
                return true;
            };
            for (auto &tool : artifacts) {
                if (check(tool)) ++ans;
            }
            return ans;
        }
    };
}

C:K 次操作后最大化顶端元素

这个题如果还是想要通过模拟去做那么就会毫无头绪。因为观察到最后要求的是栈顶的元素最大。而我们如何进行这k次操作的情况是非常多的,我们应该观察哪些元素可以通过k次操作放到栈顶。
假如对栈中的元素从1开始编号。如果我们直接出栈k次,那么是可以得到第k+1个元素的,这个也是我们能够看到的最后一个值。
对1到k-1个元素,我们都可以通过将他们的在最后一步入栈从而让他们放到栈顶。
需要进行讨论的就是我们能够让第k个元素放到栈顶?答案是不行(可以通过简单模拟验证一下)。下面我们简单证明一下。
为了让第k个元素放到栈顶,我们必须弹出前k-1个操作,这样我们就只能再操作一下,而一下无论是弹出还是插入都不能让第k个元素放到栈顶。

经过上面的讨论,我们发现,其实题目的意思就是让我们去找前k+1个元素除去第k个元素的最大值。

但是还需要注意的一点是当n为1的情况(有时间我再证明一下,当时没怎么想清楚在这里还wa了一发)

namespace C {
    /*
     * 前k - 1的最大值肯定可以取到
     * 第k个元素取不到
     * 第k + 1个元素可以取到
     */
    class Solution {
    public:
        int maximumTop(vector<int>& nums, int k) {
            int n = nums.size();
            if (n == 1 && (k % 2 == 1)) return -1;

            int ans = -1;
            int kk = min(k - 1, n);
            for (int i = 0; i < kk; ++i) {
                ans = max(ans, nums[i]);
            }
            if (k < n) {
                ans = max(ans, nums[k]);
            }
            return ans;
        }
    };
}

D:得到要求路径的最小带权子图

这个题差一点点就做出来了,思路是正确的,但是编码有一个小失误(忘记了优先队列元素的含义)

刚开始的思路是求src1和src2到dest的最短路的和,如果两个的最短路有重复就减去重复的。后来发现的例外:主要是因为减去的那个重复不一定是最大的,存在不是最短路的两个路径但是重复的成分很高,整体的和反而更小。

后来再思考了一下,那我不知道那个是重复路径的起点,不如我遍历一下,让每一个都当做起点。这样子的路径和就是dis[src1][k]+dis[src2][k]+dis[k][dest]
想到这里我觉得我找到了正确的思路,想要用一下Floyed去求最短路。一看节点个数1e5,然后就意识到肯定会超时。因为Floyed的复杂度是O(n^3)
那么只能进行优化,考虑正向建图求src1和src2到每个节点的距离,再反向建图求每个节点到dest的距离。因为数据量很大,所以要求不能使用简单的Dijkstra,要使用最小堆优化的Dijkstra,这样每次Djikstra的复杂度是O(nlogn),最后遍历一下的复杂度是O(n)。

但是在写Dijkstra的时候我是抄的板子,没有去理解优先队列节点的含义,最终导致出错了,也算是咎由自取吧。

吃了个饭回来又过了,泪目。

namespace D {
    /*
     * 假如src1在src2到dest的路径上或者返过来,则是平凡的情况
     */
    class Solution {
        using ll = long long;
        struct HeapNode {
            ll d;
            int u;
            HeapNode(ll d_, int u_):d(d_), u(u_){}
            bool operator <(const HeapNode&rhs) const {
                return d > rhs.d;
            }
        };
    public:
        long long minimumWeight(int n, vector<vector<int>>& edges, int src1, int src2, int dest) {
            vector<vector<pair<int, int>>> graph(n), reverse_graph(n);
            //建图
            int u, v, w;
            for (auto &edge : edges) {
                u = edge[0]; v = edge[1]; w = edge[2];
                graph[u].emplace_back(v, w);
                reverse_graph[v].emplace_back(u, w);
            }
            vector<ll> dis1(n), dis2(n), dis3(n);
            vector<bool> vis(n);

            auto dijkstra = [n, &vis](auto &&graph, auto &&dis, int s) {
                priority_queue<HeapNode> q;
                for (int i = 0; i < n; ++i) {
                    dis[i] = LONG_LONG_MAX;
                }
                dis[s] = 0;
                for (int i = 0; i < n; ++i) vis[i] = false;
                q.emplace(0, s);
                while (!q.empty()) {
                    HeapNode x = q.top(); q.pop();
                    int u = x.u;
                    if (vis[u]) continue;
                    vis[u] = true;
                    //print(u);
                    auto &edges = graph[u];
                    for (auto &pr : edges) {
                        int v = pr.first;
                        int w = pr.second;
                        //print("\t", u ,v ,w);
                        if (dis[v] > dis[u] + w) {
                            dis[v] = dis[u] + w;
                            q.emplace(dis[v], v);
                        }
                    }
                }
            };
            dijkstra(graph, dis1, src1);
            dijkstra(graph, dis2, src2);
            dijkstra(reverse_graph, dis3, dest);

            ll ans = LONG_LONG_MAX;
            for (int k = 0; k < n; ++k) {
                if (dis1[k] == LONG_LONG_MAX || dis2[k] == LONG_LONG_MAX || dis3[k] == LONG_LONG_MAX) {
                    continue;
                }
                ans = min(ans, dis1[k] + dis2[k] + dis3[k]);
            }

            if (ans == LONG_LONG_MAX) return -1;
            else return ans;
        }
    };
}

经验总结

  • 重复的边对Dijkstra算法是没有影响的
  • Dijkstra算法需要的是最小堆,而默认的小于号得到的是最大堆,因此应该在重载小于号的时候让含义反过来。之所以会出现这样子是和堆的实际生成有关系(上游下游什么的,已经忘记了,有时间复习一下)
  • 为了避免初始化为正无穷的时候两个正无穷相加溢出,我们可以在相加前进行判断。
  • 需要注意Dijkstra对节点标记为已处理是在弹出堆顶元素进行的,而不是在入堆的时候进行的。这一点和一般的BFS不同。Dijkstra算法正确性主要是距离源节点最近的节点的最短路径已经确认
  • 可以放心大胆地用emplace,虽然不知道为什么有一次在emplace的时候报错了,当时心态很崩。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值