LeetCode笔记:Weekly Contest 198 比赛记录

  • Weekly Contest 198
    • 0. 赛后感
    • 1. 题目一
    • 2. 题目二
    • 3. 题目三
    • 4. 题目四

0. 赛后感

在体验了几个星期的简单比赛之后,终于这次的比赛让我再一次想起了当年刚开始参加比赛的时候被卡死在一道题的恐惧。

这次真的是又一次感受到了绝望的滋味,一场比赛下来居然只搞定了两题,原则上比较简单的第二题我花了一个多小时,还错了好几次,虽然中间有不少是因为超时或者题目理解错误,但还是让人感受到无比的绝望。

但是整体来看吧,这次的第一名用时从前几周的十分钟左右暴增到35分钟,去除因错题导致的时间惩罚,第一梯队的耗时也基本都在30分钟量级,从这来看,这次的题目整体上难度确实比之前更大。

但是另一方面,从耗时分布来看,大家基本是卡在了后两题,前两题似乎并没有对大部分人造成太多的困扰,这一点无疑对我产生了暴击,卡在了第二题的我表示真的难受。虽然思路还算清楚,但是中间细节磕磕碰碰花费了我太多的时间,而其他人既然可以在如此短的时间内搞定,说明这样的题目一定存在一些固定的模板,而我必须要好好地补习一下这些模板了。

唉,总而言之,过去的就让它过去吧,好好整理一下,前车之鉴,后事之师吧,无论如何,目前也只能这么安慰一下自己了。。。

1. 题目一

给出题目链接如下:

  • https://leetcode.com/contest/weekly-contest-198/problems/water-bottles/

整的来说,第一题还是中规中矩的,普通的按照最优策略进行迭代即可,即每喝完numExchange瓶饮料就立刻兑换一瓶新的饮料,知道无法进行兑换为止。

给出实现代码如下:

class Solution:
    def numWaterBottles(self, numBottles: int, numExchange: int) -> int:
        ans = 0
        while numBottles >= numExchange:
            numBottles -= numExchange-1
            ans += numExchange
        ans += numBottles
        return ans

当然,这道题本质就是一道小学奥数题,我们可以直接给出解析解如下:

class Solution:
    def numWaterBottles(self, numBottles: int, numExchange: int) -> int:
        return 1 + (numBottles - 1) // (numExchange-1) * numExchange + (numBottles - 1) % (numExchange-1)

2. 题目二

题目二的试题链接如下:

  • https://leetcode.com/contest/weekly-contest-198/problems/number-of-nodes-in-the-sub-tree-with-the-same-label/

其内容其实并不复杂,无非就是通过枚举边的方式给出了一颗树,而后求解每个元素的所有下级节点中与该节点拥有相同的label的节点数目(包含其节点本身)。

因此,我们的解题思路就是:

  1. 找出每个节点的父节点与子节点列表;
  2. 求解每个节点的同label下方节点数目。

给出我们的代码实现如下:

class Solution:
    def countSubTrees(self, n: int, edges: List[List[int]], labels: str) -> List[int]:
        # 获取父节点与子节点对应关系
        # 即leaf2root 以及 root2leaf 两个字典
        linked = {
     i: [] for i in range(n)}
        for a,b in edges:
            linked[a].append(b)
            linked[b].append(a)
        stack = [0]
        have_seen = set()
        leaf2root = {
     }
        root2leaf = {
     }
        while stack != []:
            node = stack.pop(0)
            if node in have_seen:
                continue
            have_seen.add(node)
            root2leaf[node] = [i for i in linked[node] if i not in have_seen]
            for i in root2leaf[node]:
                leaf2root[i] = [node]
            stack.extend(root2leaf[node])

        # 统计每个节点中各个标签的总量
        labelset = set("abcdefghijklmnopqrstuvwxyz")
        counter = {
     i: {
     k:0 if labels[i] != k else 1 for k in labelset} for i in range(n)}
        stack = [i for i in range(n) if i not in root2leaf.get(i, []) == []]
        have_seen = set()
        while stack != []:
            node = stack.pop(0)
            if node in have_seen:
                continue
            elif any(p not in have_seen for p in root2leaf.get(node, [])):
                stack.append(node)
                continue
            have_seen.add(node)
            for leaf in root2leaf.get(node, []):
                for l in labelset:
                    counter[node][l] += counter[leaf][l]
            stack.extend(leaf2root.get(node, []))
        return [counter[i][labels[i]] for i in range(n)]

