LeetCode笔记:Weekly Contest 361

  • [LeetCode笔记:Weekly Contest 361](#LeetCode笔记:Weekly Contest 361)
    • [0. 吐槽](#0. 吐槽)
    • [1. 题目一](#1. 题目一)
      • [1. 解题思路](#1. 解题思路)
      • [2. 代码实现](#2. 代码实现)
    • [2. 题目二](#2. 题目二)
      • [1. 解题思路](#1. 解题思路)
      • [2. 代码实现](#2. 代码实现)
    • [3. 题目三](#3. 题目三)
      • [1. 解题思路](#1. 解题思路)
      • [2. 代码实现](#2. 代码实现)
    • [4. 题目四](#4. 题目四)
      • [1. 解题思路](#1. 解题思路)
      • [2. 代码实现](#2. 代码实现)

0. 吐槽

双周赛的4道题水的不行,然后下午就翻车了,4道题把我折磨的......

主要做第三题的时候状态太差了,一开始遇到超时就懵了,不过后来写一下公式之后感觉还是很直接的,感觉纯粹就是昨晚没休息好导致大脑还是懵逼的......

第四题倒是多少有点成就感,思路其实很直接,不过也是遇到了超时问题,优化之后能把第四题搞定也是挺爽的。

1. 题目一

给出题目一的试题链接如下:

1. 解题思路

这一题我的思路很暴力,就是在范围内对每个数字进行一下判断就是了......

2. 代码实现

给出python代码实现如下:

python 复制代码
class Solution:
    def countSymmetricIntegers(self, low: int, high: int) -> int:
        
        def is_symmetric(num):
            s = str(num)
            n = len(s)
            if n % 2 == 1:
                return False
            s1 = sum(int(x) for x in s[:n//2])
            s2 = sum(int(x) for x in s[n//2:])
            return s1 == s2
        
        res = [x for x in range(low, high+1) if is_symmetric(x)]
        return len(res)

提交代码评测得到:耗时922ms,占用内存16.2MB。

2. 题目二

给出题目二的试题链接如下:

1. 解题思路

这一题获得被25整除的数,那么就一定只有5种情况:

  1. 后两位是00
  2. 后两位是25
  3. 后两位是50
  4. 后两位是75
  5. 这个数为0

我们逐次考察一下这五种情况能否实现以及需要删除的数的个数即可。

2. 代码实现

给出python代码实现如下:

python 复制代码
class Solution:
    def minimumOperations(self, num: str) -> int:
        n = len(num)
        
        def find_pattern(pattern):
            idx = len(pattern)-1
            cnt = 0
            for ch in num[::-1]:
                if ch == pattern[idx]:
                    idx -= 1
                else:
                    cnt += 1
                if idx < 0:
                    break
            return n if idx >= 0 else cnt
        
        res = [
            find_pattern("00"),
            find_pattern("25"),
            find_pattern("50"),
            find_pattern("75"),
            n - Counter(num)["0"]
        ]
        
        return min(res)

提交代码评测得到:耗时40ms,占用内存16.3MB。

3. 题目三

给出题目三的试题链接如下:

1. 解题思路

这一题思路上其实挺直接的,就是根据给定的modulo和k,找到所有关于modulo余数为k的数字所在的位置(假设有 n n n个),然后就可以将原始的数组分为 n + 1 n+1 n+1段。

我们只需要分别考察起点在这 n + 1 n+1 n+1段当中的情况时,考察终点的位置分布即可,显然可以得到如下公式:

s = ∑ i = 0 n ∑ j = i + k n n i × n j s = \sum\limits_{i=0}^{n}\sum\limits_{j=i+k}^{n}n_i \times n_j s=i=0∑nj=i+k∑nni×nj

唯一需要注意的是,如果 k = 0 k=0 k=0,问题需要特殊考虑一下,因为 k = 0 k=0 k=0实际就是 k = m o d u l o k=modulo k=modulo的情况,此时两个起点和终点需要取在同一个interval当中,因此需要特殊考察一下。

Anyway,基本也就这样了,不过实际在做的时候我脑残了一下,直接上了个二重循环,然后就炸了......后来想了半天浪费了巨多时间也没想明白,直到我把上面那个式子写了一下,才发现直接再加一个累积数组也就搞定了......

唉,严重失误......

2. 代码实现

给出python代码实现如下:

python 复制代码
class Solution:
    def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:
        s = [1 if x % modulo == k else 0 for x in nums]
        indices = [idx for idx, x in enumerate(s) if x == 1]
        n, m = len(s), len(indices)

        if indices == []:
            return n*(n+1) // 2 if k == 0 else 0
        
        intervals = [indices[0]+1] + [indices[i+1] - indices[i] for i in range(m-1)] + [n - indices[m-1]]
        n = len(intervals)
        
        res = 0 if k != 0 else sum(x*(x-1)//2 for x in intervals)
        
        k = modulo if k == 0 else k
        
        s = [x for x in intervals]
        for i in range(n-1, -1, -1):
            if i+modulo < n:
                s[i] += s[i+modulo]
        
        for i in range(n-k):
            res += intervals[i] * s[i+k]        
        
        return res

提交代码评测得到:耗时821ms,占用内存31.5MB。

4. 题目四

给出题目四的试题链接如下:

1. 解题思路

这一题思路上其实也很直接,就是对每一个query,找到两点间的路径,然后所需要做的op的次数就是路径长度减去其中相同权重的边长出现的最大次数。

于是,问题就变成了如何快速地找到任意两个点之间的路径,以及其中的各个权重的边长分布。

我们随机选择一个点作为根节点,显然,用一个dfs我们就能够简单地找到从根节点到任意节点之间的路径。

而要找到任意两点之间的路径,我们只需要找到这两个节点最近的一个公共父节点,然后把下面分叉的两个子路径拼接在一起就是这两个点之间的连通路径了。

因此,这里的问题事实上就变成了如何高效地找到这个公共父节点,我一开始直接用了一个遍历,然后就凉凉了......这里也是卡的我时间最久的地方,不过后来突然想到,可以直接用一个二分搜索就行了,因为是找到最后一个节点,是指后续两条路径的走向不同。

由此,我们就不会再出现超时问题了......

然后,剩下的问题就在于如何统计这个路径当中的所有权重了,当然,一种直接的思路就是遍历一下,不过估摸着一定会超时......

考虑到题目中限制了权重仅可能为1到26,因此事实上我们可以用一个26元的数组来表征从根节点到任意节点地路径当中各个权重的边出现的次数。此时,假设两节点 u , v u,v u,v的最近的一个公共父节点为 p p p,那么 u , v u,v u,v这段路径当中所有权重的边长出现的次数事实上就是 u − p + v − p u-p+v-p u−p+v−p了。综上,我们也就可以快速地得到每一个query的答案了。

2. 代码实现

给出python代码实现如下:

python 复制代码
class Solution:
    def minOperationsQueries(self, n: int, edges: List[List[int]], queries: List[List[int]]) -> List[int]:
        if n == 1:
            return [0 for _ in queries]
        
        graph = defaultdict(list)
        weights = defaultdict(int)
        for u, v, w in edges:
            graph[u].append(v)
            graph[v].append(u)
            weights[(u, v)] = w
            weights[(v, u)] = w
            
        _max = 0
        for i in range(n):
            if len(graph[i]) > _max:
                u0 = i
                
        parent = {u0: -1}
        nodes = {u0: tuple([0 for _ in range(26)])}
        paths = {u0: [u0]}
        
        def dfs(u):
            nonlocal parent, nodes, paths
            val = list(nodes[u])
            for v in graph[u]:
                if v == parent[u]:
                    continue
                parent[v] = u
                w = weights[(u, v)]
                val[w-1] += 1
                nodes[v] = tuple(val)
                val[w-1] -= 1
                paths[v] = paths[u] + [v]
                dfs(v)
            return
        
        dfs(u0)

        def get_latest_ancestor(u, v):
            n = min(len(paths[u]), len(paths[v]))
            if paths[u][n-1] == paths[v][n-1]:
                return paths[u][n-1]
            i, j = 0, n-1
            while j-i > 1:
                m = (i+j) // 2
                if paths[u][m] == paths[v][m]:
                    i = m
                else:
                    j = m
            return paths[u][i]

        def query(u, v):
            p = get_latest_ancestor(u, v)
            path = [x+y-2*z for x,y,z in zip(nodes[u], nodes[v], nodes[p])]
            return sum(path) - max(path)
        
        return [query(u, v) for u, v in queries]

提交代码评测得到:耗时5087ms,占用内存432.4MB。