LeetCode|二叉树|JavaScript解法(更新中...)

二叉树(JS)

1.翻转二叉树(leetCode226)

var invertTree = function(root) {
    if(root === null) {return null}
    invertTree(root.left)
    invertTree(root.right)
    let temp = root.left
    root.left = root.right
    root.right = temp
    return root
};

2.二叉树展开为链表(leetCode114)

var flatten = function(root) {
    if(root === null) {return }
    flatten(root.left)
    flatten(root.right)
    let left = root.left
    let right = root.right

    root.left = null
    root.right = left

    let p = root
    while(p.right != null) {
        p = p.right
    }
    p.right = right

    return root
};

3.填充每个节点的下一个右侧节点指针(leetCode116)

var connect = function(root) {
    if(root === null) {return null}
    
    const helper = (node1, node2) => {
        if(node1 === null || node2 === null) {return }
        node1.next = node2
        helper(node1.left, node1.right)
        helper(node2.left, node2.right)
        helper(node1.right, node2.left)
    }

    helper(root.left, root.right)
    return root
};

4.最大二叉树(leetCode654)

var constructMaximumBinaryTree = function(nums) {

  const helper = (arr, start, end) => {
      if(start > end) {return null}
      let index = -1
      let maxVal = -1
      for(let i = start; i <= end; i++) {
          const n = arr[i]
          if(n > maxVal) {
              index = i
              maxVal = n
          }
      }
      let root = new TreeNode(maxVal)
      root.left = helper(arr, start, index-1)
      root.right = helper(arr, index+1, end)
      return root
  }

  let root = helper(nums, 0, nums.length-1)
  return root
};

5.从前序与中序遍历构造二叉树(leetCode105)

var buildTree = function(preorder, inorder) {
    const build = (preorder, preStart, preEnd, inorder, inStart, inEnd) => {
        if(preStart > preEnd) {return null}

        let rootVal = preorder[preStart]
        let index = inorder.indexOf(rootVal)
        let root = new TreeNode(rootVal)
        let leftsize = index - inStart

        root.left = build(preorder, preStart+1, preStart+leftsize, inorder, inStart, index-1)
        root.right = build(preorder, preStart+leftsize+1, preEnd, inorder, index+1, inEnd)
        return root
    }

    return build(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1)
};

6.从中序与后序遍历构造二叉树(leetCode106)

var buildTree = function(inorder, postorder) {
    const build = (inorder, inStart, inEnd, postorder, postStart, postEnd) => {
        if(postEnd < postStart) {return null}

        const rootVal = postorder[postEnd]
        const index = inorder.indexOf(rootVal)
        const leftsize = index - inStart

        const root = new ListNode(rootVal)
        root.left = build(inorder, inStart, index-1, postorder, postStart, postStart+leftsize-1)
        root.right = build(inorder, index+1, inEnd, postorder, postStart+leftsize, postEnd-1)
        return root
    }

    return build(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1)
};

7.寻找重复的子树(leetCode652)

var findDuplicateSubtrees = function(root) {
    let memo = new Map()
    let res = []

    const  helper = (root) => {
        if(root === null) {
            return '#'
        }

        const left = helper(root.left)
        const right = helper(root.right)
        const subtree = left + ',' + right + ',' + root.val

      
        if(memo.has(subtree)) {
            memo.set(subtree, memo.get(subtree) + 1)
        }else {
            memo.set(subtree, 1)
        }

        if(memo.get(subtree) === 2) {
            res.push(root)
        }

        return subtree
    }

    helper(root)
    return res
};

二叉搜索树

8.BST第k小的元素(leetCode230)

var kthSmallest = function(root, k) {
    let res = []
    const traverse = (root) => {
        if(!root) {return}
        traverse(root.left)
        res.push(root.val)
        traverse(root.right)
    }
    traverse(root)
    return res[k-1]
};

9.二叉搜索数转化累加树(leetCode538)

var convertBST = function(root) {
    let sum = 0
    const traverse = (root) => {
        if(!root) {return }
        traverse(root.right)
        
        sum += root.val
        root.val = sum
             
        traverse(root.left)
    }

    traverse(root)
    return root
};

