抱佛脚-刷题系列之二叉树

抱佛脚一时爽,一直抱佛脚一直爽!这篇文章总结常见的二叉树问题~
参考链接:leetcode 剑指offer cyc的github Annie Kim的博客

一些概念


  • 完全二叉树:堆就是完全二叉树
  • 二叉搜索树:右孩子<=根<左孩子
  • 平衡二叉树: (左子树高度-右子树高度)<=1
  • dfs=先/中/后序遍历
    bfs=层次遍历
    快速排序相当于前序遍历
    归并排序相当于后序遍历

一些经验


  • 一般都有递归、迭代的方法
  • 迭代的方法中:dfs(比如求路径和)用栈,bfs(比如求树高)用队列

各种遍历


层次遍历

  • 非递归
void traverse(TreeNode* root) {
    if (!root) return;
    queue myQueue;
    myQueue.push(root);
    while (!myQueue.empty()) {
        TreeNode* cur = myQueue.front();
        visit(cur);
        myQueue.pop();
        if (cur->left) myQueue.push(cur->left);
        if (cur->right) myQueue.push(cur->right);
    }
}
  • 递归
void traverse(TreeNode* root) {
    if (!root) return;
    int depth = getDepth(root);
    for (int i = 1; i <= depth; ++i) visitAtDepth(root, i);
}

int getDepth(TreeNode* root) {
    if (!root) return 0;
    return max(getDepth(root->left), getDepth(root->right)) + 1;
}

void visitAtDepth(TreeNode* root, int depth) {
    if (!root || depth < 1) return;
    if (depth == 1) {
        visit(root);
        return;
    }
    visitAtDepth(root->left, depth - 1);
    visitAtDepth(root->right, depth - 1);
}

中序遍历

  • 非递归
// 写法一
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    stack myStack;
    while (!myStack.empty() || root != nullptr) {
        if (root) {
            myStack.push(root);
            root = root->left;
        } else {
            root = myStack.top(); 
            myStack.pop();
            visit(root);
            root = root->right;
        }
    }
}
// 写法二
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    stack myStack;
    while (!myStack.empty() || root != nullptr) {
        while (root != nullptr) {
            myStack.push(root);
            root = root->left;
        }
        root = myStack.top();
        visit(root);
        myStack.pop();
        root = root->right;
    }   
}
  • 递归
void traverse(TreeNode* root) {
    if (!root) return;
    traverse(root->left);
    visit(root);
    traverse(root->right);
}
  • morris遍历
void inorderMorrisTraversal(TreeNode *root) {
    TreeNode *cur = root, *prev = NULL;
    while (cur != NULL) {
        if (cur->left == NULL) {
            printf("%d ", cur->val);
            cur = cur->right;
        }
        else {
            // find predecessor
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;
            if (prev->right == NULL) {
                prev->right = cur;
                cur = cur->left;
            } else {
                prev->right = NULL;
                printf("%d ", cur->val);
                cur = cur->right;
            }
        }
    }
}

前序遍历

  • 非递归
// 写法一
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    stack myStack;
    while (!myStack.empty || root != nullptr) {
        while (root != nullptr) { // 不断把左孩子推入栈
            visit(root);
            myStack.push(root);
            root = root->left;
        }
        root = myStack.top();
        myStack.pop();
        root = root->right; // 前序遍历右子树
    }
}
// 写法二
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    stack myStack;
    while (!myStack.empty() || root != nullptr) {
        if (root) {
            myStack.push(root);
            visit(root);
            root = root->left;
        } else {
            root = myStack.top(); 
            myStack.pop();
            root = root->right;
        }
    }
}
  • 递归
void traverse(TreeNode* root) {
    if (!root) return;
    visit(root);
    traverse(root->left);
    traverse(root->right);
}
  • morris遍历
