题目的主要信息:
  • 题目给出我们一棵树的其中的某一个结点指针
  • 我们需要返回这棵树按照中序遍历的该节点的下一个顺序结点指针
  • 树的每个节点都有三个指针,指向左子节点、右子节点、父节点
举一反三:

学习完本题的思路你可以解决如下题目:

JZ54. 二叉搜索树的第k个节点

JZ68. 二叉搜索树的最近公共祖先

方法一:中序遍历递归(推荐使用)

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

我们首先要根据给定输入中的结点指针向父级进行迭代,直到找到该树的根节点;然后根据根节点进行中序遍历,当遍历到和给定树节点相同的节点时,下一个节点就是我们的目标返回节点

具体做法:

  • step 1:首先先根据当前给出的结点找到根节点
  • step 2:然后根节点调用中序遍历
  • step 3:将中序遍历结果存储下来
  • step 4:最终拿当前结点匹配是否有符合要求的下一个结点

Java实现代码:

import java.util.*;
public class Solution {
    ArrayList<TreeLinkNode> nodes = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        // 获取根节点
        TreeLinkNode root = pNode;
        while(root.next != null) root = root.next;
        
        // 中序遍历打造nodes
        InOrder(root);
        
        // 进行匹配
        int n = nodes.size();
        for(int i = 0; i < n - 1; i++) {
            TreeLinkNode cur = nodes.get(i);
            if(pNode == cur) {
                return nodes.get(i+1);
            }
        }
        return null;
    }
    
    // 中序遍历
    void InOrder(TreeLinkNode root) {
        if(root != null) {
            InOrder(root.left);
            nodes.add(root);
            InOrder(root.right);
        }
    }
}

C++实现代码:

class Solution {
public:
    vector<TreeLinkNode*> nodes;
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        TreeLinkNode* root = pNode;
        // 获取根节点
        while(root->next) root = root->next;    
        
        // 中序遍历用nodes储存所有节点指针
        InOrder(root);                          
        int n = nodes.size();
        
        for(int i = 0; i < n - 1; i++) {
            TreeLinkNode* cur = nodes[i];
            // 将结点进行匹配       
            if(pNode == cur) {
                // 如果有匹配到给出的结点,则下一个结点即返回结果                  
                return nodes[i+1];              
            }
        }
        // 否则如果没有下一个结点则返回NULL
        return NULL;                            
    }
    // 中序遍历
    void InOrder(TreeLinkNode* root) {          
        if(root == NULL) return;
        InOrder(root->left);
        nodes.push_back(root);
        InOrder(root->right);
    }
};

python实现代码:

class Solution:
    nodes = []
    
    def GetNext(self, pNode):
        # 查找根节点
        root = pNode
        while root.next:
            root = root.next
        
        # 中序遍历打造nodes
        self.InOrder(root)
        
        # 匹配节点
        for i in range(len(self.nodes) - 1):
            cur = self.nodes[i]
            if pNode == cur:
                return self.nodes[i+1]
        return None
        
    # 中序遍历
    def InOrder(self, root):
        if root == None:
            return
        self.InOrder(root.left)
        self.nodes.append(root)
        self.InOrder(root.right)


复杂度分析:

  • 时间复杂度:O(N)O(N),因为遍历了树中的所有节点
  • 空间复杂度:O(N)O(N),因为引入了存储所有节点的空间
方法二:分类直接寻找

思路:

直接寻找分为三种情况

  1. 如果给出的结点有右子节点,则最终要返回的下一个结点即右子树的最左下的结点
  2. 如果给出的结点无右子节点,且当前结点是其父节点的左子节点,则返回其父节点
  3. 如果给出的结点无右子节点,且当前结点是其父节点的右子节点,则先要沿着左上方父节点爬树,一直爬到当前结点是其父节点的左子节点为止,返回的就是这个父节点;或者没有满足上述情况的则返回为NULL

具体做法:

  • step 1:判断该节点是否符合思路中第一点,则一直找到右子树的左下节点为返回值
  • step 2:判断该节点是否符合思路中第二点,则返回当前节点的父亲节点
  • step 3:判断该节点是否符合思路中第三点,则迭代向上找父节点,直到迭代的当前节点是父节点的左孩子节点为止,返回该父节点;如果不满足上述情况返回NULL

图示:

alt

Java实现代码:

import java.util.*;
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        // 情况一
        if(pNode.right != null) {
            TreeLinkNode rchild = pNode.right;
            // 一直找到右子树的最左下的结点为返回值
            while(rchild.left != null) rchild = rchild.left; 
            return rchild;
        }
        
        // 情况二
        if(pNode.next != null && pNode.next.left == pNode) {
            return pNode.next;
        }
        
        // 情况三
        if(pNode.next != null) {
            TreeLinkNode ppar = pNode.next;
            // 沿着左上一直爬树,爬到当前结点是其父节点的左自己结点为止
            while(ppar.next != null && ppar.next.right == ppar) ppar = ppar.next; 
            return ppar.next;
        }
        return null;
    }
}

C++实现代码:

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        // 如果有右子树
        if(pNode->right) {    
            TreeLinkNode* rchild = pNode->right;
            // 一直找到右子树的最左下的结点为返回值
            while(rchild->left) rchild = rchild->left;    
            return rchild;
        }
        // 如果无右子树且当前结点是其父节点的左子结点
        if(pNode->next && pNode->next->left == pNode) {    
            // 返回当前结点的父节点
            return pNode->next;   
        }
        // 如果无右子树且当前结点是其父节点的右子节点
        if(pNode->next) {         
            TreeLinkNode* ppar = pNode->next;
            // 沿着左上一直爬树,爬到当前结点是其父节点的左自己结点为止
            while(ppar->next && ppar->next->right == ppar) ppar = ppar->next;    
            // 返回当前结点的父节点
            return ppar->next;    
        }
        return NULL;
    }
};

python实现代码:

class Solution:
    def GetNext(self, pNode):
        # 情况一
        if pNode.right:
            rchild = pNode.right
            # 一直找到右子树的最左下的结点为返回值
            while(rchild.left):                     
                rchild = rchild.left
            return rchild
        
        # 情况二
        # 如果无右子树且当前结点是其父节点的左子结点
        if pNode.next and pNode.next.left == pNode: 
            return pNode.next
        
        # 情况三
        if pNode.next:
            ppar = pNode.next
            # 沿着左上一直爬树,爬到当前结点是其父节点的左自己结点为止
            while(ppar.next and ppar.next.right == ppar): 
                ppar = ppar.next
            return ppar.next
        
        return None

复杂度分析:

  • 时间复杂度:O(N)O(N),最大代价是当树退化成一个只包含右子节点的链表,当给定节点是中序遍历最后一个节点时,会进入情况三的分析部分,在向左上方向一直迭代直到根节点,才会发现应该返回NULL,即无下一个节点,此时代价最大。
  • 空间复杂度:O(1)O(1),无额外空间的借用