五、二叉樹與圖(小象)
113.路徑之和II
思考:
1、查找出路徑,並且記錄下路徑的值。
2、和要求的sum值對比,如果正確就插入到result之中。
3、進行遞迴查詢路徑的時候,什麼時候跳出遞迴?答:如果遍歷至空指標(葉子結點的孩子),結束
什麼時候新增路徑呢?為葉子結點,並且路徑值和要求的和值相同。
4、題目大概就是在前序遍歷的時候,完成插入節點到路徑的操作,在後序遍歷的時候,把該節點值從path彈出。
優化:舉個例子,如果要求的和值是5,然後我們搜尋路徑已經1->2->4,此時二叉樹下面還有一層,分別為3,5,這樣的話其實搜尋到第三層,路徑還沒有完全寫出的時候,已經是超過了要求的和值,就沒有必要再搜尋這個4節點下的左右孩子3,5,用什麼方法呢?這點我只是想到還沒解決。
5、程式碼AC了,還有個地方還是要多注意,在遞迴的時候,如果你的值需要一直保持改變後的值,就比如path_val,path,result,這種,他在每次遞迴都在 修改,就要加引用,這點如果沒有新增就會程式發生錯誤,還有可能debug半天。。
/** * 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: vector<vector<int>> pathSum(TreeNode* root, int sum) { vector<int> path; int path_val=0; vector<vector<int>> result; preOrder(root, path_val, sum, path, result); return result; } void preOrder(TreeNode* node, int &path_val, int sum, vector<int> &path, vector<vector<int>>& result) { if (!node) { return; } path.push_back(node->val); path_val += node->val; if (!node->left && !node->right && path_val == sum) { result.push_back(path); } preOrder(node->left, path_val, sum, path, result); preOrder(node->right, path_val, sum, path, result); path.pop_back(); path_val -= node->val; } };
236.二叉樹的最近公共祖先
思考:找到兩個節點的最近公共祖先,因為二叉樹的結構性,他都是從一個節點伸展出來的各個路徑,所以要找到節點p,q的公共最近祖先,只需要到p,q的兩條路徑,然後像找相交連結串列的公共節點(leetcode 160)一樣,一步步回退回去查詢到第一個相同的節點,這裡和上一題求所有路徑值為sum的題目不同在於,這裡只是查詢到p這個節點的路徑,所以只要找到了就可以設定一個標記變數finish直接退出查詢就可以了。
1、跳出迴圈的條件,當前節點為空或者finish==1
2、得到兩個路徑,向後pop_back(),知道兩個路徑相等,然後查詢是否是相同的節點。
3、和上題不同在於上一題是值,這一題是節點指標;上一題是所有路徑,這一題是單一路徑,所有設定一個finish標誌。
查詢某個節點的路徑:
void preorder(TreeNode* node,
TreeNode *search,
std::vector<TreeNode*> &path,
std::vector<TreeNode*> &result,
int &finish) {
if (!node||finish) {
return;
}
path.push_back(node);
if (node == search) {
finish = 1;
result = path;
}
preorder(node->left, search, path, result, finish);
preorder(node->right, search, path, result, finish);
path.pop_back();
}
整體程式碼:
/**
* 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
int finish = 0;
vector<TreeNode*> path;
vector<TreeNode*> result1;//想到這個地方了!是因為內建屬性才一定需要初始化,有的類string這種會幫你初始化
vector<TreeNode*> result2;
preorder(root, p, path, result1, finish);
finish = 0;
preorder(root, q, path, result2, finish);
while (result1.size() > result2.size()) {
result1.pop_back();
}
while (result2.size() > result1.size()) {
result2.pop_back();
}
while (result1.back() != result2.back()) {
result1.pop_back();
result2.pop_back();
}
return result1.back();
}
void preorder(TreeNode* node,
TreeNode *search,
std::vector<TreeNode*> &path,
std::vector<TreeNode*> &result,
int &finish) {
if (!node||finish) {
return;
}
path.push_back(node);
if (node == search) {
finish = 1;
result = path;
}
preorder(node->left, search, path, result, finish);
preorder(node->right, search, path, result, finish);
path.pop_back();
}
};
114.二叉樹展開為連結串列
思考:
1、二叉樹和連結串列的儲存結構相比,只多了一個指標。題中整理的連結串列為二叉樹的先序遍歷,只需要將二叉樹的左子樹整理成單鏈表,右子樹整理成單鏈表,然後左子樹最後的一個節點指標指向右子樹的第一個指標,所以儲存資訊的時候還應該包含一個last指標。
2、在前序遍歷的時候,相當於分治演算法的最小一個子問題,就是訪問到葉子節點,此時把last = node;
3、如果存在左子樹,中序遍歷的時候,把當前的node->left = NULL;node->right = left_last;(當前節點的下一個應該是左子樹的最後一個結點),並且記錄下last = left_last(因為可能右子樹為空,所以臨時儲存last指標)
4、如果存在右子樹,後序遍歷的時候,若存在左子樹,把左子樹最後一個節點拼接到右子樹的首部(left_last = right),之後再輸出last = right_last;
5、設定最後一個節點指標的原因,若1->2->3->4...只是很深的樹的一小部分,1不是根節點,就必須把這小部分的最後一個節點指標傳出去,完成遞迴。
6、什麼時候對節點判空?感覺應該在flatten(TreeNode* )就判斷比較好。若傳入的就是空節點,直接返回。
/**
* 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:
void flatten(TreeNode* root) {
if(!root){
return;
}
TreeNode* last = NULL;
preorde(root,last);
}
void preorde(TreeNode* node,TreeNode* &last){
if(!node->left&&!node->right){
last = node;
return ;
}
TreeNode* left = node->left;
TreeNode* right = node->right;
TreeNode* left_last = NULL;
TreeNode* right_last = NULL;
if(left){
preorde(left,left_last);
node->left = NULL;
node->right = left;
last = left_last;
}
if(right){
preorde(right,right_last);
if(left){
left_last->right = right;
}
last = right_last;
}
}
};
207.課程表(圖)
圖的基礎資料結構:
struct GraphNode{
int label;//標識圖的哪個節點
vector<GraphNode*> negihbors;//節點的領接表
};
圖的深度優先遍歷,廣度優先遍歷:
TIPS:需要一個識別符號visit來儲存每個節點是否被訪問過了,只有沒有被訪問的才需要搜尋。
深度優先遍歷,從某個節點出發,一直深度搜索,當所有領接節點都搜尋完畢之後,再搜尋下一個節點,直到圖中所有節點都搜尋完畢。
廣度優先遍歷:通過一個queue佇列,來儲存圖的某個節點,然後先搜尋它領接的所有節點,然後再將新搜尋到的節點給存入佇列,不斷的進行搜尋,直到所有節點搜尋完畢。
思考:
1、首先構建有向圖,根據pair關係。
2、一般的visit只有兩個狀態,訪問完成或者未訪問。但是這裡需要一個visit = 0,第三中狀態代表正在被訪問。因為只有正在被訪問的時候,經過的路徑途中的點會被標註成0,要是此時遇到一個還沒有訪問的領節點,但是已經正在被訪問,說明其中是有環的。
3、有環的情況有分為兩種,一種是1->2->3->1,正好找到的就是環上的節點;另一種1->2->3->2,從這個節點出發,經過了一個環。
根據深度優先結果:
struct GraphNode{
int label;
vector<GraphNode*> neighbors;
GraphNode(int x):label(x){};
};
bool DFS_Graph(GraphNode* node, vector<int>& visit) {
visit[node->label] = 0;
for (auto i : node->neighbors) {
if (visit[i->label] == 0) {
return false;
}
else if (visit[i->label] == -1 && !DFS_Graph(i, visit)) {
return false;
}
}
visit[node->label] = 1;
return true;
}
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
//1構圖
//GraphNode* Graph[numCourses]; /*表示式必須含有常量值*/
vector<GraphNode*> Graph;
//vector<int> visit(5,-1); //節點的狀態訪問 -1:沒有訪問過 0:正在訪問 1 :已經訪問過了
vector<int> visit;
for (int i = 0; i < numCourses; i++) {
Graph.push_back( new GraphNode(i));
visit.push_back(-1);
}
for (auto i : prerequisites) {
//auto begin = i.first;
auto begin = Graph[i.second];
auto end = Graph[i.first];
//Graph[begin]->neighbors.push_back(endNode);
begin->neighbors.push_back(end);
}
for (int i = 0; i < numCourses; i++) {
if (visit[i] == -1&&!DFS_Graph(Graph[i],visit)) {
return false;
}
}
return true;
}
};
根據廣度優先結果:
廣度優先搜尋解題根據入度,若一個節點在環上,那麼它的入度肯定不會為0,所以先選擇入度為0的點,把他們加入到佇列之中,然後將它的所有領接點的入度都減去1,再進行把下一個入度為0的點加入佇列,繼續搜尋...如果到最後圖中所有點的入度都為0,則不存在環,否則就存在環。
struct GraphNode{
int label;
vector<GraphNode*> neighbors;
GraphNode(int x ):label(x){};
};
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
//1構圖
vector<GraphNode*> Graph;
vector<int > indegree;
for(int i = 0; i <numCourses;i++){
Graph.push_back(new GraphNode(i));
indegree.push_back(0);
}
//確定節點直接領接關係
for(auto i : prerequisites){
auto end = i.first;
auto begin = i.second;
Graph[begin]->neighbors.push_back( Graph[end]);
indegree[end]++;
}
//把入度為0的節點傳入佇列Q之中
queue<GraphNode*> Q;
for(int i = 0; i <indegree.size();i++){
if(indegree[i]==0){
Q.push(Graph[i]);
}
}
while(!Q.empty()){
auto temp = Q.front();
Q.pop();
//寫成了temp,就顯示“基於此範圍的for語句需要begin函式”,因為不是迭代器,temp 是GraphNode*的指標
//應該是他的領接點temp->neighbors,整好也是vector,有begin和end迭代器
for(auto i : temp->neighbors){
indegree[i->label]--;
if(indegree[i->label]==0){
Q.push(i);
}
}
}
for(auto i : indegree){
if(i){
return false;
}
}
return true;
}
};