题目## 题目
题目的主要信息:
- 给定一颗二叉树的根节点,输出其中序遍历的结果
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:递归(推荐使用)
知识点:二叉树递归
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。
思路:
什么是二叉树的中序遍历,简单来说就是“左根右”,展开来说就是对于一棵二叉树,我们优先访问它的左子树,等到左子树全部节点都访问完毕,再访问根节点,最后访问右子树。同时访问子树的时候,顺序也与访问整棵树相同。
从上述对于中序遍历的解释中,我们不难发现它存在递归的子问题,根节点的左右子树访问方式与原本的树相同,可以看成一颗树进行中序遍历,因此可以用递归处理:
- 终止条件: 当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。
- 返回值: 每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。
- 本级任务: 每个子问题优先访问左子树的子问题,等到左子树的结果返回后,再访问自己的根节点,然后进入右子树。
具体做法:
- step 1:准备数组用来记录遍历到的节点值,Java可以用List,C++可以直接用vector。
- step 2:从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问。
- step 3:左子树访问完毕再回到根节点访问。
- step 4:最后进入根节点的右子树进行递归。
Java实现代码:
import java.util.*;
public class Solution {
public void inorder(List<Integer> list, TreeNode root){
//遇到空节点则返回
if(root == null)
return;
//先去左子树
inorder(list, root.left);
//再访问根节点
list.add(root.val);
//最后去右子树
inorder(list, root.right);
}
public int[] inorderTraversal (TreeNode root) {
//添加遍历结果的数组
List<Integer> list = new ArrayList();
//递归中序遍历
inorder(list, root);
//返回的结果
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)
res[i] = list.get(i);
return res;
}
}
C++实现代码:
class Solution {
public:
void inorder(vector<int> &res, TreeNode* root){
//遇到空节点则返回
if(root == NULL)
return;
//先遍历左子树
inorder(res, root->left);
//再遍历根节点
res.push_back(root->val);
//最后遍历右子树
inorder(res, root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
//递归中序遍历
inorder(res, root);
return res;
}
};
Python实现代码
import sys
class Solution:
def inorder(self, list: List[int], root: TreeNode):
# 遇到空节点则返回
if not root:
return
# 先遍历左子树
self.inorder(list, root.left)
# 再遍历根节点
list.append(root.val)
# 最后遍历右子树
self.inorder(list, root.right)
def inorderTraversal(self , root: TreeNode) -> List[int]:
# 由于python存在最大的递归深度约束,这一步是更改最大深度的限制
sys.setrecursionlimit(1500)
res = [] # 添加遍历结果的list
# 递归中序遍历
self.inorder(res, root)
return res
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n为二叉树的节点数,遍历二叉树所有节点
- 空间复杂度: O ( n ) O(n) O(n),最坏情况下二叉树化为链表,递归栈深度为 n n n
方法二:非递归(扩展思路)
知识点:栈
栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
思路:
与前序遍历类似,我们利用栈来代替递归。如果一棵二叉树,对于每个根节点都优先访问左子树,那结果是什么?从根节点开始不断往左,第一个被访问的肯定是最左边的节点,
//每次找到最左节点
while(root != NULL){
s.push(root);
root = root->left;
}
然后访问该节点的右子树,最后向上回到父问题。因为每次访问最左的元素不止对一整棵二叉树成立,而是对所有子问题都成立,因此循环的时候自然最开始都是遍历到最左,然后访问,然后再进入右子树,我们可以用栈来实现回归父问题。
具体做法:
- step 1:优先判断树是否为空,空树不遍历。
- step 2:准备辅助栈,当二叉树节点为空了且栈中没有节点了,我们就停止访问。
- step 3:从根节点开始,每次优先进入每棵的子树的最左边一个节点,我们将其不断加入栈中,用来保存父问题。
- step 4:到达最左后,可以开始访问,如果它还有右节点,则将右边也加入栈中,之后右子树的访问也是优先到最左。
图示:
Java实现代码:
import java.util.*;
public class Solution {
public int[] inorderTraversal (TreeNode root) {
//添加遍历结果的数组
List<Integer> list = new ArrayList();
Stack<TreeNode> s = new Stack<TreeNode>();
//空树返回空数组
if(root == null)
return new int[0];
//当树节点不为空或栈中有节点时
while(root != null || !s.isEmpty()){
//每次找到最左节点
while(root != null){
s.push(root);
root = root.left;
}
//访问该节点
TreeNode node = s.pop();
list.add(node.val);
//进入右节点
root = node.right;
}
//返回的结果
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)
res[i] = list.get(i);
return res;
}
}
C++实现代码:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
//辅助栈
stack<TreeNode*> s;
//当树节点不为空或栈中有节点时
while(root != NULL || !s.empty()){
//每次找到最左节点
while(root != NULL){
s.push(root);
root = root->left;
}
//访问该节点
TreeNode* node = s.top();
s.pop();
res.push_back(node->val);
//进入右节点
root = node->right;
}
return res;
}
};
Python实现代码
class Solution:
def inorderTraversal(self , root: TreeNode) -> List[int]:
# s是辅助栈
res, s = [], []
# 当树节点不为空或栈中有节点时
while root or s:
# 每次找到最左节点
while root:
s.append(root)
root = root.left
# 访问该节点
node = s[-1]
s.pop()
res.append(node.val)
# 进入右节点
root = node.right
return res
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n为二叉树的节点数,遍历二叉树所有节点
- 空间复杂度: O ( n ) O(n) O(n),辅助栈空间最大为链表所有节点数
题目的主要信息:
- 题目给出我们一棵树的其中的某一个结点指针
- 我们需要返回这棵树按照中序遍历的该节点的下一个顺序结点指针
- 树的每个节点都有三个指针,指向左子节点、右子节点、父节点
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:中序遍历递归(推荐使用)
知识点:二叉树递归
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。
思路:
我们首先要根据给定输入中的结点指针向父级进行迭代,直到找到该树的根节点;然后根据根节点进行中序遍历,当遍历到和给定树节点相同的节点时,下一个节点就是我们的目标返回节点
具体做法:
- 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 ) O(N) O(N),因为引入了存储所有节点的空间
方法二:分类直接寻找
思路:
直接寻找分为三种情况
- 如果给出的结点有右子节点,则最终要返回的下一个结点即右子树的最左下的结点
- 如果给出的结点无右子节点,且当前结点是其父节点的左子节点,则返回其父节点
- 如果给出的结点无右子节点,且当前结点是其父节点的右子节点,则先要沿着左上方父节点爬树,一直爬到当前结点是其父节点的左子节点为止,返回的就是这个父节点;或者没有满足上述情况的则返回为NULL
具体做法:
- step 1:判断该节点是否符合思路中第一点,则一直找到右子树的左下节点为返回值
- step 2:判断该节点是否符合思路中第二点,则返回当前节点的父亲节点
- step 3:判断该节点是否符合思路中第三点,则迭代向上找父节点,直到迭代的当前节点是父节点的左孩子节点为止,返回该父节点;如果不满足上述情况返回NULL
图示:
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) O(N),最大代价是当树退化成一个只包含右子节点的链表,当给定节点是中序遍历最后一个节点时,会进入情况三的分析部分,在向左上方向一直迭代直到根节点,才会发现应该返回NULL,即无下一个节点,此时代价最大。
- 空间复杂度: O ( 1 ) O(1) O(1),无额外空间的借用