首先要明确的是,数据结构是工具,算法是通过合适的工具解决特定问题的方法。
二叉树是最容易培养框架思维的,而且大部分算法技巧,本质上都是树的遍历问题。
二叉树问题基本框架:
void traverse(TreeNode root) {
// 前序遍历
traverse(root.left)
// 中序遍历
traverse(root.right)
// 后序遍历
}
LeetCode 124 题,难度 Hard,让你求二叉树中最大路径和
题目具体如下:
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。
该路径至少包含一个节点,且不一定经过根节点。
初始代码如下:
class Solution {
public:
int maxPathSum(TreeNode* root) {
}
};
如开篇所提,二叉树问题的核心就在于三种遍历方式,此题也不例外。
根据题意,最大路径可在某一结点折返一次,即最长路径可为:
左子节点->公共父结点->右子节点;
再往下细分,如何选择左子节点和右子结点呢?对于每一个结点而言,我们需要分别其左子树和右子树各自的MaxPath,这样我们就将可能的最长路径抽象为了左子树MaxPath->公共父结点->右子树MaxPath。之后再将这个可能的最长路径与当前最长路径进行比对,保留较大值。典型的后序遍历。
利用后序遍历由叶节点开始遍历每一节点。对于每一结点他都可能成为最大值所经过的结点。
设左子树能提供的大小为pal,右子树为par;
倘若pal<0,则令其等于0,视为无意义;par同理;
则最大可能值为pal+par+root—>val;
其为父节点能做的贡献为max(pal,par)+root—>val;
C++ 实现代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxPathSum(TreeNode* root) {
int ans = INT_MIN;
oneSideMax(root,ans);
return ans;
}
int oneSideMax(TreeNode* root,int &ans) {
if (root == NULL) return 0;
int left = max(0, oneSideMax(root->left,ans));
int right = max(0, oneSideMax(root->right,ans));
ans = max(ans, left + right + root->val);
return max(left, right) + root->val;
}
};
Java代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
oneSideMax(root);
return res;
}
public int oneSideMax(TreeNode root){
if (root == null) return 0;
int left = Math.max(oneSideMax(root.left),0);
int right = Math.max(oneSideMax(root.right),0);
res = Math.max(left+right+root.val,res);
return root.val+Math.max(left,right);
}
}
LeetCode 105 题,难度 Medium,让你根据前序遍历和中序遍历的结果还原一棵二叉树,很经典的问题
前序遍历和中序遍历有如下特征:
前序中左起第一位肯定是根结点,我们可以据此找到中序中根结点的位置rootin
;
while (rootin <= inEnd && in[rootin] != pre[preStart]) rootin++;
中序中根结点左边就是左子树结点,右边就是右子树结点,即[左子树结点,根结点,右子树结点]
,我们就可以得出左子树结点个数为int left = rootin - inStart;
前序中结点分布应该是:[根结点,左子树结点,右子树结点]
根据前一步确定的左子树个数,可以确定前序中左子树结点和右子树结点的范围:
左子树范围:[preStart + 1, preStart + left]
右子树范围:[preStart + 1 + left, preEnd]
我们可以通过递归进行二叉树的创建:
如果我们要前序遍历生成二叉树的话,下一层递归应该是:
左子树:root->left = pre_order(前序左子树范围,中序左子树范围,前序序列,中序序列);
;
右子树:root->right = pre_order(前序右子树范围,中序右子树范围,前序序列,中序序列);
。
每一层递归都要返回当前根结点root
;
最后一个结点也被接入二叉树之后,通过以下判断,就无须进行遍历了:
if (preStart> preEnd|| inStart> inEnd) return NULL;
C++完整代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return pre_order(0, inorder.size() - 1, 0, inorder.size() - 1, preorder, inorder);
}
TreeNode *pre_order(int preStart, int preEnd, int inStart, int inEnd, vector<int> &pre, vector<int> &in) {
if (preStart > preEnd || inStart > inEnd) return NULL;
TreeNode *root = new TreeNode(pre[preStart]);
int rootin = inStart;
while (rootin <= inEnd && in[rootin] != pre[preStart]) rootin++;
int left = rootin - inStart;
root -> left = pre_order(preStart+1, preStart+left, inStart, rootin-1, pre, in);
root -> right = pre_order(preStart+1+left, preEnd, rootin+1, inEnd, pre, in);
return root;
}
};