void preorderMorrisTraversal(TreeNode *root) {
    TreeNode *cur = root, *prev = NULL;
    while (cur != NULL) {
        if (cur->left == NULL) {
            printf("%d ", cur->val);
            cur = cur->right;
        } else {
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;
            if (prev->right == NULL) {
                printf("%d ", cur->val);  // the only difference with inorder-traversal
                prev->right = cur;
                cur = cur->left;
            } else {
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

后序遍历

  • 非递归
void traverse(TreeNode* root) {
    stack myStack;
    TreeNode* lastvisit = root;
    while (!myStack.empty || root != nullptr) {
        if (root != nullptr) {
            myStack.push(root);
            root = root->left;
        } else {
            root = myStack.top();
            if (root->right == nullptr || root->right == lastvisit) {
                visit(root);
                myStack.pop();
                lastvisit = root;
                root = nullptr;
            } else {
                root = root->right;
            }
        }
    }
}
  • 递归
void traverse(TreeNode* root) {
    if (!root) return;
    traverse(root->left);
    traverse(root->right);
    visit(root);
}
  • morris遍历
void reverse(TreeNode *from, TreeNode *to) { // reverse the tree nodes 'from' -> 'to'.
    if (from == to)
        return;
    TreeNode *x = from, *y = from->right, *z;
    while (true) {
        z = y->right;
        y->right = x;
        x = y;
        y = z;
        if (x == to)
            break;
    }
}

void printReverse(TreeNode* from, TreeNode *to) { // print the reversed tree nodes 'from' -> 'to'.
    reverse(from, to);
    TreeNode *p = to;
    while (true) {
        printf("%d ", p->val);
        if (p == from)
            break;
        p = p->right;
    }
    reverse(to, from);
}

void postorderMorrisTraversal(TreeNode *root) {
    TreeNode dump(0);
    dump.left = root;
    TreeNode *cur = &dump, *prev = NULL;
    while (cur) {
        if (cur->left == NULL) {
            cur = cur->right;
        } else {
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;
            if (prev->right == NULL) {
                prev->right = cur;
                cur = cur->left;
            } else {
                printReverse(cur->left, prev);  // call print
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

一些题目


求二叉树的直径(lc543)

  • 思路
    1. 包含根的最大直径=左子树的高+右子树的高,不包含根的最大直径=max(左子树的直径,右子树的直径)
    2. 所以,直径=max(左子树的直径,右子树的直径,左子树的高+右子树的高)

前序+中序->二叉树(lc105)

  • 思路
    从前序中找当前树的根节点,中序中根节点的左(右)边就是左(右)子树的节点
class Solution {
public:
    TreeNode *buildTree(vector &preorder, vector &inorder) {
        return buildTree(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);
    }

    TreeNode *buildTree(vector &preorder, int pLeft, int pRight, vector &inorder, int iLeft, int iRight) {
        if (pLeft > pRight || iLeft > iRight) return nullptr;
        int i = 0;
        for (i = iLeft; i <= iRight; ++i) {
            if (preorder[pLeft] == inorder[i]) break;
        }
        int leftTreeSize = i - iLeft;
        TreeNode *cur = new TreeNode(preorder[pLeft]);
        cur->left = buildTree(preorder, pLeft + 1, pLeft + leftTreeSize, inorder, iLeft, i - 1);
        cur->right = buildTree(preorder, pLeft + leftTreeSize + 1, pRight, inorder, i + 1, iRight);
        return cur;
    }
};

前序+中序->后序

class Solution {
public:
    void buildTree(vector &preorder, vector &inorder, vector& postorder) {
        buildTree(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1, postorder);
    }

    void buildTree(vector &preorder, int pLeft, int pRight, vector &inorder, int iLeft, int iRight, vector& postorder) {
        if (pLeft > pRight || iLeft > iRight) return NULL;
        int i = 0;
        for (i = iLeft; i <= iRight; ++i) {
            if (preorder[pLeft] == inorder[i]) break;
        }
        int leftTreeSize = i - iLeft;
        buildTree(preorder, pLeft + 1, pLeft + leftTreeSize, inorder, iLeft, i - 1);
        buildTree(preorder, pLeft + leftTreeSize + 1, pRight, inorder, i + 1, iRight);
        postorder.push_back(inorder[i]);
    }
};

判断二叉树是否对称(lc101)

  • 法一:递归
class Solution {
public:
  bool isSameRoot(TreeNode* root1, TreeNode* root2) {
    if (!root1 && !root2) return true;
    if (!root1 || !root2) return false;
    if (root1->val != root2->val) return false;
    return isSameRoot(root1->left, root2->right) && isSameRoot(root1->right, root2->left);
  }

  bool isSymmetric(TreeNode* root) {
    if (!root) return true;
    return isSameRoot(root->left, root->right);
  }
};
  • 法二:迭代,队列1装左子树层序遍历的结果,队列2装右子树反向层次遍历的结果
class Solution {
public:
  bool isSymmetric(TreeNode* root) {
    if (!root) return true;
    if (!root->left && !root->right) return true;
    if (!root->left || !root->right) return false;
    queue q1, q2;
    q1.push(root->left);
    q2.push(root->right);
    while (!q1.empty()) {
      TreeNode* cleft = q1.front(); q1.pop();
      TreeNode* cright = q2.front(); q2.pop();
      if (cleft->val != cright->val) return false;
      if (!cleft->left && cright->right || cleft->left && !cright->right) return false;
      if (!cleft->right && cright->left || cleft->right && !cright->left) return false;
      if (cleft->left) {
        q1.push(cleft->left);
        q2.push(cright->right);
      }
      if (cleft->right) {
        q1.push(cleft->right);
        q2.push(cright->left);
      }
    }
    return true;
  }
};

判断二叉树是否有从根到节点的路径,和为target(lc112)

  • 法一:递归
  bool hasPathSum(TreeNode* root, int sum) {
    if (!root) return false;
    if (!root->left && !root->right) return root->val == sum;
    int flag = false;
    if (root->left) {
      flag = flag || hasPathSum(root->left, sum - root->val);
    }
    if (root->right) {
      flag = flag || hasPathSum(root->right, sum - root->val);
    }
    return flag;
  }
  • 法二:迭代,把根节点的值累加到孩子节点上
  bool hasPathSum(TreeNode* root, int sum) {
    if (!root) return false;
    stack s;
    s.push(root);
    while (!s.empty()) {
      TreeNode* cur = s.top();
      s.pop();
      if (!cur->left && !cur->right && cur->val == sum) return true;
      if (cur->left) {
        cur->left->val += cur->val;
        s.push(cur->left);
      }
      if (cur->right) {
        cur->right->val += cur->val;
        s.push(cur->right);
      } 
    }
    return false;
  }

二叉树中和为target的路径(jz24)

class Solution {
public:
    void helper(TreeNode* root, int sum, vector& cur, vector>& result) {
        if (!root) return;
        cur.push_back(root->val);
        if (root->left) helper(root->left, sum - root->val, cur, result);
        if (root->right) helper(root->right, sum - root->val, cur, result);
        if (!root->left && !root->right && sum == root->val) result.push_back(cur);
        cur.pop_back(); 
    }

    vector> pathSum(TreeNode* root, int sum) {
        vector> result;
        vector cur;
        helper(root, sum, cur, result);
        return result;
    }
};

二叉搜索树的最近公共祖先(lc235)

  • 思路:如果根节点的值大于p和q之间的较大值,说明p和q都在左子树中,那么此时我们就进入根节点的左子节点继续递归,如果根节点小于p和q之间的较小值,说明p和q都在右子树中,那么此时我们就进入根节点的右子节点继续递归,如果都不是,则说明当前根节点就是最小共同父节点
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (!root) return nullptr;
    while (root) {
        if (root->val > max(p->val, q->val)) root = root->left;
        else if (root->val < min(p->val, q->val)) root = root->right;
        else return root;
    }
    return nullptr;
}

二叉树的最近公共祖先(lc236)

  TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (!root || p == root || q == root) return root;
    TreeNode* left = lowestCommonAncestor(root->left, p, q);
    TreeNode* right = lowestCommonAncestor(root->right, p, q);
    if (left && right) return root;
    return left ? left : right;
  }

判断是否为二叉搜索树(lc98)

  • 注意:在这道题中定义是左子树值<根<右子树
  • 法一:中序遍历该数到数组中,判断左树是否都小于根、右树是否都大于根、左右树是否都是二叉搜索树
  • 法二:判断左子树的最右节点是否大于根、右子树的最左节点是否小于根、左右树是否都是二叉搜索树
bool isValidBST(TreeNode* root) {
    if (!root) return true;
    TreeNode* rleft = root->left;
    while (rleft && rleft->right) {
        rleft = rleft->right;
    }
    if (rleft && rleft->val >= root->val) return false;
    TreeNode* rright = root->right;
    while (rright && rright->left) {
        rright = rright->left;
    }
    if (rright && rright->val <= root->val) return false;
    return isValidBST(root->left) && isValidBST(root->right);
}

判断一棵树是否另一棵树的子树(jz17)

class Solution {
public:
    bool SubtreeWithRoot(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot2) return true;
        if (!pRoot1) return false;
        if (pRoot1->val != pRoot2->val) return false;
        return SubtreeWithRoot(pRoot1->left, pRoot2->left) && SubtreeWithRoot(pRoot1->right, pRoot2->right);
    }
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot2) return false;
        if (!pRoot1) return false;
        return HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2) || SubtreeWithRoot(pRoot1, pRoot2);
    }
};

把二叉树变为其镜像(jz18)

  • 法一:迭代
void Mirror(TreeNode *pRoot) {
  if (!pRoot) return;
  queue q;
  q.push(pRoot);
  while (!q.empty()) {
    TreeNode* cur = q.front();
    TreeNode* tmp = cur->left;
    cur->left = cur->right;
    cur->right = tmp;
    if (cur->left) q.push(cur->left);
    if (cur->right) q.push(cur->right);
    q.pop();
  } 
}
  • 法二:递归
void Mirror(TreeNode *pRoot) {
    if (!pRoot) return;
    Mirror(pRoot->left);
    Mirror(pRoot->right);
    TreeNode* tmp = pRoot->left;
    pRoot->left = pRoot->right;
    pRoot->right = tmp;
}

判断数组是否为二叉搜索树后序遍历结果(jz23)

bool VerifySquenceOfBSTHelp(vector& sequence, int left, int right) {
  if (left >= right) return true;
  int root = sequence[right];
  int index = left;
  while (index < right && sequence[index] <= root) ++index;
  for (int i = index; i < right; ++i) {
    if (sequence[i] < root) return false;
  }
  return VerifySquenceOfBSTHelp(sequence, left, index - 1) && VerifySquenceOfBSTHelp(sequence, index, right - 1);
}

bool VerifySquenceOfBST(vector sequence) {
  if (!sequence.size()) return false;
  int n = sequence.size();
  return VerifySquenceOfBSTHelp(sequence, 0, n - 1);
}

二叉树的深度(jz38)

  • 法一:递归,max(左子树深度,右子树深度)+1
  • 法二:迭代,层次遍历算有多少层
int TreeDepth(TreeNode* pRoot) {
    if (!pRoot) return 0;
    queue myQueue;
    myQueue.push(pRoot);
    int cnt = 0;
    while (!myQueue.empty()) {
        int width = myQueue.size();
        ++cnt;
        while (width--) {
            TreeNode* cur = myQueue.front();
            myQueue.pop();
            if (cur->left) myQueue.push(cur->left);
            if (cur->right) myQueue.push(cur->right);
        }
    }
    return cnt;
}

二叉树的最小深度(lc111)

  • 法一:递归(注意:考虑到[1,2]的情况,不能直接 return 1 + min(minDepth(root->left), minDepth(root->right)))
int minDepth(TreeNode* root) {
    if (!root) return 0;
    if (!root->left) return 1 + minDepth(root->right);
    if (!root->right) return 1 + minDepth(root->left);
    return 1 + min(minDepth(root->left), minDepth(root->right));
}
  • 法二:迭代
int minDepth(TreeNode* root) {
    if (!root) return 0;
    queue q;
    q.push(root);
    int res = 0;
    while (!q.empty()) {
        int width = q.size();
        ++res;
        while (width--) {
            TreeNode* cur = q.front();
            q.pop();
            if (cur->left) q.push(cur->left);
            if (cur->right) q.push(cur->right);
            if (!cur->left && !cur->right) return res;
        }
    }
    return res;
}

判断是否为平衡二叉树(jz39)

  • 思路:递归,abs(左子树深度-右子树深度) <= 1 && 左右子树均是平衡二叉树

二叉树最大路径和(lc124)

  • 思路:对于每个结点来说,要知道经过其左子结点的 path 之和大还是经过右子节点的 path 之和大。递归函数返回值就可以定义为以当前结点为根结点,到叶节点的最大路径之和,然后全局路径最大值放在参数中,用结果 res 来表示
class Solution {
public:
    int maxPathSum(TreeNode* root) {
        int res = INT_MIN;
        helper(root, res);
        return res;
    }
    int helper(TreeNode* node, int& res) { // 返回以node为根节点到叶节点的最大路径和
        if (!node) return 0;
        int left = max(helper(node->left, res), 0);
        int right = max(helper(node->right, res), 0);
        res = max(res, left + right + node->val);
        return max(left, right) + node->val;
    }
};

二叉树展开为链表(lc114)

  • 法一:递归
void flatten(TreeNode* root) {
  if (!root) return;
  if (root->left) flatten(root->left);
  if (root->right) flatten(root->right);
  // 把根节点的左孩子插到根和右孩子之间
  TreeNode* tmp = root->right;
  root->right = root->left;
  root->left = nullptr; // 不要忘了这一句!
  while (root->right) root = root->right;
  root->right = tmp;
}
  • 法二:迭代
void flatten(TreeNode *root) {
    TreeNode *cur = root;
    while (cur) {
        if (cur->left) {
            TreeNode *p = cur->left;
            while (p->right) p = p->right;
            p->right = cur->right;
            cur->right = cur->left;
            cur->left = NULL;
        }
        cur = cur->right;
    }
}

有序数组转平衡二叉树(lc108)

  • 思路:递归,每次取中点作为root,再分别构建左子树和右子树
class Solution {
public:
    TreeNode* sortedArrayToBST(vector& nums, int left, int right) {
      if (right < 0 || right >= nums.size() || left > right) return nullptr;
      int mid = left + (right - left) / 2;
      ListNode* root = new ListNode(nums[mid]);
      ListNode* leftRoot = sortedArrayToBST(nums, left, mid - 1);
      ListNode* rightRoot = sortedArrayToBST(nums, mid + 1, right);
      root->left = leftRoot;
      root->right = rightRoot;
      return root;
    }

    TreeNode* sortedArrayToBST(vector& nums) {
      if (!nums.size()) return nullptr;
      TreeNode* root = sortedArrayToBST(nums, 0, nums.size() - 1);
      return root;
    }
};

返回所有和为target的路径(lc113)

  void findPath(TreeNode* root, int sum, vector>& result, vector& cur) {
    if (!root) return;
    cur.push_back(root->val);
    if (!root->left && !root->right) {
      if (root->val == sum) result.push_back(cur);
    }
    if (root->left) findPath(root->left, sum - root->val, result, cur);
    if (root->right) findPath(root->right, sum - root->val, result, cur);
    cur.pop_back();
  }

  vector> pathSum(TreeNode* root, int sum) {
     vector> result;
     vector cur;
     findPath(root, sum, result, cur);
     return result;
  }

中序遍历的下一个元素

二叉搜索树转为排序的双向链表

你可能感兴趣的:(抱佛脚-刷题系列之二叉树)