树——题解汇总

二叉搜索树:

1. 剑指 Offer 36. 二叉搜索树与双向链表 

题目描述:将二叉搜索树转换为双向循环链表
解题思路:
关键点1:二叉搜索树的中序遍历 DFS
关键点2:双向链表
关键点3:循环链表
代码:
var treeToDoublyList = function(root) {
    // 声明一个空的头结点
    var head =null;
    // 初始化一个pre指向头结点
    var pre=head;
    // 中序遍历二叉搜索树的每一个节点
    function dfs(root){
        if(root==null)return;
        // 遍历左子树
        dfs(root.left);
        // root指向的是当前节点,如果pre为空代表此时指向头结点,需要将head指向当前节点(也就是头结点)
        if(pre==null){
            head=root;
        }else{
            // 前一个节点的后继为当前节点
            pre.right=root;
            // 当前结点的前驱为前一个节点
            root.left=pre;
        }
        // 移动前一个节点到当前节点
        pre=root;
        // 遍历右子树
        dfs(root.right);
    }
    // 注意树为空的情况
    if(root==null)return null;
    dfs(root);
    // 更改头结点的指向
    head.left=pre;
    pre.right=head;
    return head;
};
代码

 2. 剑指 Offer 33. 二叉搜索树的后序遍历序列

题目描述:根据给定数组判断是否是二叉搜索树
解题思路:
关键点1:二叉搜索树的后续遍历的最后一个值是根节点 DFS
关键点2:找到比根节点小的最后一个元素,记录他的索引位置,以此为分界限,此节点向左为左子树,此节点向右(包括此节点为右子树),只有当左右子树也是二叉搜索树才是二叉搜索树
代码:
var verifyPostorder = function(postorder) {
    if(postorder.length==0)return true;
    return helper(postorder);
};
function helper(arr){
    if(arr.length<=1){
        return true
    }
    var root = arr.pop();
    var i=0;
    for(i=0;i<arr.length;i++){
        if(arr[i]>root){
            break;
        }
    }
    for(var j=i;j<arr.length;j++){
        if(arr[j]<root){return false;}
    }
    return helper(arr.slice(0,i)) && helper(arr.slice(i,arr.length));
}
二叉搜索树的后序遍历

 3. 剑指 Offer 07. 重建二叉树

题目描述:根据给定的先序遍历和中序遍历数组还原二叉树
解题思路:
关键点1:前序遍历的首个元素即为根节点 root 的值
关键点2:在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为 [ 左子树 | 根节点 | 右子树 ]
关键点3:根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为 [ 根节点 | 左子树 | 右子树 ] 
关键点4:根据子树特点,我们可以通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理
代码
var buildTree = function(preorder, inorder) {
    if(preorder.length==0 || inorder.length==0) return null;
    var root = new TreeNode(preorder[0]);
    var index=inorder.indexOf(root.val);
    var leftArr = inorder.slice(0,index);
    var preL = preorder.slice(1,index+1);
    var rightArr = inorder.slice(index+1);
    var preR = preorder.slice(index+1);
    root.left = buildTree(preL, leftArr);
    root.right = buildTree(preR, rightArr);
    return root;    
};
重建二叉树

 4.剑指 Offer 32 - I. 从上到下打印二叉树

题目描述:广度优先搜索 BFS
解题思路:
关键点1:DFS广度优先搜索
关键点2:将每一层的节点存到数组中
关键点3:依次弹出每一个节点,将其左右子节点存入数组,将当前节点的值存到结果数组中
代码
var levelOrder = function(root) {
    if(root==null){
        return [];
    }
    var curr=[root];
    var result=[];
    while(curr.length){
        var m=curr.shift();
        result.push(m.val)
        if(m.left){
            curr.push(m.left);
        }
        if(m.right){
            curr.push(m.right);
        }
    }
    return result;
};
BFS

 5.剑指 Offer 32 - III. 从上到下打印二叉树 III