10.BST转累加树(leetCode1038)

var bstToGst = function(root) {
    let sum = 0
    const traverse = (root) => {
        if(!root) {return}
        traverse(root.right)
        sum += root.val
        root.val = sum
        traverse(root.left)
    }
    traverse(root)
    return root
};

11.删除二叉搜索树中的节点(leetCode450)

var deleteNode = function(root, key) {
    if(!root) {return null}
	//定义找到BST右子树最小节点的方法
    const getMin = (node) => {
        while(node.left != null) {
            node = node.left
        }
        return node 
    }
	
	//搜索BST
    if(key < root.val) {
        root.left = deleteNode(root.left, key)
    }else if(key > root.val) {
        root.right = deleteNode(root.right, key)
    }else {
    	//分析前两种情况
        if(root.left === null) {
            return root.right
        }else if(root.right === null) {
            return root.left
        }else {
        	//处理复杂的第三种情况
            let minNode = getMin(root.right)
            root.right = deleteNode(root.right, minNode.val)
            minNode.left = root.left
            minNode.right = root.right
            root = minNode
        }
    }
    return root
};

12.二叉搜索树中的插入操作(leetCode701)

var insertIntoBST = function(root, val) {
    if(root === null) return new TreeNode(val)

    if(root.val > val) {
        root.left = insertIntoBST(root.left, val)
    }else if(root.val < val) {
        root.right = insertIntoBST(root.right, val)
    }
    return root
};

13.二叉搜索树中的搜索(leetCode700)

var searchBST = function(root, val) {
    if(!root) {return null}

    if(root.val > val) {
        return searchBST(root.left, val)
    }

    if(root.val < val) {
        return searchBST(root.right, val)
    }

    return root
};

​ 如果在一颗普通的二叉树中寻找,可以这样写代码:

var searchBST = function(root, val) {
	if(!root) {return null}
	
	if(root.val === val) {
		return root
	}
	
	let left = searchBST(root.left, val)
	let right = searchBST(root.right, val)
	return left != null ? left : right
}

14.验证二叉搜索树(leetCode98)

我们通过使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,这也是二叉树算法的一个小技巧吧。

var isValidBST = function(root) {

    const helper = (root, min, max) => {
        if(!root) return true

        if(min != null && root.val <= min.val) {
            return false
        }
        if(max != null && root.val >= max.val) {
            return false
        }

        return helper(root.left, min, root) && helper(root.right, root, max)
    }

    return helper(root, null, null)
};

15.不同的二叉搜索树(leetCode96)

步骤:

  1. 定义备忘录,消除重叠子问题。为什么会出现重叠子问题呢?

    例如,i=1时。left=count(1,0)=1 right=count(2,5)
         i=2时,left=count(1,1)  right=count(3,5)
         但是count(1,1)的left=count(1,0),right=(2,1)
         所以出现了两次count(1,0)。这样的重叠子问题还有很多。为了能够提高效率,我们要设置备忘录。
    
  2. 设置base case,当lo>high时意味者两种情况。lo为根节点,左子树为null;第二种情况是hi为根节点,右子树为null。这两种情况count均返回1。

  3. 其他情况,count返回的结果为左子树数量乘以右子树数量

    var numTrees = function(n) {
        let memo = new Array(n+1).fill(0).map(() => new Array(n+1).fill(0))
        
        const count = (lo, hi) => {
            if(lo > hi) return 1
            if(memo[lo][hi] != 0) {
                return memo[lo][hi]
            }
            let res = 0
            for(let i = lo; i <= hi; i++) {
                const left = count(lo, i-1)
                const right = count(i+1, hi)
                res += left * right
            }
            memo[lo][hi] = res
            return res
        }
        return count(1, n)
    };
    

16.不同的二叉搜索树2(leetCode95)

明白了上道题构造合法 BST 的方法,这道题的思路也是一样的

1、穷举root节点的所有可能。

2、递归构造出左右子树的所有合法 BST。

3、给root节点穷举所有左右子树的组合。

