五、二叉树与图(小象)

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;
    }
};

猜你喜欢

转载自blog.csdn.net/qq_34269988/article/details/84675753