题目:
对于一个没有重复元素的整数数组,请用其中元素构造一棵MaxTree,MaxTree定义为一棵二叉树,其中的节点与数组元素一一对应,同时对于MaxTree的每棵子树,它的根的元素值为子树的最大值。现有一建树方法,对于数组中的每个元素,其在树中的父亲为数组中它左边比它大的第一个数和右边比它大的第一个数中更小的一个。若两边都不存在比它大的数,那么它就是树根。请设计O(n)的算法实现这个方法。
给定一个无重复元素的数组A和它的大小n,请返回一个数组,其中每个元素为原数组中对应位置元素在树中的父亲节点的编号,若为根则值为-1。
测试用例:
[3,1,4,2],4返回:[2,0,-1,2]
思路:
一开始看上去很像大顶堆,但是注意堆要求的是一个完全二叉树,但是此题得到的树却不一定是完全二叉树。比如:[3,4,5,1,2],按照题目要求应该得到:
因为1左侧比它大的第一个数是5,右侧比它大的第一个数是2,它不能是4的子结点,这棵树也不是完全二叉树。
这题题目提示的比较清楚,问题在于怎么快速得到某个结点左(右)侧第一个比它大的数。我们可以用两个栈来记录。首先从左向右扫描,每向栈中加入一个数时,先把比它小的数从栈顶退出,直至遇到一个比它大的数或是栈为空,再把这个数在A中的位置压入栈中(因为题目要求返回的是父结点下标值),入栈之前需要把这个比它大的数记录下来(因为有可能是它的父结点)。然后按照同样的方法从右边扫描一遍A,遍历到相同位置时,取左右最大值的最小值的下标为答案。
代码:
class MaxTree:
def buildMaxTree(self, A, n):
# write code here
res = [n]*n
left_max = [0]
for i in range(1,n):
while len(left_max) > 0 and A[left_max[-1]] < A[i]:
left_max = left_max[:-1]
if len(left_max) > 0:
res[i] = left_max[-1]
left_max.append(i)
right_max = [n-1]
for i in range(n-2, -1, -1):
while len(right_max) > 0 and A[right_max[-1]] < A[i]:
right_max = right_max[:-1]
if len(right_max) > 0:
if res[i] == n:
res[i] = right_max[-1]
elif A[res[i]] > A[right_max[-1]]:
res[i] = right_max[-1]
right_max.append(i)
for i in range(n):
if res[i] == n:
res[i] = -1
return res
NOTE:
视屏中还介绍了为什么这样做可以构建一棵树。我的理解是:首先一个结点有且只有一个结点为父结点,且子结点一定比父结点小,所以在只有一个最大值,且不会出现环的情况下,最后一定可以形成一棵树;其次形成的一定是一棵二叉树。假设一个结点n的右侧同时有k1,k2两个子结点(n...k1...k2),根据A中没有重复元素的特点,k1和k2必有大小之分。如果k1<k2,那么因为k1右侧第一个大于大于它的数是k2,所以k1是k2的子结点而不是n的子结点(k1 < n, k2 < n, k1 < k2)。反之亦然。所以任何一个结点在一侧只会有一个子结点。