不用吐槽,我知道这个代码实现有点丑,而且其实现效率也低的可以,统计耗时7952ms,而最优效率的解法实现仅需1448ms,效率差了近乎一个量级,这实在让人没有优化的动力。

看了一下top rank的各位大佬们的解题思路,发现整体思路上其实和我并没有太大的差异,但是在实现上与我有所不同,具体包括:

  1. 在构建父子节点的对应关系时,我使用了字典,而大佬们都使用了数组,可以在时间上更加优化;
  2. 在搜索方面,我是从底层叶子节点开始由下往上地遍历,而大佬们都采用了dfs,即深度优先遍历的方式。
  3. 在2的实现上,我自己构建了一个不怎么优秀的栈,而大佬们则是直接采用了递归的方式简化代码。

基于大佬们的代码,我们仿写得到代码实现如下:

class Solution:
    def countSubTrees(self, n: int, edges: List[List[int]], labels: str) -> List[int]:
        adj = [[] for i in range(n)]
        for a, b in edges:
            adj[a].append(b)
            adj[b].append(a)
        ans = [0 for i in range(n)]
        counter = [[0 for i in range(26)] for k in range(n)]
        
        def dfs(node, parent):
            counter[node][ord(labels[node]) - ord('a')] += 1
            for p in adj[node]:
                if p == parent:
                    continue
                dfs(p, node)
                for i in range(26):
                    counter[node][i] += counter[p][i]
            ans[node] = counter[node][ord(labels[node]) - ord('a')]
            
        dfs(0, -1)
        return ans

运行总时长从7952ms缩减至了2972ms。

3. 题目三

给出题目三的链接如下:

  • https://leetcode.com/contest/weekly-contest-198/problems/maximum-number-of-non-overlapping-substrings/

这一题大概算是这次比赛中最难的一道题了,整体的想法大概使用动态规划来实现,但是如何实现这个动态规划就完全没有一个清晰的思路。

比赛中把最后的半个小时时间完全的浪费在了这道题目上大概是我这次最大的一个失误之一了。

看了一下大佬们的耗时,基本也都在20分钟量级,换做其他时候,估计都已经完成一次比赛了。。。

另一方面,看了一下大佬们的实现代码,发现也极其的不友好,基本都是4、50行起底,且没有模块拆分,完全没有去读他们代码的欲望,所以,这里就暂时不讨论这题了,等哪天我自己有思路然后实现出来了再来这里做一下补充吧。。。嗯,大概会的。。。

4. 题目四

同样,我们首先给出题目四的题目链接如下:

  • https://leetcode.com/contest/weekly-contest-198/problems/find-a-value-of-a-mysterious-function-closest-to-target/

这次的题目四虽然被归类为hard,但是其难度事实上是较为亲民的,虽然比赛中我完全没有时间去看这道题,但是在事后去看这道题的时候感觉并非无从下手,无非就是如何尽可能地优化时间复杂度的问题,将其从 O ( N 2 ) O(N^2) O(N2)变为 O ( N ) O(N) O(N)而已。

而看了大佬们的解题思路,也都是比较简单的,基本就是一看答案就秒会的那种。

下面,我们给出参考大佬们的解答后得到的代码实现如下:

import math

class Solution:
    def closestToTarget(self, arr: List[int], target: int) -> int:
        cache = set()
        ans = math.inf
        for n in arr:
            tmp = {
     n}
            for m in cache:
                tmp.add(n & m)
            for m in tmp:
                ans = min(ans, abs(m-target))
            cache = tmp
        return ans

你可能感兴趣的:(leetcode笔记,leetcode,算法)