1. 程式人生 > 其它 >力扣關於樹的題目(三)(一般二叉樹+套路總結)

力扣關於樹的題目(三)(一般二叉樹+套路總結)

二叉樹的屬性問題

1. 101. 對稱二叉樹

檢查自己,左子樹,右子樹

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    const check = (left,right) => {
      if (left === null && right === null) {
          return true;
      }
      if (left === null || right === null) {
          return false;
      }

      let leftData = check(left.left,right.right);
      let rightData = check(right.left,left.right);

      return leftData && rightData && (left.val === right.val);
    }
    return check(root.left,root.right)
};
2. 104. 二叉樹的最大深度

套路,左子樹的深度和右子樹的深度,最大的+1

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    let deep = 0;
    if (!root) {
        return deep;
    }
    const findDeep = (root) => {
      if (!root) {
          return 0;
      }
      let left = findDeep(root.left);
      let right = findDeep(root.right);
      
      return Math.max(left,right)+1;
    }
    return findDeep(root);
};
3. 111. 二叉樹的最小深度

跟上一題一樣,套路,左右兩邊小的+1

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    const findMin = (root) => {
        if(!root){
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        let min = Infinity;
        if(root.left){
            min = Math.min(min,findMin(root.left))
        }
        if(root.right){
            min = Math.min(min,findMin(root.right))
        }
    
        return min + 1;
    }
    return findMin(root);
};
4. 222. 完全二叉樹的節點個數

套路,左邊+右邊+1

/**
 * @param {TreeNode} root
 * @return {number}
 */
var countNodes = function(root) {
    const count = (root) => {
      if (!root) {
          return 0;
      }
      let leftData = count(root.left);
      let rightData = count(root.right);
      return leftData + rightData + 1;
    }
    
    return count(root);
};
5. 110. 平衡二叉樹

套路,向左邊要資訊,右邊要資訊,再判斷返回

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
    function returnData(is, height) {
        this.isBan = is;
        this.height = height;
    }
    const check = (root) => {
      if (!root){
          return new returnData(true,0);
      }
      let leftData = check(root.left);
      let rightData = check(root.right);

      let is = true;
      if (!leftData.isBan || !rightData.isBan || Math.abs(leftData.height-rightData.height) > 1) {
          is = false;
      }

      return new returnData(is,Math.max(leftData.height,rightData.height)+1);
    }

    return check(root).isBan;
};
6. 257. 二叉樹的所有路徑

這題有點不一樣,像全排列,回溯

/**
 * @param {TreeNode} root
 * @return {string[]}
 */
var binaryTreePaths = function(root) {
    let res = [];
    let path = [];

    function changeToStr() {
        let str = "";
        for (let i = 0; i < path.length; i++) {
            if (i === path.length-1) {
                 str = str.concat(path[i]);
            } else {
                str = str.concat(path[i] + "->");
            }
        }
        return str;
    }

    const backtrace = (root) => {
      if (root) {
          path.push(root.val);
      }

      if (!root.left && !root.right){
          res.push(changeToStr());
          return;
      }

      if (root.left){
          backtrace(root.left)
          path.pop();
      }

      if (root.right) {
          backtrace(root.right);
          path.pop();
      }
    }

    backtrace(root);
    return res;
};
7. 404. 左葉子之和

這裡實際上還是遍歷,遍歷到所有的節點,看它是否存在左節點,而且是葉子節點

/**
 * @param {TreeNode} root
 * @return {number}
 */
var sumOfLeftLeaves = function(root) {
    let sum = 0;
    function checkLeave(root) {
        if (!root.left && !root.right) {
            return true;
        }else {
            return false;
        }
    }
    const bianli = (root) => {
        if(!root){
            return;
        }
        if(root.left && checkLeave(root.left)){
            sum += root.left.val;
        }
        bianli(root.left);
        bianli(root.right);
    }
    bianli(root)
    return sum;
};
8. 112. 路徑總和

回溯問題,後面應該會單獨再歸納回溯的專題

/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    if(!root) {
        return false;
    }
    function checkLeave(root) {
        if (!root.left && !root.right){
            return true;
        }else {
            return false;
        }
    }
    // backtrace
    const check = (root,sum) => {
        if (checkLeave(root) && sum === 0){
            return true;
        }
      if (checkLeave(root)) {
          return false;
      }

      if (root.left) {
          if (check(root.left,sum- root.left.val)){
              return true;
          }
      }

      if (root.right) {
          if (check(root.right,sum- root.right.val)){
              return true;
          }
      }

      return false;
    }
    return check(root,targetSum-root.val);
};

二叉樹的修改和構造問題

套路:

修改:找到對應的節點,修改左右子樹

