2-树-恢复二叉搜索树

这是树的第二篇算法,力扣链接。

给你二叉搜索树的根节点 root ,该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下,恢复这棵树 

示例 1:

2-树-恢复二叉搜索树_第1张图片

输入:root = [1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3 不能是 1 的左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。

示例 2:

2-树-恢复二叉搜索树_第2张图片

输入:root = [3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。

上次算法提到了中序遍历:

中序遍历 (Inorder Traversal)

在中序遍历中,我们按照以下顺序遍历树中的节点:

  1. 遍历左子树
  2. 访问根节点
  3. 遍历右子树

对于二叉搜索树(BST),中序遍历会按照升序访问所有节点,因为二叉搜索树的特点是左子节点的值小于根节点的值,根节点的值小于右子节点的值。

中序遍历会将树一维化并且是有序的:

假设有一棵二叉树如下:

    A
   / \
  B   C
 / \   \
D   E   F

对这棵树进行不同的遍历会得到以下结果:

  • 中序遍历D, B, E, A, C, F。首先遍历左子树(D, B, E),然后是根节点(A),最后是右子树(C, F)。 

因此这里的第一个解法是将树一维化,然后找到非有序的节点,交换两个节点位置即可。

func recoverTree(root *TreeNode) {
	nodeList := make([]*TreeNode, 0)
	stack := make([]*TreeNode, 0)
	cur := root
	for len(stack) > 0 || cur != nil {
		for cur != nil {
			stack = append(stack, cur)
			cur = cur.Left
		}
		cur = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		nodeList = append(nodeList, cur)
		cur = cur.Right
	}
	index1, index2 := -1, -1
	for i := 0; i < len(nodeList)-1; i++ {
		if nodeList[i+1].Val < nodeList[i].Val {
			index2 = i + 1
			if index1 == -1 {
				index1 = i
			} else {
				break
			}
		}
	}
	nodeList[index1].Val, nodeList[index2].Val = nodeList[index2].Val, nodeList[index1].Val
}

这道题的进阶方法是不用额外的空间复杂度。

先不讲这道题怎么做,先试一下不用额外空间复杂度的中序遍历。这个做法叫做Morris 中序遍历,其核心思路是利用叶子节点右侧的空指针指向中序遍历的后继节点,从而避免了栈的使用。

操作思路是首先检查当前节点的左子节点。如果左子节点不存在,我们输出当前节点并将其右子节点作为下一个当前节点。如果左子节点存在,我们找到当前节点在其左子树上的前驱节点,这是其左子树中最右侧的节点。如果前驱节点的右子节点为空,我们将其设置为当前节点,并将当前节点更新为其左子节点。如果前驱节点的右子节点是当前节点,我们将其重新设置为空,输出当前节点,并将当前节点更新为其右子节点。

这种方式的遍历确保了每个节点都被访问两次,但只有在第二次访问时才会输出。在整个过程中,我们没有使用栈或递归,从而实现了常数空间复杂度。

这个做法很神奇,不求一次性就能记住,多少留个印皙那个每次都可以回看:

func morrisTraversal(root *TreeNode) {
	nodeList := make([]*TreeNode, 0)
	cur := root
	for cur != nil {
		if cur.Left == nil {
			nodeList = append(nodeList, cur)
			cur = cur.Right
		} else {
			pre := cur.Left
			for pre.Right != nil && pre.Right != cur {
				pre = pre.Right
			}
			if pre.Right == nil {
				pre.Right = cur
				cur = cur.Left
			} else {
				pre.Right = nil
				nodeList = append(nodeList, cur)
				cur = cur.Right
			}
		}
	}
}

至于这道题怎么利用morris解,最简单的当然还是用数组找索引,但是为了不用额外空间,我们可以考虑原地更改,不过需要记录索引。

func recoverTree(root *TreeNode) {
	var first, second, prev *TreeNode
	cur := root
	for cur != nil {
		if cur.Left == nil {
			if prev != nil && cur.Val < prev.Val {
				if first == nil {
					first = prev
				}
				second = cur
			}
			prev = cur
			cur = cur.Right
		} else {
			pre := cur.Left
			for pre.Right != nil && pre.Right != cur {
				pre = pre.Right
			}
			if pre.Right == nil {
				pre.Right = cur
				cur = cur.Left
			} else {
				pre.Right = nil
				if prev != nil && cur.Val < prev.Val {
					if first == nil {
						first = prev
					}
					second = cur
				}
				prev = cur
				cur = cur.Right
			}
		}
	}
	first.Val, second.Val = second.Val, first.Val
}

你可能感兴趣的:(白话算法,数据结构,算法)