思路
关于树的深度搜索操作,一般都有递归和栈两种实现。
此题对树进行中序遍历,在遍历过程中操作前后两个结点,所以需要pre指针。
时间复杂度O(n),空间复杂度O(1)。
正确的递归代码
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private TreeNode pre;
private void helper(TreeNode curr) {
if(curr == null) return ;
helper(curr.left);
curr.left = pre;
if(pre != null) {
pre.right = curr;
}
pre = curr;
helper(curr.right);
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) return null;
helper(pRootOfTree);
TreeNode p = pRootOfTree;
// 类似非递归代码,设置一个成员变量可以无需循环查找
while(p.left != null) {
p = p.left;
}
return p;
}
}
错误的递归代码
public class Solution {
private void helper(TreeNode pre, TreeNode curr) {
if(curr == null) return ;
helper(pre, curr.left);
curr.left = pre;
if(pre != null) {
pre.right = curr;
}
pre = curr;
helper(pre, curr.right);
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) return null;
helper(null, pRootOfTree);
TreeNode p = pRootOfTree;
while(p.left != null) {
p = p.left;
}
return p;
}
}
非递归代码
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) return null;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode first = null;
TreeNode p = pRootOfTree;
TreeNode pre = null;
while(p != null || !stack.empty()) {
while(p != null) {
stack.push(p);
p = p.left;
}
p = stack.pop();
if(pre != null) {
p.left = pre;
pre.right = p;
pre = p;
} else {
p.left = null;
pre = p;
first = p;
}
p = p.right;
}
return first;
}
}
笔记
错误递归代码主要是递归携带的pre参数受到了运行时栈帧的影响,先压入的栈帧无法感知到后压入栈帧对pre的修改,所以在栈帧弹出时,后弹出的栈帧使用的依旧是旧的pre参数。但整个递归函数的运行,per参数是需要在递归每一层更新的,也就是说先入栈的栈帧将pre参数传递给后面入栈的栈帧,同时先弹出的栈帧,需要将修改后的pre同步给还未弹出的栈帧。引用传递并没有这个效果,所以虽然pre是引用传递,但每一个栈帧在运行时,使用的仍是当初保存的旧的引用。
引用传递保证的是不同变量指向同一内存(区别于C++的引用)
所以这里需要使用成员变量,使每一个栈帧操作的都是同一个pre。