構造:先構造根節點,再遞迴構造左右子樹

1. 226. 翻轉二叉樹

遍歷,每一個節點都交換它的左右節點

/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    // if (!root) {
    //     return root;
    // }

    function swap(root) {
        let tmp = root.left;
        root.left = root.right;
        root.right = tmp;
    }

    const order = (root) => {
      if (!root) {
          return;
      }
      if (root.left || root.right) {
          swap(root);
      }
      order(root.left);
      order(root.right)
    }
    order(root);
    return root;
};
2. 106. 從中序與後序遍歷序列構造二叉樹

在後序找到根節點,再根據中序構造左右子樹

/**
 * @param {number[]} inorder
 * @param {number[]} postorder
 * @return {TreeNode}
 */
var buildTree = function(inorder, postorder) {
    if (postorder.length === 0) {
        return null;
    }
    let root = new TreeNode(postorder[postorder.length-1]);
    let mid = inorder.findIndex((number) => number === root.val);
    root.left = buildTree(inorder.slice(0,mid),postorder.slice(0,mid));
    root.right = buildTree(inorder.slice(mid+1,inorder.length),postorder.slice(mid,postorder.length-1));

    return root;
};
3. 105. 從前序與中序遍歷序列構造二叉樹

跟上一題一樣,在前序找到根節點,再根據中序構造左右子樹

/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
    if (preorder.length === 0) {
        return null;
    }
    let root = new TreeNode(preorder[0]);
    let mid = inorder.findIndex((number) => number === root.val);
    root.left = buildTree(preorder.slice(1,mid+1),inorder.slice(0,mid));
    root.right = buildTree(preorder.slice(mid+1,preorder.length),inorder.slice(mid+1,inorder.length));

    return root;
};
4. 654. 最大二叉樹

套路,找到對應的節點構造根,再構造左右子樹

/**
 * @param {number[]} nums
 * @return {TreeNode}
 */
var constructMaximumBinaryTree = function(nums) {
    function getMax(start, end) {
        let res = start;
        for (let i = start; i <= end; i++) {
            if (nums[i]>nums[res]) {
                res = i;
            }
        }
        return res;
    }
    const backtrace = (start=0,end=nums.length-1) => {
        if (start > end){
            return null;
        }
      let index = getMax(start,end);
      let root = new TreeNode(nums[index],backtrace(start,index-1),backtrace(index+1,end))

      return root;
    }
    return backtrace();
};
5. 617. 合併二叉樹

套路,找到對應的節點構造根,再構造左右子樹

/**
 * @param {TreeNode} root1
 * @param {TreeNode} root2
 * @return {TreeNode}
 */
var mergeTrees = function(root1, root2) {
    const backtrace = (node1,node2) => {
      if (!node1) {
          return node2;
      }
      if (!node2) {
          return node1;
      }
      let root = new TreeNode(node1.val+node2.val,backtrace(node1.left,node2.left),backtrace(node1.right,node2.right))

      return root;
    }
    return backtrace(root1,root2);
};

二叉樹的祖先問題

1. 236. 二叉樹的最近公共祖先
  • map儲存所有的節點以及其父節點
  • 利用map記錄p的所有祖宗節點,一直往上竄,直到root
  • 利用map找q的祖宗節點,直到找到的節點出現在p的祖宗節點中
/**
 * 最低公共祖先
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    let map = new Map();
    map.set(root,root);
    const preOrder = (root) => {
      if (!root) {
          return;
      }
      map.set(root.left,root);
      map.set(root.right,root);
      preOrder(root.left);
      preOrder(root.right);
    }
    preOrder(root);
    let hashset = [];
    let cur = p;
    while (cur !== map.get(cur)) {
        hashset.push(cur);
        cur = map.get(cur);
    }
    hashset.push(root)

    cur = q;
    while (cur !== map.get(cur)) {
        let index = hashset.findIndex((item) => item.val === cur.val)
        if (index === -1){
            cur = map.get(cur);
        }else {
            return hashset[index];
        }
    }
    return root;
};

優化的程式碼

  • 如果是p和q分別在root的左右兩邊,也就是說left和right都有值,那麼root就是祖先
  • left和right有一方是沒有值的,說明另一方就是這一方的祖先
/**
 * 最低公共祖先
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    const check = (root,p,q) => {
        if(!root || root === p || root === q) {
            return root;
        } 
        let left = check(root.left,p,q)
        let right = check(root.right,p,q);
        if(left && right) {
            return root;
        }
        return left !== null ? left : right;
    }

    return check(root,p,q)
};

二叉樹的套路

  1. 定義資料型別
  2. 向左邊要資訊,要右邊要資訊
  3. 然後根據左右的資訊,算出自己要返回的資訊