题目描述:广度优先搜索 BFS
解题思路:
关键点1:BFS广度优先搜索
关键点2:将每一层的节点存到curr数组中
关键点3:遍历当前层节点数组,将当前节点的值存到currValue数组中,将当前节点的左右子节点存到nextNode节点数组中
关键点4:设置标志位,奇数层尾部插入数组,偶数层头部插入数组
代码
var levelOrder = function(root) {
    if(root==null) return [];
    var curr=[root];
    // console.log(curr)
    var flag=1;
    var tmpValue=[];
    var nextNode =[];
    var result=[];
    while(curr.length){
        tmpValue=[];
        nextNode=[];
        for(var i=0;i<curr.length;i++){
            if(flag==1){
                tmpValue.push(curr[i].val);
            }else{
                tmpValue.unshift(curr[i].val);
            }
            if(curr[i].left){
                nextNode.push(curr[i].left);
            }
            if(curr[i].right){
                nextNode.push(curr[i].right);
            }
        }
        flag *=-1;
        result.push(tmpValue);
        if(nextNode){
            curr=nextNode;
        } 
    }
    return result;
};
BFS

 6.剑指 Offer 34. 二叉树中和为某一值的路径

题目描述:返回二叉树中和为某一值的所有路径的数组
解题思路:
关键点1:深度优先搜索、先序遍历
关键点2:当路径和为target并且到达叶子节点时存入结果数组,注意存入数组的浅拷贝,因为路径数组path会一直在发生变化
关键点3:注意回溯
代码
var pathSum = function(root, sum) {
    var res =[];
    // 存放每条路径的数组
    var path=[];
    if(!root)return [];
    function helper(root,target){
        if(root==null)return;
        path.push(root.val);
        target -= root.val;
        // 对于每一个节点都要判断,如果target减为0并且为叶子节点(左右子树均为空)的时候满足要求,就存入结果数组中
        if(target==0 && root.left==null && root.right==null){
            // 这里存放的是path的拷贝,否则path会随着元素的变化而变化
            res.push([...path]);
        }
        helper(root.left,target);
        helper(root.right,target);
        // 注意这里一定要弹出当前元素,这是回溯算法关键的一点
        path.pop();
    }
    helper(root,sum);
    return res;
};
};
二叉树中和为某一值的路径

 7.98. 验证二叉搜索树

题目描述:返回二叉树中和为某一值的所有路径的数组
解题思路:
关键点1:中序遍历
关键点2:如果是二叉搜索树,判断中序遍历数组是否为升序即可
关键点3:LeetCode中不要定义全局变量
代码
var isValidBST = function(root) {
    var res=[];
    function inOrder(root){
        if(root==null) return [];
        if(root.left){
            inOrder(root.left);
        }
        res.push(root.val);
        if(root.right){
            inOrder(root.right);
        }
    }
    inOrder(root);
    for (let i = 0; i < res.length - 1; i++) {
        if (res[i + 1] <= res[i]) return false;
    }
    return true;
};
方法一
// 方法二 时间上更高效的方法,空间上不需要开辟新数组
// 也是中序遍历的方法
// 但是注意在LeetCode上提交的时候不要把 pre变量定义为全局变量
var isValidBST = function(root){
    var pre=-Infinity;
    function helper(root){
        if(root==null)return true;
        if(!helper(root.left)){
            return false;
        }
        if(root.val<=pre){
            return false;
        }
        pre=root.val;
        if(!helper(root.right)){
            return false;
        }
        return true;
    }
    return helper(root);
}
改进的方法二

 8.96. 不同的二叉搜索树

题目描述:给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种 (理解不够深刻)
解题思路:
关键点1:动态规划、递归
关键点2:我们可以遍历每个数字 ii,将该数字作为树根,将 1 \cdots (i-1)1⋯(i−1) 序列作为左子树,将 (i+1) \cdots n(i+1)⋯n 序列作为右子树。
     接着我们可以按照同样的方式递归构建左子树和右子树
