小白友好向
题目
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路
一开始没 get 到意思,关键函数只有一个参数 pNode
传入,数据结构如下所示:
/*function TreeLinkNode(x){ this.val = x; this.left = null; this.right = null; this.next = null; }*/
结合原书描述和官方题解表述的意思,此处的 next
指向父节点,非下一节点指向
暴力
时间复杂度 O(n) ,空间复杂度 O(n)
分析废物必备写法
- 得出此时的中序遍历序列
- 取传入节点的后一位
分类处理
分类容易分漏
相比暴力,降低了空间复杂度到 O(1)
官方题解中的分类方式为看图分析找不同,再归类
私以为,此种办法小白比较容易遗漏,接着卡住,听取蛙声一片
万能分类讨论法(梦回高中数学题):
咱从规则出发分类
分到头了再对上图里的点验证一下有没有遗漏
结果相似的看看是不是分类分繁了,可整理为:
中序遍历 => 左 中 右 => 从当前结点看,其属于中,故只讨论右子树的情况
有右子树
中序遍历 => 左 中 右
最先想到有左子树的情况,下一个为右子树的最左节点(中(左中右))
没左子树的话,根据遍历规则,下一个为右子树的根节点 (中(- 中右))
- 右子树有左子树 => 结果:右子树最左节点(A, B, C, D)
- 右子树无左子树 => 结果:右子树自身节点(E, F, G)
无右子树
中序遍历 => 左 中 右
此时这部分小范围已经结束遍历了,结合遍历规则可得
接下来得根据它相对其父节点的位置再分析,即:
如为父的左子树,下一个就是父节点(中 -)中
如为父的右子树,下一个就是第一个有未访问过右子树的父节点((中 -)...) 中
注意此块得循环判断,即某部分可能是某层的右子树,再上一层可能是左子树
1. 当前结点为其父节点的左子树 => 结果:父节点 (I, L, N)
- 当前结点为其父节点的右子树 => 结果:第一个有未访问过右子树的父节点(H, J, K, M)
- 最末尾元素 => 结果:
null
(示例图片源自牛客该题中的评论区)
综上所述,总共为 5 种,分别为 ....
// 暴力 function GetNext(pNode) { if(!pNode) return null; const stack = []; const res = []; let curr = pNode; // 取到根节点 while(curr.next) { curr = curr.next; } // 中序遍历 while(stack.length || curr) { while(curr) { stack.push(curr); curr = curr.left; } const node = stack.pop(); // 题目中未声明数据不重复,故存整个节点 res.push(node); if(node.right) { curr = node.right; } } // 遍历找下一值 return res[res.indexOf(pNode) + 1]; }
// 分类 - 简化写法,两类再合并 function GetNext(pNode) { if(!pNode) return null; if(pNode.right){ // 1. 有右子树, 右子树有/无左子树 // 结果:右子树最左节点(无左子树时,自身即为最左) pNode = pNode.right; while(pNode.left){ pNode = pNode.left; } return pNode; } while(pNode.next){ // 2. 无右子树,当前结点为其父节点的右/左子树 // 结果:找第一个“当前节点”为左子树的父节点 let pRoot = pNode.next; if(pNode === pRoot.left){ return pRoot; } pNode = pNode.next; } // 3. 中序遍历最末节点 return null; }