【限时免费】20天拿下华为OD笔试之【DFS+树形DP】2023Q2-获取食物游戏【闭着眼睛学数理化】全网注释最详细分类最全的华为OD真题题解

【DFS+树形DP】2023Q2-获取食物游戏

题目描述与示例

题目描述

主办方设计了一个获取食物的游戏。

游戏的地图由 N个方格组成,每个方格上至多 2个传送门,通过传送门可将参与者传送至指定的其它方格。 同时,每个方格上标注了三个数字:

  1. 第一个数字 id:代表方格的编号,从0N - 1,每个方格各不相同;
  2. 第二个数字 parent-id:代表从编号为parent-id的方格可以通过传送门传送到当前方格(-1则表示没有任何方格可以通过传送门传送到此方格,这样的方格在地图中有且仅有一个);
  3. 第三个数字value:取值在[100,100]的整数值之间,正整数代表参与者得到相应取值单位的食物,负整数代表失去相应数值单位的食物(参与者可能存在临时持有食物为负数的情况),0则代表无变化。

此外,地图设计时保证了参与者不可能到达相同的方格两次,并且至少有一个方格的value是正整数。

游戏开始后,参与者任意选择一个方格作为出发点,当遇到下列情况之一退出游戏:

  • 参与者当前所处的方格无传送门;
  • 参与者在任意方格上主动宣布退出游戏。

请计算参与者退出游戏后,最多可以获得多少单位的食物。

示例一

输入

7
0 1 8
1 -1 -2
2 1 9
4 0 -2
5 4 3
3 0 -3
6 2 -3

输出

9

说明

参与者从方格0出发,通过传送门到达方格4,再通过传送门到达方格5。一共获得8+(−2)+3=9个单位食物,得到食物最多 或者参与者在游戏开始时处于方格2,直接主动宣布退出游戏,也可以获得9个单位食物。

示例二

输入

3
0 -1 3
1 0 1
2 0 2

输出

5

说明

参与者从方格0出发,通过传送门到达方格2,一共可以获得3+2=5个单位食物,此时得到食物最多。

解题思路

首先,题目存在两个非常重要的条件:

  1. 地图设计时保证了参与者不可能到达相同的方格两次
  2. parent-id = -1的方格在地图中有且仅有一个。

这保证题目一定是一个树形结构,即这个地图本身不存在环,而唯一存在的parent-id = -1的方格即为整个树形结构的根节点。这个设定大大地减少了我们思考问题的难度。

整个问题也就可以转变为:寻找树形结构中的一条路径,使得路径上所有节点的和最大

树形结构,通常会用二叉树来表示,但这道题给的是idparent-idval这三个数据,因此我们考虑用一个哈希表nodes来储存这个树形结构,哈希表的key为父节点编号parent-id,哈希表的value为该父节点下的子节点id。而每一个节点的val我们可以储存在一个长度为n的列表vals中,vals的索引即为这个节点的编号id

我们思考这样的问题,假设我们走到某一个节点cur_node,为了计算包含当前节点的路径最大和cur_max,我们都将面临以下选择:

  1. 以这个当前节点作为一条新路径的开始节点,即舍弃前面的路径。
  2. 以这个当前节点作为之前某一条路径的延伸。

而上述选择的结果,实际上取决于之前某一条路径能够取得比0更大的路径和。

假设当前节点cur_node之前的路径最大和pre_max已经被计算出来,如果:

  1. pre_max < 0,那么我们将舍弃前面的路径,因为如果加上前面的路径,我们只会使得当前路径总和cur_max更小,因此我们选择以这个当前节点作为一条新路径的开始节点。
  2. pre_max >= 0,无论当前节点的值如何,我们都将以这个当前节点为之前某一条路径的延伸,因为这样才能使得cur_max尽可能地大。

上述两种选择可以合并为if-else语句或者三元表达式:

cur_max = vals[cur_node] if pre_max < 0 else vals[cur_node] + pre_max

在每一次计算得到cur_max之后,我们都必须更新一次全局的最大值ans

ans = max(ans, cur_max)

那么之前的路径最大和pre_max又应该如何得到呢?每一次算出cur_max,对于当前节点cur_node的下一个节点nxt_node而言,cur_max就是nxt_nodepre_max。因此我们整个过程可以非常方便地使用DFS****递归来进行。

而调用dfs()递归函数的入口为根节点root,根节点的pre_max0,表示根节点前面没有任何路径。

顺带提一嘴,对于上述最关键的cur_max的计算步骤,实际上也是一个dp过程。因为我们每一次都会根据pre_max的结果来对cur_max的计算做选择,所以式子cur_max = vals[cur_node] if pre_max < 0 else vals[cur_node] + pre_max实际上就是一个动态转移方程,只不过我们是使用在dfs()递归****函数中传参的形式来传递每一次的计算结果而已,而不需要显式地把dp数组写出来。

这也提醒我们,dp递归****实际上有着非常深刻的内在联系,这一点需要同学们多多体会。

时空复杂度

时间复杂度:O(N)。需要遍历整个树形结构,每一个节点仅需经过一次。

空间复杂度:O(N)。哈希表nodes中的列表vals所占空间。

代码

from collections import defaultdict
from math import inf

n = int(input())
nodes = defaultdict(list)
vals = [0] * n
for _ in range(n):
    idx, parent, val = map(int, input().split())
    # 将树形结构用哈希表的形式储存,key为父节点编号,value为该父节点下的子节点
    nodes[parent].append(idx)
    # vals列表储存每一个节点的val
    vals[idx] = val

root = nodes[-1][0]         # parent为-1的节点为根节点root

# 初始化答案为-inf
ans = -inf

# dfs()函数中包含两个参数:
# cur_node表示【当前节点】
# pre_max 表示【以当前节点的父节点为结尾的某条路径】的最大和
def dfs(cur_node, pre_max):
    # ans是一个位于函数外部的全局变量,需要声明一下
    global ans
    # 两种情况:
    # 1. 如果之前路径最大和是个负数,那么不再考虑前面的路径,应该从当前节点重新出发,
    # 即 cur_max = vals[cur_node]
    # 2. 如果之前路径最大和是个正数,那么以当前节点为结尾的路径的最大和应该加上之前路径最大和,
    # 即 cur_max = vals[cur_node] + pre_max
    cur_max = vals[cur_node] if pre_max < 0 else vals[cur_node] + pre_max
    # 计算了当前路径最大和之后,更新答案ans
    ans = max(ans, cur_max)
    # 如果当前节点cur_node不位于nodes哈希表中,即cur_node不是任何节点的父节点
    # 表示已经到达路径末端,终止递归
    if cur_node not in nodes:
        return
    # 如果节点cur_node位于nodes哈希表中,即仍有向下走的可能性
    else:
        # 遍历当前节点cur_node的所有下一个节点
        for nxt_node in nodes[cur_node]:
            # 对于下一个节点nxt_node而言,cur_node的cur_max就是它的pre_max
            dfs(nxt_node, cur_max)

# 调用dfs函数,入口为根节点root,根节点的pre_max为0,即前面没有任何路径
dfs(root, 0)
print(ans)

华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

你可能感兴趣的:(最新华为OD真题,#,dp,#,DFS,深度优先,华为od,游戏)