代码
var numTrees = function(n) {
    // 开辟一个数组,用0初始化
    var dp= new Array(n+1).fill(0);
    // 初始化
    dp[0]=1;
    dp[1]=1;
    // 分别以每个元素i为根节点值
    for(var i=2;i<=n;i++){
        for(var j=1;j<=i;j++){
            dp[i] += dp[j-1]*dp[(i-j)];
        }
    }
    return dp[n];
};
View Code

 9.1382. 将二叉搜索树变平衡

题目描述:如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的
解题思路:
关键点1:二叉树的重构、中序遍历
关键点2:中序遍历有序数组,然后奖盖有序数组重构为二叉平衡树
关键点3:重构的时候为保证左右子树高度差小于等于1,取数组中间值作为根节点,然后递归求得左右子树
代码
var balanceBST = function(root) {
    var res = [];
    function dfs(root){
        if(root==null) return null;
        dfs(root.left);
        res.push(root.val);
        dfs(root.right);
    }
    dfs(root);
    function reBuild(arr){
        if(arr.length==0){
            return null;
        }
        if(arr.length==1){
            return new TreeNode(arr[0]);
        }
        var mid=Math.floor(arr.length/2);
        var newRoot = new TreeNode(arr[mid]);
        newRoot.left = reBuild(arr.slice(0,mid));
        newRoot.right = reBuild(arr.slice(mid+1));
        return newRoot;
    }
    return reBuild(res);
};
View Code

 10.701. 二叉搜索树中的插入操作

题目描述:二叉搜索树的巨大优势就是:在平均情况下,能够在 O(logN) 的时间内完成搜索和插入元素
解题思路:
关键点1:若 val > node.val,插入到右子树。
关键点2:若 val < node.val,插入到左子树。
代码
// 递归
var insertIntoBST = function(root, val) {
    if(root==null) return new TreeNode(val);
    if(root.val < val){
        root.right = insertIntoBST(root.right,val);
    }else{
        root.left = insertIntoBST(root.left,val);
    }
    return root;
};
// 迭代
var insertIntoBST = function(root, val) {
    if(root==null) return new TreeNode(val);
    var parent = root,p=root;
    while(p!=null){
        parent = p;
        p=p.val<val ? p.right:p.left;
    }
    if(parent.val<val){
        parent.right = new TreeNode(val);
    }else{
        parent.left = new TreeNode(val);
    }
    return root;
};
二叉搜索树插入节点

 

 11.450. 删除二叉搜索树中的节点

题目描述:二叉搜索树的巨大优势就是:在平均情况下,能够在 O(logN) 的时间内完成搜索和插入元素
解题思路:分为三种情况:
1.如果要删除的节点在左子树,递归左子树
2.如果要删除的节点在右子树,递归右子树
3.要删除的节点为根节点:再分情况判断左右子树是否为空,当都不为空的时候,找到右子树上的最小节点,将根节点的值替换为这个最小值,然后删除这个最小节点
var deleteNode = function(root, key) {
    if(root==null){
        return root;
    }
    // 要删除的节点在右子树
    if(root.val<key){
        root.right = deleteNode(root.right,key);
    }else if(root.val>key){
        // 要删除的节点在左子树
        root.left = deleteNode(root.left,key);
    }else{
        // 要删除的节点为根节点
        // 如果左右子树均为空
        if(root.left==null && root.right==null){
            root = null;
        }
        // 如果右子树为空
        else if(root.right==null && root.left!=null){
            root = root.left;
        } 
        // 如果左子树为空
        else if(root.left==null && root.right!=null){
            root = root.right;
        }else {
            // 如果左右子树均不为空
            var tmp = root.right;
            // 找到右子树上最小的数
            while(tmp.left!==null){
                tmp = tmp.left;
            }
            // 将根节点的值替换为这个最小的值
            root.val=tmp.val;
            // 删除有字数上这个最小的值
            root.right = deleteNode(root.right, root.val);
        }
        
    }
    return root;
};
二叉搜索树删除节点

 

 

posted @ 2020-09-07 10:43  ccv2  阅读(141)  评论(0编辑  收藏  举报