var generateTrees = function(n) {
    if(n === 0) {return []}

    const build = (lo, hi) => {
        let res = []
        if(lo > hi) {
            res.push(null)
            return res
        }

        for(let i = lo; i <= hi; i++) {
            let leftTree = build(lo, i-1)
            let rightTree = build(i+1, hi)
            for(let left of leftTree ) {
                for(let right of rightTree) {
                    root = new TreeNode(i)
                    root.left = left
                    root.right = right
                    res.push(root)
                }
            }
        }
        return res
    }

    return build(1,n)
};

17.二叉搜索子树的最大键值和(leetCode1373)

18.二叉树的序列化和反序列化(leetCode297)

19.扁平化嵌套列表迭代器(leetCode341)

题目要求是:

NestedIterator(List nestedList) 用嵌套列表 nestedList 初始化迭代器。
int next() 返回嵌套列表的下一个整数。
boolean hasNext() 如果仍然存在待迭代的整数,返回 true ;否则,返回 false 。

思路:

  1. 定义一个dealData函数将多维数组转化成一维数组,调用该函数。设置arr和index属性
  2. 在原型上设置hasNext函数,若index小于length则为true
  3. 在原型上设置next函数,返回this.arr[this.index++]
/**
 * @constructor
 * @param {NestedInteger[]} nestedList 
 */
var NestedIterator = function (nestedList) {
    // 通过参数说明我们可以知道nestedList的类型是NestedInteger的数组Ï
    const arr = [];
    // 多维数组递归转一维数组
    function dealData(nestedList) {
        for (let i = 0; i < nestedList.length; i++) {
            if (nestedList[i].isInteger()) {
                arr.push(nestedList[i].getInteger())
            } else {
                dealData(nestedList[i].getList());
            }
        }
    }
    // 调用我们写的递归函数
    dealData(nestedList);
    this.arr = arr;
    this.index = 0;
};

NestedIterator.prototype.hasNext = function () {
    return this.index < this.arr.length;
};

NestedIterator.prototype.next = function () {
    return this.arr[this.index++];
};

20.二叉树的最近公共祖先(leetCode236)

到任何递归型的问题,无非就是灵魂三问

1、这个函数是干嘛的

首先看第一个问题,这个函数是干嘛的?或者说,你给我描述一下lowestCommonAncestor这个函数的「定义」吧。

描述:给该函数输入三个参数root,p,q,它会返回一个节点。

情况 1,如果p和q都在以root为根的树中,函数返回的即使p和q的最近公共祖先节点。

情况 2,那如果p和q都不在以root为根的树中怎么办呢?函数理所当然地返回null呗。

情况 3,那如果p和q只有一个存在于root为根的树中呢?函数就会返回那个节点。

题目说了输入的p和q一定存在于以root为根的树中,但是递归过程中,以上三种情况都有可能发生,所以说这里要定义清楚,后续这些定义都会在代码中体现。

2、这个函数参数中的变量是什么

函数参数中的变量是root,因为根据框架,lowestCommonAncestor(root)会递归调用root.left和root.right;至于p和q,我们要求它俩的公共祖先,它俩肯定不会变化的。
第二个问题也解决了,你也可以理解这是「状态转移」,每次递归在做什么?不就是在把「以root为根」转移成「以root的子节点为根」,不断缩小问题规模嘛?

3、得到函数的递归结果,你应该干什么

先想 base case,如果root为空,肯定得返回null。如果root本身就是p或者q,比如说root就是p节点吧,如果q存在于以root为根的树中,显然root就是最近公共祖先;即使q不存在于以root为根的树中,按照情况 3 的定义,也应该返回root节点。

最后,是使用递归调用的left和right搞事,分情况讨论

情况 1,如果p和q都在以root为根的树中,那么left和right一定分别是p和q(从 base case 看出来的)。

情况 2,如果p和q都不在以root为根的树中,直接返回null。

情况 3,如果p和q只有一个存在于root为根的树中,函数返回该节点。

