主办方设计了一个获取食物的游戏。
游戏的地图由 N
个方格组成,每个方格上至多 2
个传送门,通过传送门可将参与者传送至指定的其它方格。 同时,每个方格上标注了三个数字:
id
:代表方格的编号,从0
到 N - 1
,每个方格各不相同;parent-id
:代表从编号为parent-id
的方格可以通过传送门传送到当前方格(-1
则表示没有任何方格可以通过传送门传送到此方格,这样的方格在地图中有且仅有一个);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
个单位食物,此时得到食物最多。
首先,题目存在两个非常重要的条件:
parent-id = -1
的方格在地图中有且仅有一个。这保证题目一定是一个树形结构,即这个地图本身不存在环,而唯一存在的parent-id = -1
的方格即为整个树形结构的根节点。这个设定大大地减少了我们思考问题的难度。
整个问题也就可以转变为:寻找树形结构中的一条路径,使得路径上所有节点的和最大。
树形结构,通常会用二叉树来表示,但这道题给的是id
,parent-id
和val
这三个数据,因此我们考虑用一个哈希表nodes
来储存这个树形结构,哈希表的key
为父节点编号parent-id
,哈希表的value
为该父节点下的子节点id
。而每一个节点的val
我们可以储存在一个长度为n
的列表vals
中,vals
的索引即为这个节点的编号id
。
我们思考这样的问题,假设我们走到某一个节点cur_node
,为了计算包含当前节点的路径最大和cur_max
,我们都将面临以下选择:
而上述选择的结果,实际上取决于之前某一条路径能够取得比0
更大的路径和。
假设当前节点cur_node
之前的路径最大和pre_max
已经被计算出来,如果:
pre_max < 0
,那么我们将舍弃前面的路径,因为如果加上前面的路径,我们只会使得当前路径总和cur_max
更小,因此我们选择以这个当前节点作为一条新路径的开始节点。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_node
的pre_max
。因此我们整个过程可以非常方便地使用DFS****递归来进行。
而调用dfs()
递归函数的入口为根节点root
,根节点的pre_max
为0
,表示根节点前面没有任何路径。
顺带提一嘴,对于上述最关键的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算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
绿色聊天软件戳 od1336
了解更多