一道百度面试题

这题很不错,由于题目是树形,而且状态转移并不复杂,动归的思想可以从这题中很好的体现。所以把这题求解的过程详细的记录下来。微笑


原题 : // 一个有向无环图,只有一个入口和一个出口,插入若干个节点,使得从入口到出口的所有路径长度相同(长度是路径经过的节点的个数)。
   // 并分析时空复杂度。


分析 : 

题目要求插入若干个点,使得从入口到出口的所有路径长度相同 。
首先将题目表达的更明确些:
设e为图的出口节点,i为图的非出口节点,要使i到e的所有路径长度相同。



直观的感觉可以分两步解决这题:先找出i到e的所有路径中最长的路径长度,再将剩余路径补上一块,使得从i到e的所有路径长度相等。
如果第一步得到解决,那么第二步将变得很简单。所以问题的关键转移到如何解决第一步,即找出i到e的所有路径长度的最大值


求最值问题,就很容易联想到动归。但如果能找到问题存在子问题,并且子问题间重叠,那么就很有可能可以应用动归了。


动归的第一步,寻找子问题( 即最优子结构 )。
见图:
可以发现 i到e的最大路径长度 = 所有i的子节点到e的最大路径长度 的最大值+1
好了,找到子问题了,下面来看看能否找出子问题间的重叠。
^_^,找到了,节点j1有两个父节点,要求出j1的两个父节点到e的最大路径长度,都要经过节点j1,即先求出j1到e最大路径长度

原问题的子问题与子问题的重叠性都找到了,这题可以用动归解决了。





动归的第二步,给出一个递归的方程。
现在来定义问题状态:D[i],表示节点i到e的最大路径长度。
上面的关系可以用递归方程表示:
D[i] = Max{ D[j] } + 1 ( 其中 i != e )
D[e] = 1                     ( 出口节点到自身的长度设为1 )



现在可以根据第二步的方程很容易写出动归的程序了,完成解决这题的第一步了。

不过这题并不是要求某节点到出口节点的最大路径,而是要求节点到出口的所有路径长度相同。

这一步比较简单,当求出某节点的所有子节点到出口的最大路径长度后,只需对所有子节点到出口的路径补上一块,使这些路径长度相同即可。


由于这题是树形结构,更适合记忆化搜索,下面给出记忆化搜索的伪代码。

其中 Table[]是备忘录,以免子问题被多次重复计算。

参数i是节点标号,j是i的子节点标号。


int DP( i )
{
	// 节点i已求
	if( Table[i] != 0 )
		return Table[i];

	// 节点i未求,从所有子问题求得i
	int maxV = 1;
	for( all sub nodes of i )
	{
		maxV = max( maxV, DP( j ) );
	}
	// 修正从节点i出发的路径长度,使所有路径长度都为maxV
	for( all sub nodes of i )
	{
		Table[j] = maxV;
	}

	// 求出节点i的下降路径长度
	Table[i] = maxV + 1;
	return Table[i];
}


最后,计算这题的时空复杂度。
如果用邻接表来实现图,可以将图表示为G( V, E ),Table[]表的长度等于节点数,因此空间复杂度由图的大小决定,O( |E| + |V| )。
所有节点和边都仅访问一次,因此时间复杂度O( |V| + |E| )












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值