代码流程:

  1. base case分析,root === null的情况以及root === p || root === q的情况
  2. 选用后序遍历,后序遍历可理解为从下往上走,先遍历后再进行其他逻辑判断。
  3. 遍历完成后,最后开始其他逻辑判断
var lowestCommonAncestor = function(root, p, q) {
    if(!root) {return null}
    if(root === p || root === q)  return root

    const left = lowestCommonAncestor(root.left, p, q)
    const right = lowestCommonAncestor(root.right, p, q)

    if(left != null && right != null) {
        return root
    }
 
    if(left === null && right === null) {
        return null
    }

    return left === null ? right : left

};

21.完全二叉树的节点个数(leetCode222)

**完全二叉树:**每一层都是紧凑靠左排列的。

**满二叉树:**是一种特殊的完全二叉树,每层都是是满的,像一个稳定的三角形。

1.求普通二叉树的节点个数:

function countNodes(root) {
  if(root === null) return 0
  return 1 + countNodes(root.left) + countNodes(root.right)
}

2.求满二叉树的节点个数:满二叉树的节点个数和树的高度呈指数关系

function countNodes(root) {
  let h = 0
  while(root != null) {
    root = root.left
    h++
  }
  return Math.pow(2, h) - 1
}

3.完全二叉树的节点个数则是将两者结合起来:

var countNodes = function(root) {
    let l = root, r = root
    let hl = 0, hr =0
    while(l != null) {
        l = l.left
        hl++
    }
     while(r != null) {
        r = r.right
        hr++
    }
    if(hl === hr) {
        return Math.pow(2, hl) - 1
    }else {
        return 1 + countNodes(root.left) + countNodes(root.right)
    }
};

22.面试提问:如何将递归改成迭代算法

如下是迭代遍历二叉树的完整代码框架:

  • 代码中最有技巧性的是这个 visited 指针,它记录最近一次遍历完的子树根节点(最近一次 pop 出栈的节点),我们可以根据对比 p 的左右指针和 visited 是否相同来判断节点 p 的左右子树是否被遍历过,进而分离出前中后序的代码位置。
  • visited 指针初始化指向一个新 new 出来的二叉树节点,相当于一个特殊值,目的是避免和输入二叉树中的节点重复。
var traverse = function(root) {
 
  //左侧树枝一撸到底
  const pushLeftBranch = (p) => {
    while(p != null) {
      /*******************/
      /** 前序遍历代码位置 **/
      /*******************/
      stack.push(p)
      p = p.left
    }
  }

  // 模拟函数调用栈
  let stack = []
  // 指向上一次遍历完的子树根节点
  let visited = new TreeNode(-1)
  // 开始遍历整棵树
  pushLeftBranch(root)

  // 判断stack是否还有元素
  while(!(stack.length === 0)) {
    // 得到栈顶元素
    let p = stack[stack.length - 1]

    // p的左子树被遍历完了,且右子树没有被遍历过
    if((p.left === null || p.left === visited)
    && p.right != visited) {
      /*******************/
      /** 中序遍历代码位置 **/
      /*******************/
      // 去遍历 p 的右子树
      pushLeftBranch(p.right)
    }
    // p 的右子树被遍历完了
    if(p.right === null || p.right === visited) {
      /*******************/
      /** 后序遍历代码位置 **/
      /*******************/
      // 以 p 为根的子树被遍历完了,出栈
      // visited 指针指向 p
      postorder.push(p.val)
      visited = stack.pop()
    }
  }

  return postorder

};

如下是后序遍历的迭代算法示例:

var postorderTraversal = function(root) {

  let stack = []
  let postorder = []
  let visited = new TreeNode(-1)

  const pushLeftBranch = (p) => {
    while(p != null) {
      stack.push(p)
      p = p.left
    }
  }

  pushLeftBranch(root)
  while(!(stack.length === 0)) {
    let p = stack[stack.length - 1]
    console.log(p)
    if((p.left === null || p.left === visited)
    && p.right != visited) {
      pushLeftBranch(p.right)
    }

    if(p.right === null || p.right === visited) {
      postorder.push(p.val)
      visited = stack.pop()
    }
  }

  return postorder

};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值