LeetCode 双周赛 91 题解

plus2047 于 2022-11-12 发布

本周双周赛难度一般,第三题和第四题难度差不多,第三题甚至要麻烦一些。

1. 不同的平均值数目

image

按照要求操作即可。注意两个要点:

class Solution:
    def distinctAverages(self, nums: List[int]) -> int:
        nums.sort();
        return len(set(x + y for x, y in zip(nums, nums[::-1])))

2. 统计构造好字符串的方案数

image

一个非常简单、非常典型的 DP,很适合用作大学一年级学完 DP 之后的课后练习的第一题。

反复确认了好几遍这个 0 和 1 没啥特别之处。

class Solution:
    def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
        mod = 10 ** 9 + 7
        dp = [1] + [0] * (high)
        for i in range(1, high + 1):
            dp[i] += dp[i - zero] if i - zero >= 0 else 0
            dp[i] += dp[i - one] if i - one >= 0 else 0
            dp[i] %= mod
        return sum(dp[low:high + 1]) % mod

3. 树上最大得分和路径

image image image

这个题目讨论区有人认为作为第三题太难了。实际上这个题目只是稍微麻烦了一点,一点都不难。

这个题目比较简单的点在于,由于给定的是无向树而不是图,所以任意两个节点之间路径存在且唯一。因此,Bob 的路是完全确定的,可以先把 Bob 的路径标记出来,包括他到达路径上每一个点的时刻。而 Alice 前往任意一个叶节点的路径也是唯一的,可以用一个 DFS 遍历一遍图,把 Alice 的路径全部找出来,并在遍历过程中处理与 Bob 相交的节点。

class Solution:
    def mostProfitablePath(self, edges: List[List[int]], bob: int, amount: List[int]) -> int:
        n = len(amount)
        
        # 建图
        graph = [[] for _ in range(n)]
        for x, y in edges:
            graph[x].append(y)
            graph[y].append(x)
        
        # 求以 0 为根节点时,每个节点的父节点
        # bob 的路径就是每次前往父节点,直到到达 0
        parent = [-1] * n
        def get_parent(node, p):
            parent[node] = p
            for child in graph[node]:
                if child != p:
                    get_parent(child, node)
        get_parent(0, -1)
        
        # 求 bob 到达其路径上的每个节点的时刻
        # 未到达节点时刻标记为 n
        t = 0
        visit = [n] * n
        while bob != -1:
            visit[bob] = t
            t += 1
            bob = parent[bob]
        
        # 标记 alice 到达每个叶节点时总得分
        # node: 当前节点
        # parent: dfs 路径的上一个节点
        # time: 也即 dfs 深度
        # got: 路径总得分
        # result: 记录叶节点总得分的数组
        def dfs(node, parent, time, got, result):
            if visit[node] > time:
                # bob 未曾到达
                got += amount[node]
            elif visit[node] == time:
                # 与 bob 同时到达
                got += amount[node] // 2
            
            # 先假设这是个叶节点,更新得分
            result[node] = got
            
            for child in graph[node]:
                if child != parent:
                    dfs(child, node, time + 1, got, result)
                    # 如果有任意一个字节点,则说明这不是个叶节点,删除得分
                    result[node] = float("-inf")
        
        # 得分初始化为 -inf 因为这个问题中得分可以为负数
        res = [float("-inf")] * n
        dfs(0, -1, 0, 0, res)
        return max(res)

4. 根据限制分割消息

image

从各个角度来说,这个问题都不是很难。我给出的解法也不是最优的,但思路比较简单。

这种复杂的字符串操作问题最好使用 Python 实现。这个问题的难点在于,不同的总消息数下后缀长度不同,每一个部分由于序号不同,后缀长度也不同。

思路上最简单的做法是,只要确认了总消息数长度,分割方法就几乎确定了,因此可以尝试总消息数为 9, 99, 999 等等,如果能够成功分割,再按照真实的分割数量重新生成一遍。

这个代码比较浪费,在尝试时不必生成字符串,只需要检查合法与否就可以了。但因为数据规模不大、O(n) 算法,这样也是可以的。

class Solution:
    def splitMessage(self, message: str, limit: int) -> List[str]:
        
        # 一个数字的十进制表示长度
        # 直接使用 len(str(x)) 也可以,但开销比较大
        def slen(x):
            return 1 if x < 10 else 2 if x < 100 else 3 if x < 1000 else 4 if x < 10000 else 5
        
        # 按照总数为 n_part 尝试进行分割
        def check(n_part):
            n_part_len = slen(n_part)
            message_len = len(message)
            # idx 是下一个需要分配的字符
            idx = 0
            res = []
            while idx < message_len and len(res) < n_part:
                cost = 3 + n_part_len + slen(len(res) + 1)
                if cost >= limit:
                    # 后缀大于等于 lim 则无法分割
                    return []
                used = limit - cost
                # 生成一个分割
                res.append("%s<%s/%s>" % (message[idx:idx + used], len(res) + 1, n_part))
                idx += used
            
            # 检查是否合法并返回
            return res if idx >= message_len and len(res) <= n_part else []

        # 尝试总长度为 9, 99, 999 等等
        # 最大尝试 9999, 如果仍然无法分割,则一定无法分割,因为 len(s) < 1e4
        curr_total = 9
        while slen(curr_total) < 5:
            res = check(curr_total)
            if res:
                # 一旦分割成功,则按照真实长度重新生成一次
                res = check(len(res))
                return res
            curr_total = curr_total * 10 + 9

        return []