以下是问题描述:
给定二叉树,检查它是否是自身的镜像(即,围绕其中心对称)。
例如,这个二叉树[1,2,2,3,4,4,3]是对称的:
1
/ \
2 2
/ \ / \
3 4 4 3
但是以下[1,2,2,null,3,null,3]不是:
1
/ \
2 2
\ \
3 3
来自:Determine if tree is symmetric
我花了很多时间来解决问题,我想出的解决方案是进行水平顺序遍历并检查每个级别的值是否为回文。这个实现通过了leetcode的测试。然而,当我阅读社论时,我看到了一个极短的递归程序,我一直无法理解它。
public boolean isSymmetric(TreeNode root) {
return isMirror(root, root); }
public boolean isMirror(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
return (t1.val == t2.val)
&& isMirror(t1.right, t2.left)
&& isMirror(t1.left, t2.right);}
我理解树本身就是一个递归数据结构,即由左右子树组成,遵循相同的结构,但出于某种原因,当我尝试验证此解决方案的有效性时,我尝试可视化递归调用,最终我的思绪变得纠结。 This guy已经很好地解释了调用堆栈如何在递归过程中展开,但我只是想改进我的思维过程,这样简单的" easy"递归问题,因此我发布在这里。
(FWIW,我熟悉递归/ DFS /回溯以及呼叫流程如何,但我仍然陷入困境并验证上述问题的高级递归思想)
感谢您提供帮助。
答案 0 :(得分:0)
这是可以使用光滑递归算法完成的解决方案之一。这个想法是保持两个引用最初指向根,然后将一个子树移动到左边,将另一个子树移动到相反的方向,反过来两个子节点的遍历方向然后指向对称节点子树(如果存在)
此处t1
和t2
指的是左侧和右侧子树。
if (t1 == null || t2 == null) return false;
这一步做的是检查是否存在左右子树,因为如果我们没有任何一个子树,那么它就不能对称,所以我们返回false
if (t1 == null && t2 == null) return true;
这解释了叶节点可以将null作为左右子树。所以我们回归真实;
return (t1.val == t2.val)
&& isMirror(t1.right, t2.left)
&& isMirror(t1.left, t2.right);}
可以重写为
if(t1.val != t2.val) return false;
auto left = isMirror(t1.right, t2.left)
auto right = isMirror(t1.left, t2.right);
return left && right
现在我们知道子树都是有效的(即非空),然后检查它们的值以检查它们是否相同。如果不相同,我们可以返回false,因为没有必要进一步观察。
我们可以比较的原因是因为我们知道它必须是一个完全的对称树,我们可以将左子树(t1)移动到左边和右边的子树(t2)向右移动右子树上的对称节点。
1 (t1, t2)
/ \
2 2
/ \ / \
4 5 5 4
isMirror(t1.right, t2.left)
之后
1
/ \
(t2) 2 2(t1)
/ \ / \
4 5 5 4
再次递归调用isMirror(t1.right, t2.left)
后
1
/ \
2 2
/ \ / \
(t2) 4 5 5 4(t1)
现在,这将调用它的子节点只返回true,因为它们都是null。然后检查t1
和t2
的值,并返回true
或false
。然后现在调用isMirror(t1.left, t2.right)
来到达此处。
1
/ \
2 2
/ \ / \
4 5 5 4
(t2)(t1)
现在与上面的步骤相同并展开调用堆栈。
因此,在堆栈帧处,我们left
指示t1
的左子树是否与t2
的右子树对称,而right
是否相反。< / p>
由于在递归检查孩子之前我们已经检查了t1.val
是否等于t2.val
,我们知道根是相等的,如果它的孩子是平等的,那么我们返回return left && right
t1
的子树与t2
的子树对称
如果这有点令人费解,你可以在papare上找出它并检查它可能会让事情变得更好。
希望这会有所帮助。
答案 1 :(得分:0)
如何证明上述递归版本的正确性? (一世 猜猜这可以归纳证明吗?)
是的,你可以通过归纳来证明。
有人可以勾勒出想出这样一个想法的过程 解。您是否通过实际可视化呼叫来验证解决方案 堆栈还是有一个很好的高层次思考框架来推理 这样的问题?
我用两种方式解决了问题 - 级别顺序遍历和递归。通过看到这个问题,我的第一个想法是使用级别顺序遍历。在第一次尝试时,思考次优解(带有额外空间)并不羞耻。然后我想出了递归方法。
我认为这都是关于练习的。您解决的递归问题越多,您开始在头部思考/可视化的对应递归树就越多。在开始时,我努力使用它并使用调试器的帮助来查看每次调用中函数堆栈的内容。但是调试非常耗时,而且您无法在白板编码中找到调试范围。现在在我的水平上,我可以通过在笔和模拟中模拟,通过在头部和硬脑中看到它来找出简单/中等递归问题。纸/白板。
这些东西会随着经验和更多练习而改善。某些数据结构问题的经验 - 看到并解决了大量二叉树(指针表示)/ BST问题的人比那些能够处理非常好的递归但没有解决二叉树问题的人更有可能胜过这里。得多。
希望这有帮助!