BFS的核心思想形象地将就是当面对岔路口时我们要每个都进去走一步,重点在于保存每个路口。一般用到队列的数据结构,使用其先进先出的特性。
LeetCode102-二叉树的层序遍历 Middle
典型的DFS题目,使用队列保存树的每层节点,按层遍历。
class Solution
{
public:
vector<vector<int>> levelOrder(TreeNode *root)
{
queue<TreeNode *> q; //保存树的节点
vector<vector<int>> res;
if (!root)
{
return res;
}
q.push(root);
while (!q.empty()) //每次进入while循环都是对于一层节点的遍历
{
res.push_back(vector<int>());
int len = q.size();
for (int i = 0; i < len; i++) //对于当前一层的节点一次遍历并入队其存在的左右子树
{
TreeNode *temp = q.front();
res.back().push_back(temp->val);
q.pop(); //每个节点访问后都出队,这样每次都只访问新的一层的节点
if (temp->left)
q.push(temp->left);
if (temp->right)
q.push(temp->right);
}
}
return res;
}
};
LeetCode107-二叉树的层序遍历II Middle
绝了,就是102题的代码加一行reverse~,就当复习BFS编程了。
class Solution
{
public:
vector<vector<int>> levelOrderBottom(TreeNode *root)
{
queue<TreeNode *> q;
vector<vector<int>> res;
if (!root)
return res;
q.push(root);
while (!q.empty())
{
int len = q.size();
res.push_back(vector<int>());
for (int i = 0; i < len; i++)
{
TreeNode *temp = q.front();
q.pop();
res.back().push_back(temp->val);
if (temp->left != nullptr)
q.push(temp->left);
if (temp->right != nullptr)
q.push(temp->right);
}
}
reverse(res.begin(), res.end());
return res;
}
};
LeetCode103-二叉树的锯齿形层序遍历 Middle
笑开花了,这两道题过得也太容易了。还是基础BFS加上reverse~
class Solution
{
public:
vector<vector<int>> zigzagLevelOrder(TreeNode *root)
{
queue<TreeNode *> q;
vector<vector<int>> res;
if (!root)
return res;
q.push(root);
while (!q.empty())
{
int len = q.size();
res.push_back(vector<int>());
for (int i = 0; i < len; i++)
{
TreeNode *temp = q.front();
q.pop();
res.back().push_back(temp->val);
if (temp->left != nullptr)
q.push(temp->left);
if (temp->right != nullptr)
q.push(temp->right);
}
}
for (int i = 0; i < res.size(); i++)
{
if (i % 2 != 0)
reverse(res[i].begin(), res[i].end());
}
return res;
}
};
LeetCode226-翻转二叉树 Easy
核心思路:对于每个非叶子节点,交换其左右子树,递归或迭代的交换下去
编程其实很简单,三种方法一看就会。递归与迭代两种方法时间复杂的均为O(N),但空间复杂度迭代法使用O(叶子节点数目)的空间,递归法使用O(树高度)的空间。
递归(自下而上)
class Solution
{
public:
TreeNode *invertTree(TreeNode *root)
{
Exchange(root);
return root;
}
void Exchange(TreeNode *root)
{
if (root == nullptr)
return;
Exchange(root->left);
Exchange(root->right);
TreeNode *temp = root->left;
root->left = root->right;
root->right = temp;
return;
}
};
递归(自上而下)
class Solution
{
public:
TreeNode *invertTree(TreeNode *root)
{
Exchange(root);
return root;
}
void Exchange(TreeNode *root)
{
if (root == nullptr)
return;
TreeNode *temp = root->left;
root->left = root->right;
root->right = temp;
Exchange(root->left);
Exchange(root->right);
return;
}
};
BFS-迭代
class Solution
{
public:
TreeNode *invertTree(TreeNode *root)
{
queue<TreeNode *> q;
if (root == nullptr)
return root;
q.push(root);
while (!q.empty())
{
int len = q.size();
TreeNode *temp;
for (int i = 0; i < len; i++)
{
temp = q.front();
TreeNode *tmp = temp->left;
temp->left = temp->right;
temp->right = tmp;
q.pop();
if (temp->left != nullptr)
q.push(temp->left);
if (temp->right != nullptr)
q.push(temp->right);
}
}
return root;
}
};
116. 填充每个节点的下一个右侧节点指针 Middle
对于结果是层次遍历的二叉树,没啥好说的,直接BFS,但是看到题目额外要求不使用额外空间,我们便应该想到使用题目中已有的空间或元素进行空间优化。
先来看一下直接BFS的思路以及代码:
直接使用了经典BFS的模板进行修改,用额外的队列保存一层的所有节点,达到横向的遍历。
class Solution {
public:
Node* connect(Node* root) {
queue<Node *> q; //保存树的节点
vector<vector<int>> res;
if (!root)
{
return root;
}
q.push(root);
while (!q.empty()) //每次进入while循环都是对于一层节点的遍历
{
res.push_back(vector<int>());
int len = q.size();
for (int i = 0; i < len; i++) //对于当前一层的节点一次遍历并入队其存在的左右子树
{
Node *temp = q.front();
res.back().push_back(temp->val);
q.pop(); //每个节点访问后都出队,这样每次都只访问新的一层的节点
if(i<len-1) temp->next=q.front();
if (temp->left)
q.push(temp->left);
if (temp->right)
q.push(temp->right);
}
}
return root;
}
};
接下来考虑优化,在遍历每一层时我们遇到的问题就是需要找到当前节点的next节点,上一种方法中,我们遍历每一层节点的同时,通过队列保存的下一个节点,对当前节点进行next连接的建立。
当不使用额外的队列用于保存当前层次节点时,我们面对的问题是想找到next节点只能通过上层节点的指针,那我们不妨在每一层节点上对下一层节点进行连接建立,这样可以方便的使用本层所建立的连接,用于寻找相关节点。
由于题目限制了二叉树为完美二叉树,所以,对于当前层次每一个节点,其左节点next指向右节点,右节点通过当前节点的next找到兄弟节点,并将next指向兄弟节点的左节点。
上代码:
class Solution
{
public:
Node *connect(Node *root)
{
if (root == nullptr)
{
return root;
}
Node *head = root, *l = root;//l用于保存下一层最左侧的节点
while (l->left != nullptr)//外层循环用于一层一层向下遍历
{
head = l;
while (head != nullptr)//内层循环用于一层内的横向遍历
{
head->left->next = head->right;//先将左儿子的next指向右儿子
if (head->next != nullptr)
{
head->right->next = head->next->left;//通过当前节点的next为右儿子寻找next
}
head = head->next;
}
l = l->left;
}
return root;
}
};
117. 填充每个节点的下一个右侧节点指针 II Middle
本题代码源自官方题解
说实话这题给我搞的心态有点崩,总体的思路和上一题类似,但是当为一个儿子寻找next时,我最初的想法是为每个儿子找到他的next,但是这样就需要分情况讨论,最后代码写的自己都看不下去,完全就是思路的混乱。
看了题解发现自己就是绕进去了,完全可以转换角度,我们每次保存当前最后一个没有建立next连接的儿子,然后对于遍历到的每个儿子节点,都用于和最后一个有效的儿子节点建立连接,只需要额外写一个函数就好…
class Solution
{
public:
void handle(Node *&last, Node *&p, Node *&nextStart)
{
if (last)
{
last->next = p;
}
if (!nextStart)
{
nextStart = p;
}
last = p;
}
Node *connect(Node *root)
{
if (!root)
{
return nullptr;
}
Node *start = root;
while (start)
{
Node *last = nullptr, *nextStart = nullptr;
for (Node *p = start; p != nullptr; p = p->next)
{
if (p->left)
{
handle(last, p->left, nextStart);
}
if (p->right)
{
handle(last, p->right, nextStart);
}
}
start = nextStart;
}
return root;
}
};