145. 二叉树的后序遍历
还是递归好用嘛
(1)确定递归函数的参数和返回值
参数是当前节点,vector数组,返回值是0或1
vector<int> postorder(TreeNode* root,vector<int>& post)
(2)确定终止条件
叶节点的下一个节点
if(root==NULL)
return 0;
(3)确定单层递归的逻辑
访问左子树,右子树,将遍历到的节点加入数组。
postorder(root->left, post);
postorder(root->right, post);
post.push_back(root->val);
整理起来
class Solution {
public:
int postorder(TreeNode* root,vector<int>& post)
{
if(root==NULL)
return 0;
postorder(root->left, post);
postorder(root->right,post);
post.push_back(root->val);
return 1;
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> post;
if(root==nullptr)
return post;
postorder(root, post);
return post;
}
};
很简单但很慢。。。
还是要学习迭代的写法嘛。。。
评论区大佬表示:
重要的是背诵模板,而不是乱写代码。 只要写出了下面这个模板
while( 栈非空 || p 非空)
{
if( p 非空)
{
}
else
{
}
}
那么不仅能够慢慢想出后序遍历,前中序遍历也能够想出来
先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转result数组,输出的结果顺序就是左右中了
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
但是这种方法实质上只是前序遍历的变体,而不是后序遍历思想中对节点的左右中顺序进行依次访问
递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同
后序与中序的不同之处在于:
中序遍历中,从栈中弹出的节点,其左子树是访问完了,可以直接访问该节点,然后接下来访问右子树。
后序遍历中,从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。
引入了一个prev来记录历史访问记录
当访问完一棵子树的时候,我们用prev指向该节点。
这样,在回溯到父节点的时候,我们可以依据prev是指向左子节点,还是右子节点,来判断父节点的访问情况。
那么后序遍历的思想就是
把当前节点嘎嘎嘎逐次访问其左子节点,其左子节点的左子节点,其左子节点的左子节点的左子节点……
然后顶部出栈,存入结果数组
如果有右节点的话,访问右节点。没有右节点或者已经访问玩右子节点的节点存入结果数组
class Solution {
public:
vector<int> postorderTraversal(TreeNode *root) {
stack<TreeNode *> stack;
vector<int> result;
if (root == nullptr) {
return result;
}
TreeNode *prev = nullptr;
while (root != nullptr || !stack.empty()) {
while (root != nullptr) {
stack.emplace(root);//把root插入栈
root = root->left;
}
root = stack.top();
stack.pop();
if (root->right == nullptr || root->right == prev) {
result.push_back(root->val);
prev = root;
root = nullptr;//避免重复访问左子树
} else {
stack.emplace(root);
root = root->right;
}
}
return result;
}
};
把最后的if else倒过来可能更易于理解
class Solution {
public:
vector<int> postorderTraversal(TreeNode *root) {
if (!root) return {};
vector<int> vec;
stack<TreeNode *> stk;
TreeNode *prev = nullptr;
auto node = root;
while (!stk.empty() || node) {
// 1.遍历到最左子节点
while (node) {
stk.emplace(node);
node = node->left;
}
node = stk.top(); stk.pop();
// 2.遍历最左子节点的右子树(右子树存在 && 未访问过)
if (node->right && node->right != prev) {
// 重复压栈以记录当前路径分叉节点
stk.emplace(node);
node = node->right;
} else {
// 后序:填充vec在node->left和node->right后面
// 注意:此时node的左右子树应均已完成访问
vec.emplace_back(node->val);
// 避免重复访问右子树[记录当前节点便于下一步对比]
prev = node;
// 避免重复访问左子树[设空节点]
node = nullptr;
}
}
return vec;
}
};
160. 相交链表
看了一下大佬的思路:求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到和curB 末尾对齐的位置,此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求链表A的长度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求链表B的长度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
双指针法
构建两个双指针,分别指向headA、headB,然后当a与b不相等时,先遍历各自节点部分,再遍历公共部分(若存在),接着遍历非公共部分,直到a等于b,则找到a为相交点(a->c->b与b->c->a,即a、b为非公共节点,c为公共节点)。否则遍历完各自节点均为有公共部分,说明不相交。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *a = headA, *b = headB;
while (a != b) {
a = a ? a->next : headB; //当a遍历为空时,则从另外指针的头结点开始遍历
b = b ? b->next : headA;
}
return a;
}
};