文章目录
1.树形DP套路
例:
(1)二叉树节点间的最大距离问题
解:想象以x为头节点的二叉树的最大距离:树形DP常见的可能性分类:头节点参与/头节点不参合。左树/右树需要给我的信息:最大距离和树高。
代码实现:
class Node {
public:
Node* left;
Node* right;
int val;
Node(int val) {
this->left = nullptr;
this->right = nullptr;
this->val = val;
}
};
struct info {
int height;
int maxDistance;
info(int height,int maxDistance) {
this->height = height;
this->maxDistance = maxDistance;
}
};
info* process(Node* head) {
if (head == nullptr) {
return new info(0, 0);
}
info* leftInfo = process(head->left);
info* rightInfo = process(head->right);
int leftmaxDis = leftInfo->maxDistance;
int rightmaxDis = rightInfo->maxDistance;
int maxDis = leftInfo->height + rightInfo->height + 1;
int height = max(leftInfo->height, rightInfo->height) + 1;
return new info(height, max(max(leftmaxDis, rightmaxDis), maxDis));
}
int getMaxDistance(Node* head) {
return process(head)->maxDistance;
}
(2)派对的最大快乐值
解:本题是个多叉树
以x为头节点的整棵树最大快乐值是多少?分成x是参与还是不参与:
问每棵子树要头节点来的时候的最大快乐值和头节点不来时候的最大快乐值
代码实现:
class Node {
public:
vector<Node*>nexts;
int happiness;
Node(int happiness) {
this->happiness = happiness;
}
};
struct info {
int laiMaxHa;
int buMaxHa;
info(int laiMaxHa, int buMaxHa) {
this->laiMaxHa = laiMaxHa;
this->buMaxHa = buMaxHa;
}
};
info* process(Node* head) {
if (head->nexts.size() == 0) {
return new info(head->happiness, 0);
}
int lai = head->happiness;
int bu = 0;
for (Node* next : head->nexts) {
info* nextInfo = process(next);
bu += max(nextInfo->buMaxHa, nextInfo->laiMaxHa);
lai += nextInfo->buMaxHa;
}
return new info(lai, bu);
}
int getMaxHa(Node* head) {
info* res = process(head);
return max(res->buMaxHa, res->laiMaxHa);
}
2.Morris遍历
笔试用其他的遍历方法,面试可以用Morris遍历,增长印象分。利用底层大量的空闲指针来回到上级去(线索二叉树)。一个节点如果有左树一定能回到该节点两次,否则只能回到自己一次,根据右指针的指向来判断第几次到达自己(mostRight的右指针指向空,则是第一次来到自己,mostRight右指针指向自己,则是第二次来到自己)
递归的遍历每个节点到达3次,而Morris遍历最多只能到达两次,对于没有左子树的节点只到达一次,对于有左子树的节点会到达两次。
时间复杂度:
代码实现:
class Node {
public:
Node* left;
Node* right;
int val;
Node(int val) {
this->left = nullptr;
this->right = nullptr;
this->val = val;
}
};
//Morris遍历
void morris(Node* head) {
Node* cur = head;
Node* mostRight = nullptr;//左树的最右节点
while (cur != nullptr) {
mostRight = cur->left;
if (mostRight != nullptr) {//有左树的情况
while (mostRight->right != nullptr && mostRight->right != cur) {
mostRight = mostRight->right;
}
if (mostRight->right == nullptr) {//第一次来到cur
mostRight->right = cur;
cur = cur->left;
continue;
}
else {//第二次来到cur
mostRight->right = nullptr;
}
}
cur = cur->right;
}
}
Morris遍历改先序遍历:1) 对于只经过一次的节点–>直接打印;2) 对于经过两次的节点–>第一次打印
代码实现:
//Morris改先序遍历
void morrisPre(Node* head) {
Node* cur = head;
Node* mostRight = nullptr;
while (cur != nullptr) {
mostRight = cur->left;
if (mostRight != nullptr) {
while (mostRight->right != nullptr && mostRight->right != cur) {
mostRight = mostRight->right;
}
if (mostRight->right == nullptr) {
cout << cur->val << " ";
mostRight->right = cur;
cur = cur->left;
}
else {
mostRight->right = nullptr;
cur = cur->right;
}
}
else {
cout << cur->val << " ";
cur = cur->right;
}
}
cout << endl;
}
Morris遍历改中序遍历:1) 对于只经过一次的节点–>直接打印;2) 对于经过两次的节点–>第二次打印
代码实现:
//Morris改中序遍历
void morrisIn(Node* head) {
Node* cur = head;
Node* mostRight = nullptr;
while (cur != nullptr) {
mostRight = cur->left;
if (mostRight != nullptr) {
while (mostRight->right != nullptr && mostRight->right != cur) {
mostRight = mostRight->right;
}
if (mostRight->right == nullptr) {
mostRight->right = cur;
cur = cur->left;
}
else {
cout << cur->val << " ";
mostRight->right = nullptr;
cur = cur->right;
}
}
else {
cout << cur->val << " ";
cur = cur->right;
}
}
cout << endl;
}
Morris遍历改后序遍历:1) 对于经过两次的节点–>第二次经过自己时,逆序打印自己左树的右边界;2) 对于只经过一次的节点–>什么都不做;3) 遍历完之后,单独逆序打印整棵树的右边界
如何逆序打印右边界?链表的逆序!
代码实现:
//morris改后序遍历
Node* reverse(Node* head) {
Node* cur = head;
Node* pre = nullptr;
while (cur != nullptr) {
Node* next = cur->right;
cur->right = pre;
pre = cur;
cur = next;
}
return pre;
}
void printRightEdge(Node* head) {
Node* h = reverse(head);
Node* cur = h;
while (cur != nullptr) {
cout << cur->val << " ";
cur = cur->right;
}
head = reverse(h);
}
void morrisPos(Node* head) {
Node* cur = head;
Node* mostRight = nullptr;
while (cur != nullptr) {
mostRight = cur->left;
if (mostRight != nullptr) {
while (mostRight->right != nullptr && mostRight->right != cur) {
mostRight = mostRight->right;
}
if (mostRight->right == nullptr) {
mostRight->right = cur;
cur = cur->left;
}
else {
mostRight->right = nullptr;
printRightEdge(cur->left);//是左树的右边界
cur = cur->right;
}
}
else {
cur = cur->right;
}
}
printRightEdge(head);
cout << endl;
}
例:利用Morris遍历判断一个二叉树是否为搜索二叉树
代码实现:
//是否为搜索二叉树
bool isSBT(Node* head) {
Node* cur = head;
Node* mostRight = nullptr;
int pre = INT_MIN;
bool flag = true;
while (cur != nullptr) {
mostRight = cur->left;
if (mostRight != nullptr) {
while (mostRight->right != nullptr && mostRight->right != cur) {
mostRight = mostRight->right;
}
if (mostRight->right == nullptr) {
mostRight->right = cur;
cur = cur->left;
}
else {
mostRight->right = nullptr;
if (pre > cur->val) {
flag = false;
}
pre = cur->val;
cur = cur->right;
}
}
else {
if (pre > cur->val) {
flag = false;
}
pre = cur->val;
cur = cur->right;
}
}
return flag;//防止改变二叉树的结构,因此要遍历完之后再返回
}
总结
Morris遍历解决了最本质的遍历问题,因此很多问题的解都以他为最优解。哪些二叉树的题目是以树形DP作为最优解的?哪些二叉树的题目是以Morris作为最优解的?如果选择的方法必须做信息的强整合,则必须用DP;并不需要做信息的强整合,则Morris为最优解