【二叉搜索树与双向链表】【2种解法(一次递归 或 递归+遍历)】

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解法 1: 一次递归(推荐)

二叉搜索树的性质是:左节点 < 当前节点 < 右节点。转换后的双向链表是有序的,这里采用中序递归遍历保证有序性。

设计的递归函数返回的是:已转换好的双向链表的尾结点,也就是当前节点的 left 指针应该指向的地方。递归函数的实现思路:

  • 检查 left 是否为空,不为空,那么递归调用(传入左子树)
  • 将 left 指针指向已转换好的双向链表的尾结点,并将尾节点的 right 指向当前节点
  • 更新双向链表尾节点(变为当前节点),检查 right 是否为空,不为空,递归调用(传入右子树)
  • 返回转换后的双向链表尾节点

代码实现如下。逻辑在__Convert()函数中,Convert()函数的目的是因为牛客网 oc 条件需要返回转换后链表的首节点。

// ac地址:https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5
// 原文地址:https://xxoo521.com/2020-02-06-btree-link/

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
/**
 * @param {TreeNode} pRootOfTree
 * @return {TreeNode}
 */
function Convert(pRootOfTree) {
    if (!pRootOfTree) {
        return null;
    }
    __Convert(pRootOfTree, null);
    let node = pRootOfTree;
    while (node.left) {
        node = node.left;
    }
    return node;
}

/**
 * @param {TreeNode} pRootOfTree
 * @param {TreeNode} lastNodeInList
 * @return {TreeNode}
 */
function __Convert(pRootOfTree, lastNodeInList = null) {
    if (!pRootOfTree) {
        return null;
    }
    // step1:左子树
    if (pRootOfTree.left) {
        lastNodeInList = __Convert(pRootOfTree.left, lastNodeInList);
    }
    // step2:当前节点
    pRootOfTree.left = lastNodeInList;
    if (lastNodeInList) {
        lastNodeInList.right = pRootOfTree;
    }
    // step3:右子树
    lastNodeInList = pRootOfTree;
    if (pRootOfTree.right) {
        lastNodeInList = __Convert(pRootOfTree.right, lastNodeInList);
    }

    return lastNodeInList;
}

整个过程的要递归遍历一遍二叉树,时间复杂度是 O(N),空间复杂度是 O(N)。

解法 2: 递归+数组

解法 2 的实现非常简单。流程如下:

  • 中序遍历一遍二叉搜索树,将节点保存在一个数组中。
  • 遍历数组,更改每个节点的 left 和 right
  • 返回数组第一个元素
// ac地址:https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5
// 原文地址:https://xxoo521.com/2020-02-06-btree-link/

/**
 * @param {TreeNode} pRootOfTree
 * @return {TreeNode}
 */
function Convert(pRootOfTree) {
    if (!pRootOfTree) {
        return null;
    }

    const nodes = [];
    midTravel(pRootOfTree, nodes);
    const num = nodes.length;
    for (let i = 0; i < num; ++i) {
        nodes[i].right = nodes[i + 1] || null;
        nodes[i].left = nodes[i - 1] || null;
    }
    return nodes[0];
}
/**
 * @param {TreeNode} node
 * @param {TreeNode[]} nodes
 */
function midTravel(node, nodes) {
    node.left && midTravel(node.left, nodes);
    nodes.push(node);
    node.right && midTravel(node.right, nodes);
}

时间复杂度是 O(N),空间复杂度是 O(N)。相较于解法 1,多开辟了 O(N)的数组空间。但是整体思路更容易想到和实现。

博客地址:剑指 Offer 题解+代码

收藏 Github :https://github.com/dongyuanxin/blog