leetcode 第 270 场算法比赛

作者: | 更新日期:

链表专题,难度还可以,但是差几分钟做完所有题,可惜了。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

这次比赛的题目与之前都不同。

是链表专题,难度还可以。

可惜我速度慢了点,差几分钟就做完所有题。

题意:给若干个数字(可能有重复),任意挑选三个数字以任意顺序组合,问以得到多少个不重复的无前导零三位偶数数字。

思路:数据量不大,枚举所有组合判断是否满足题意即可。

复杂度:O(n^3)

题意:给一个链表,求删除中间节点后,剩余的链表。
中间的定义是向下取整。

思路: 先求链表的长度计算出中间的位置,然后循环找到删除即可。

注意事项:长度为 1 时,删除后就是空链表了。

题意:给一个二叉树,问从一个节点到另一个节点的操作路径。

有三种操作路径:
向上:U
向下左二子:L
向下右儿子:R

思路:经典的双参数递归题。

递归的时候,同时记录两个查找目标的路径。
某个子树上同时找到两个目标,则返回成功,否则返回失败。

复杂度:O(n)

  1. string Dfs(TreeNode* root, int from, int to, string& src, string& dst){
  2. if(root->val == from) { // 标记目标是否找到
  3. src.push_back(' ');
  4. }
  5. if(root->val == to) {
  6. dst.push_back(' ');
  7. }
  8. if(root->left) {
  9. string s, d, ans;
  10. ans = Dfs(root->left, from, to, s, d);
  11. if(!ans.empty()) { // 子树找到两个目标
  12. return ans;
  13. }
  14. if(!s.empty()) { // 子树找到一个目标,记录路径
  15. src.swap(s); // swap,防止复杂度退化为 O(n^2)
  16. src.push_back('L');
  17. }
  18. if(!d.empty()) {
  19. dst.swap(d);
  20. dst.push_back('L');
  21. }
  22. }
  23. if(root->right) {
  24. ... // 同左子树逻辑
  25. }
  26. // 当前子树 才 找到两个目标
  27. if(!src.empty() && !dst.empty()) {
  28. return CalAns(src, dst);
  29. }
  30. return string();
  31. }

题意:给一个二元组pairs[i] = [starti, endi]列表。
通过对二元组重新排列,可以做到end[i-1] == start[i]
求其中一个满足题意的重新排列后的二元组。

题目保证一定存在答案。

思路:

** 构图 **

这其实是一个图论题。

二元组就是一条有向边。

题目是要求找一个路径,遍历整个图,覆盖所有边。

所以,我们需要先把边的集合转化为一个含出入度的有向图。

  1. struct Node{
  2. int val;
  3. int indeg = 0;
  4. int outdeg = 0;
  5. vector<int> next;
  6. int firstLinkPos = 0;
  7. };
  8. unordered_map<int, Node> m;

** 路径 **

路径显然是一个链表,所以需要维护一个链表。

  1. struct LinkNode{
  2. int from = 0, to = 0;
  3. int next = 0, pre = 0;
  4. };
  5. vector<LinkNode> LinkNodeBufs;
  6. int bufIndex = 1;

** 起点分析 **

由于是需要覆盖所有边,那就可以从顶点的出入度来分析问题了。

正常情况下,路径的起点出度为奇数,且比入度大一。
路径的终点入度为奇数,且比出度大一。

特殊情况下,可以形成环,此时所有顶点的入度等于出度。

所以,我们根据上面的顶点出入度,可以找到起点。
对于特殊情况,选择第一个顶点当做起点即可。

  1. int source = m.begin()->first; // 随便指定一个源
  2. for(auto& p: m) {
  3. Node& node = p.second;
  4. if(node.outdeg > node.indeg) {
  5. source = node.val;
  6. break;
  7. }
  8. }

** 剩余的环 **

有了起点,无脑循环搜索,直到没有下条边即可。

正常情况下,此时恰好把整个图搜索完。

特殊情况是,某个点有一个环,即可以出去转一圈回到当前点。

所以我们需要遍历剩余的顶点,遇到一个有边,就寻找一个环,插入到当前顶点所在的路径中。

例如当前路径是 a->b->c,顶点 b 有一个环 b->d->e->b
插入到路径就是 a->b->d->e->b->c

  1. InsertNode(source, 0); // 原始路径
  2. while(edgeNum < n) { // 还有几个环
  3. for(auto&p: m) {
  4. // 在链上,且有出度,说明有环
  5. if(p.second.outdeg && p.second.firstLinkPos) {
  6. InsertNode(p.first, p.first);
  7. }
  8. }
  9. }

InsertNode 函数需要两个参数的原因是。
对于原始路径,是从表头开始插入的。
对于环,是从当前节点的父节点插入的。

** 路径转答案 **

  1. vector<vector<int>> ans;
  2. ans.reserve(n);
  3. int pos = LinkNodeBufs[0].next;
  4. while(pos) {
  5. LinkNode& linkNode = LinkNodeBufs[pos];
  6. ans.push_back({linkNode.from, linkNode.to});
  7. pos = linkNode.next;
  8. }
  9. return ans;

这次比赛的难度其实还可以,不过链表都属于模拟题,代码量大一些。

最近几次比赛我都是使用标准的键盘手势敲代码的,有点慢,但是慢慢习惯了。

再多练习一个月,应该慢慢的就敲的快了吧。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越