题目
题目来源:力扣(LeetCode)
给你一棵二叉树的根节点 root ,树中有 n 个节点,每个节点都有一个不同于其他节点且处于 1 到 n 之间的值。
另给你一个由 n 个值组成的行程序列 voyage ,表示 预期 的二叉树 先序遍历 结果。
通过交换节点的左右子树,可以 翻转 该二叉树中的任意节点。例,翻转节点 1 的效果如下:
请翻转 最少 的树中节点,使二叉树的 先序遍历 与预期的遍历行程 voyage 相匹配 。
如果可以,则返回 翻转的 所有节点的值的列表。你可以按任何顺序返回答案。如果不能,则返回列表 [-1]。
示例 1:
输入:root = [1,2], voyage = [2,1]
输出:[-1]
解释:翻转节点无法令先序遍历匹配预期行程。
示例 2:
输入:root = [1,2,3], voyage = [1,3,2]
输出:[1]
解释:交换节点 2 和 3 来翻转节点 1 ,先序遍历可以匹配预期行程。
示例 3:
输入:root = [1,2,3], voyage = [1,2,3]
输出:[]
解释:先序遍历已经匹配预期行程,所以不需要翻转节点。
思路分析
题目给出了一个待操作的树 A 的根节点 root, 和树 V 先序遍历的结果数组,然后求的是 A 能否翻转最少的 n 个节点,使得 A 和 V 一致。
我们遍历树V先序遍历的结果数组voyage (其实相当于是前序遍历树V),然后用 V 的值去匹配 A, 看看能否进行树的匹配
对于一次遍历,我们首先判断根节点的值是一致,才会进入这一次的遍历中,然后主要是看左右树,对于树 V 来说,用 pos 不断按照先序遍历给出值,而对于 V 来说,你可以用左树匹配,如果匹配成功,则啥事没有,大家是一样的;而如果左树不匹配,用右树先行,然后再走左树,这种情况就需要翻转一下当前的节点,保证树 A 要在前序遍历的情况和 V 匹配;
如果在匹配过程中 A 的左右树都没有匹配成功,则会提前走出 A 的遍历,这个时候 pos 就没有迭代完,也就是树A 的节点数量少于树V 的节点数量,这个时候就是异常,返回 [-1]
/*** Definition for a binary tree node.* function TreeNode(val, left, right) {* this.val = (val===undefined ? 0 : val)* this.left = (left===undefined ? null : left)* this.right = (right===undefined ? null : right)* }*//*** @param {TreeNode} root* @param {number[]} voyage* @return {number[]}*/var flipMatchVoyage = function (root, voyage) {// 用来在进入 dfs 前对根节点的判断// 如果根节点的值不一致,则直接返回-1,不再进入dfsif (root.val !== voyage[0]) return [-1]const ret = []let pos = 0 // 这是用来获取 voyage 值,也是遍历树 V 的,如果没有走完,证明无法匹配 rootconst dfs = root => {// 每一次的遍历 pos 都要跟随着pos++// 对于每一个节点,都是按照先序遍历的写法if (root.left && root.left.val === voyage[pos]) {// 如果在这节点左树适配,那就继续走,因为这是先序遍历dfs(root.left)}if (root.right && root.right.val === voyage[pos]) {dfs(root.right)// 右树完成之后,需要看看现在 pos 所在的值是否可以匹配左树,即是否先走右树再走左树,成立即当前的 root 节点就是需要进行翻转的节点if (root.left && root.left.val === voyage[pos]) {ret.push(root.val)dfs(root.left)}}}dfs(root)// 代码走到此处,代表树A 已经遍历完了// 此时 pos 的值代表的就是树A的节点数量,树A 的节点数量少于 树 V 的节点数量,说明不匹配if (pos < voyage.length) {return [-1]}return ret};
