【力扣】2003. 每棵子树内缺失的最小基因值

【力扣】2003. 每棵子树内缺失的最小基因值

1. 题目介绍

有一棵根节点为 0 的 家族树 ,总共包含 n 个节点,节点编号为 0 到 n - 1 。

  • 给你一个下标从 0 开始的整数数组 parents ,其中 parents[i] 是节点 i 的父节点。由于节点 0 是 根 ,所以 parents[0] == -1 。
  • 给你一个下标从 0 开始的整数数组 nums ,其中 nums[i] 是节点 i 的基因值,且基因值 互不相同 。总共有 105 个基因值,每个基因值都用 闭区间 [1, 10^5] 中的一个整数表示。

请你返回一个数组 ans ,长度为 n ,其中 ans[i] 是以节点 i 为根的子树内 缺失 的 最小 基因值。

  • 节点 x 为根的 子树 包含节点 x 和它所有的 后代 节点。

示例

  • 1
    在这里插入图片描述
  • 2
    在这里插入图片描述
  • 3
    在这里插入图片描述

提示
在这里插入图片描述

2. 思路

  • 方法一:深度优先搜索 + 启发式合并

    • 为了优化时间复杂度,基于启发式合并的原理(参考 OIWiki 树上启发式合并),在合并子节点基因值集合时,需要将小集合合并到大集合。
    • 时间复杂度:O(nlog⁡n),其中 n 是树的节点数目。基于启发式合并的基因值集合合并需要 O(nlog⁡n)。
    • 空间复杂度:O(nlog⁡n)。
  • 方法二:深度优先搜索

    • 根据题意,所有基因值互不相同,因此最多只有一个基因值为 1 的节点。
    • 首先,如果不存在基因值为 1 的节点,那么 1 就是以任一节点为根的子树缺失的最小基因值。
    • 如果存在基因值为 1 的节点,那么可以把所有节点分成两种类型:
      • 以该节点为根的子树不包含基因值为 1 的节点;
      • 以该节点为根的子树包含基因值为 1 的节点。
    • 对于第一种类型,类似地,1 就是以任一节点为根的子树缺失的最小基因值。
    • 对于第二种类型,这类节点是基因值为 1 的节点的祖先节点(充分必要条件)。
      • 我们可以先找到基因值为 1 的节点,然后可以通过 parents 数组依次遍历它的祖先节点(类似于遍历链表)。在遍历过程中,我们对节点 node 执行一次深度优先搜索(使用 visited 来记录已经访问过的节点,避免重复搜索),将访问过的节点的基因值加入到集合 geneSet 中,然后不断枚举基因值(类似于方法一,枚举值为上一次枚举的缺失的最小基因值),直到找到节点 node 为根的子树缺失的最小基因值。
    • 时间复杂度:O(n),
    • 空间复杂度:O(n)。

3. 解题代码

class Solution:
    def smallestMissingValueSubtree(self, parents: List[int], nums: List[int]) -> List[int]:
        n = len(parents)
        childs = [[] for _ in range(n)]
        for i in range(1, n):
            childs[parents[i]].append(i)

        res = [1] * n
        def dfs(node):
            geneSet = {nums[node]}  # 当前节点基因
            for child in childs[node]:
                childGeneSet, y = dfs(child)
                res[node] = max(res[node], y)
                if len(childGeneSet) > len(geneSet):
                    geneSet, childGeneSet = childGeneSet, geneSet
                geneSet.update(childGeneSet)
            while res[node] in geneSet: # 从1开始搜索,如果存在一直累加,直到不存在为止
                res[node] += 1
            return geneSet, res[node]
        
        dfs(0)
        return res
class Solution:
    def smallestMissingValueSubtree(self, parents: List[int], nums: List[int]) -> List[int]:
        n = len(parents)
        children = [[] for _ in range(n)]
        for i in range(1, n):
            children[parents[i]].append(i)
 
        geneSet = set()
        visited = [False] * n
        def dfs(node):
            if visited[node]:
                return
            visited[node] = True
            geneSet.add(nums[node])
            for child in children[node]:
                dfs(child)

        node = nums.index(1) if 1 in nums else -1
        res, iNode = [1] * n, 1

        while node != -1:
            dfs(node)
            while iNode in geneSet:
                iNode += 1
            res[node], node = iNode, parents[node]
        return res

4. Danger

力扣(LeetCode)是领扣网络旗下专注于程序员技术成长和企业技术人才服务的品牌。源自美国硅谷,力扣为全球程序员提供了专业的IT技术职业化提升平台,有效帮助程序员实现快速进步和长期成长。此外,力扣(LeetCode)致力于解决程序员技术评估、培训、职业匹配的痛点,逐步引领互联网技术求职和招聘迈向专业化。

  • 据了解到的情况,Easy题和Medium 题在面试中比较常见,通常会以手写代码之类的形式出现,您需要对问题进行分析并给出解答,并于面试官进行交流沟通,有时还会被要求分析时间复杂度8与空间复杂度°,面试官会通过您对题目的分析解答,了解您对常用算法的熟悉程度和您的程序实现功底。
  • 而在一些对算法和程序实现功底要求较高的岗位,Hard 题也是很受到面试官的青睐,如果您在面试中成功Bug-Free出一道Hard题,我们相信您一定会给面试官留下很深刻的印象,并极大增加拿到Offer的概率,据相关人士统计,如果您在面试成功解出一道Hard题,拿不到Offer的概率无限接近于0。
  • 所以,力扣中Easy和Medium相当于面试中的常规题,而Hard 则相当于面试中较难的题,解出—道Hard题,Offer可以说是囊中之物。

参考

【1】https://leetcode.cn/problems/smallest-missing-genetic-value-in-each-subtree/?envType=daily-question&envId=2023-10-31

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋冬无暖阳°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值