分门别类刷leetcode——二叉树和图(C++实现)

本文详细介绍了二叉树的基础知识及经典LeetCode题目解析,包括路径总和II、最近公共祖先等,并深入探讨了图的相关概念及其遍历算法,如深度优先搜索和广度优先搜索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

二叉树的各种基础知识

Leetcode  113 路径总和 II

Leetcode  236 二叉树的最近公共祖先

leetcode  114 二叉树展开为链表

LeetCode 199

复习层序遍历

leetcode 103二叉树的锯齿形层次遍历

复习图

邻接矩阵——常常用于表示稠密图

邻接表——常用于表示稀疏图——用链表实现

图的深度优先遍历

图的广度优先搜索

leetcode 207 课程表——考察判断图是否有环


二叉树的各种基础知识

二叉树的构建、递归和非递归版本的三种遍历、层序遍历

 

 

Leetcode  113 路径总和 II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1

返回:

[
   [5,4,11,2],
   [5,8,4,5]
]

思路:

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<vector<int>> result;
        vector<int> path;
        int curSum = 0;        
        pathSum(root, sum, curSum, path, result);       
        return result;
    }
    
    void pathSum(TreeNode* root, int sum, int curSum, vector<int>& path, 
                vector<vector<int>>& result) {
        if (!root)  return;
        
        curSum += root->val;
        path.push_back(root->val);
        if (curSum == sum && !root->left && !root->right) {
            result.push_back(path);
        }
        
        pathSum(root->left, sum, curSum, path, result);
        pathSum(root->right, sum, curSum, path, result);
        
        path.pop_back();
    }
};

 

 

 

Leetcode  236 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。

 

思路

1、先获取两个节点的路径

2、求较短路径的长度

3、同时遍历两个节点的路径,遍历n个节点,最后一个相同节点,即最近公共祖先。

class Solution {
public:
    //获取根节点到某个节点的路径
    void searchpPath(TreeNode *node, TreeNode *search, vector<TreeNode*>&path, 
              vector<TreeNode*>&result, int &finish){
        if(!node || finish) return;
        path.push_back(node);
        if(node==search){
            finish=1;
            result=path;
        }
        searchpPath(node->left, search, path, result, finish);
        searchpPath(node->right, search, path, result, finish);
        path.pop_back();
    }
    
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {       
        int finish=0;
        vector<TreeNode*>result_p;
        vector<TreeNode*>result_q;
        vector<TreeNode*>path;
        searchpPath(root, p, path, result_p, finish);
        path.clear();
        finish=0;
        searchpPath(root, q, path, result_q, finish);
        
        //找到第一个两个节点相异的值,前一个即为公共祖先
        int i=0;
        int len=result_p.size()<result_q.size()?result_p.size():result_q.size();
        for( i=0; i<len; i++){
            if(result_p[i]!=result_q[i]){
                return result_p[i-1];
            }
        }
        
        if(len=result_p.size()){
            return result_p[i-1];
        }else{
            return result_q[i-1];
        }
        
    }
};

 

 

 

leetcode  114 二叉树展开为链表

给定一个二叉树,原地将它展开为链表。

例如,给定二叉树

    1
   / \
  2   5
 / \   \
3   4   6

将其展开为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

方法一:先序遍历,把遍历的每个结果存入数组,然后将数组中的每个元素串成一个单链表(目测面试官不满意这个方法)

class Solution {
public:
    void helper(TreeNode* root,vector<TreeNode*>&con){
        if(root==nullptr) return;
        con.push_back(root);
        helper(root->left, con);
        helper(root->right,con);
    }
    
    void flatten(TreeNode* root) {
        vector<TreeNode*>con;
        helper(root,con);
        for(int i=1; i<con.size(); i++){            
            con[i-1]->left=nullptr;
            con[i-1]->right=con[i];          
        }
    }
};

 

 

方法二:

递归的使用先序遍历,先备份当前节点的左子节点和右子节点,然后将左子树置为空。

class Solution {
public:
    void helper(TreeNode* node, TreeNode *&last){
        if(node==nullptr) return;
        if(!node->left && !node->right){
            //叶节点,他就是以root为根节点的先序遍历的最后一个节点
            last=node;
            return;
        }
        
        TreeNode*left=node->left;
        TreeNode*right=node->right;
        TreeNode* left_last=nullptr;
        TreeNode*right_last=nullptr;
        //若有左子树,递归将左子树转换成单链表,左指针赋空
        if(left){
            helper(left, left_last);
            node->left=nullptr;
            node->right=left;
            //保存左子树的先序遍历的最后一个节点
            last=left_last;
        }
        if(right){
            helper(right, right_last);
            if(left_last){
                left_last->right=right;
            }
            last=right_last;
        }
    }
    
    void flatten(TreeNode* root) {
        TreeNode *last=nullptr;
        helper(root, last);
    }
};

 

方法三:循环

class Solution {
public:
    void flatten(TreeNode *root) {
        TreeNode*now = root;
        while (now){
            if(now->left){
                //Find current node's prenode that links to current node's right subtree
                TreeNode* pre = now->left;
                while(pre->right) {
                    pre = pre->right;
                }
                pre->right = now->right;
                //Use current node's left subtree to replace its right subtree(original right 
                //subtree is already linked by current node's prenode
                now->right = now->left;
                now->left = NULL;
            }
            now = now->right;
        }
    }
};

 

 

 

LeetCode 199

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---

 

考点:层序遍历,深度优先搜索

思路:

  • 层序遍历:每层最后入队的那个值就是要输出的值;

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int>view;
        queue<pair<TreeNode*,int>>Q;
        if(root){
            Q.push(make_pair(root,0));
        }
        
        while(Q.size()){
            TreeNode*node=Q.front().first;
            int depth=Q.front().second;
            Q.pop();
            
            //保证有几层,view.size就有多大
            if(view.size()==depth){
                view.push_back(node->val);
            }else{
                //不断修改存储的值,最后一次修改时,保存的就是这一层最后一个节点的值了
                view[depth]=node->val;
            }
            
            if(node->left){
                Q.push(make_pair(node->left, depth+1));
            }
            if(node->right){
                Q.push(make_pair(node->right, depth+1));
            }
        }
        return view;
    }
};

 

 

 

复习层序遍历

层序遍历的主要思路就是先把根节点存入,然后输出,输出的同时把根节点的左右孩子存入,再把左孩子输出,同样输出的同时把左孩子的孩子在存入,直到遍历完成,例如:

       a

   b       c

d   e   f   g   

先把a存入,然后输出a,输出a的同时把b c存入,然后再输出b,输出b的同时存入d e,继续输出c,然后存入f g,直到全部输出

二叉树层序遍历代码如下:

#include "stdafx.h"
#include<iostream>
#include<vector>
#include<queue>
using namespace std;

//定义树的基本框架
struct TreeNode {
	int val;
	TreeNode *left;
	TreeNode *right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

void answer(TreeNode* arr[]){
	queue<TreeNode*> rel; //定义一个队列,数据类型是二叉树指针,不要仅是int!!不然无法遍历
	rel.push(arr[0]);
	while (!rel.empty()){
		TreeNode* front = rel.front();
		cout << front->val << endl;
		//删除最前面的节点
		rel.pop(); 
		//判断最前面的左节点是否为空,不是则放入队列
		if (front->left != nullptr) 
			rel.push(front->left);
		//判断最前面的右节点是否为空,不是则放入队列
		if (front->right != nullptr)
			rel.push(front->right);
	}
}

int main(){	
	//构建二叉树
	TreeNode* s_arr[6];
	s_arr[0] = new TreeNode(0);
	s_arr[1] = new TreeNode(1);
	s_arr[2] = new TreeNode(2);
	s_arr[3] = new TreeNode(3);
	s_arr[4] = new TreeNode(4);
	s_arr[5] = new TreeNode(5);
	s_arr[0]->left = s_arr[1];  //   0
	s_arr[0]->right = s_arr[2]; //  1  2
	s_arr[1]->left = s_arr[3];  // 3     5
	s_arr[3]->left = s_arr[4];  //4
	s_arr[2]->right = s_arr[5]; //所以层序遍历的结果为:0 1 2 3 5 4
	
	answer(s_arr);

    return 0;
}

 

 

 

leetcode 103二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

例如:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回锯齿形层次遍历如下:

[
  [3],
  [20,9],
  [15,7]
]

思路:

思路一:对特定输入的结果进行反转输出

因为输出结果是第二层、第四层、第六层。。。的层序遍历结果反向输出,可以在输出时将对应的行反转输出即可。

//蛇形遍历(反转)
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
	vector<vector<int>> answer;
	if (root == nullptr) return answer;

	queue<TreeNode *>p;
	p.push(root);
	TreeNode* h = nullptr;

	while (!p.empty()) {
		int size = p.size();
		vector<int> temp;
		while (size--) {
			h = p.front();
			temp.push_back(h->val);
			p.pop();
			//子树非空,则压入队列   
			if (h->left != NULL)
				p.push(h->left);
			if (h->right != NULL)
				p.push(h->right);
		}
		answer.push_back(temp);
	}

	for (int i = 1; i < answer.size(); i += 2) {
		reverse(answer[i].begin(), answer[i].end());
	}

	return answer;
}

 

思路二:利用双端队列

//蛇形遍历(双端队列)
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
	vector<vector<int>> answer;
	if (root == nullptr) return answer;

	deque<TreeNode*> deq;
	deq.push_back(root);
	int flag = 0;
	int size = 1;
	TreeNode *node = nullptr;

	while (!deq.empty()) {
		vector<int> vec;
		size = deq.size();
		for (int i = 0; i < size; i++) {
			node = deq.front();
			deq.pop_front();
			//从左到右      
			if (flag % 2 == 0) {
				vec.push_back(node->val);
			}
			else {
				vec.insert(vec.begin(), node->val);
			}
			if (node->left != NULL)
				deq.push_back(node->left);
			if (node->right != NULL)
				deq.push_back(node->right);
		}
		answer.push_back(vec);
		flag++;
	}
	return answer;
}

 

研究了一下双端队列,后来发现改成队列就可以了。

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {  
    vector<vector<int>> answer;  
    if (root == nullptr) return answer;  
  
    queue<TreeNode*> deq;  
    deq.push(root);  
    int flag = 0;  
    int size = 1;  
    TreeNode *node = nullptr;  
  
    while (!deq.empty()) {  
        vector<int> vec;  
        size = deq.size();  
        for (int i = 0; i < size; i++) {  
            node = deq.front();  
            deq.pop();  
            //从左到右        
            if (flag % 2 == 0) {  
                vec.push_back(node->val);  
            }  
            else {  
                vec.insert(vec.begin(), node->val);  
            }  
            if (node->left != NULL)  
                deq.push(node->left);  
            if (node->right != NULL)  
                deq.push(node->right);  
        }  
        answer.push_back(vec);  
        flag++;  
    }  
    return answer;  
}  
};

 

 


复习图

 

 

邻接矩阵——常常用于表示稠密图

邻接矩阵的代码实现:

#include<stdio.h>

int main(int argc, char *argv[]) {
	const int MAX_N = 5;
	//初始化二维数组
	int Graph[MAX_N][MAX_N] = { 0 };
	//指0->2的这条路径是连通的
	Graph[0][2] = 1;
	Graph[0][4] = 1;
	Graph[1][0] = 1;
	Graph[1][2] = 1;
	Graph[2][3] = 1;
	Graph[3][4] = 1;
	Graph[4][3] = 1;
	printf("Graph:\n");
	for (int i = 0; i < MAX_N; i++) {
		for (int j = 0; j < MAX_N; j++) {
			printf("%d", Graph[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 

 

邻接表——常用于表示稀疏图——用链表实现

无向图示例

有向图示例

邻接表的实现

#include<stdio.h>
#include<vector>

struct GraphNode {
	int label;
	std::vector<GraphNode*>neighbors;
	GraphNode(int x) :label(x) {};
};

int main(int argc, char *argv[]) {
	const int MAX_N = 5;
	GraphNode *Graph[MAX_N];
	
	for (int i = 0; i < MAX_N; i++) {
		Graph[i] = new GraphNode(i);
	}

	Graph[0]->neighbors.push_back(Graph[2]);
	Graph[0]->neighbors.push_back(Graph[4]);
	Graph[1]->neighbors.push_back(Graph[0]);
	Graph[1]->neighbors.push_back(Graph[2]);
	Graph[2]->neighbors.push_back(Graph[3]);
	Graph[3]->neighbors.push_back(Graph[4]);
	Graph[4]->neighbors.push_back(Graph[3]);

	printf("Graph:\n");
	for (int i = 0; i < MAX_N; i++) {
		printf("Label(%d):", i);
		for (int j = 0; j < Graph[i]->neighbors.size(); j++) {
			printf("%d", Graph[i]->neighbors[j]->label);
		}
		printf("\n");
	}
	for (int i = 0; i < MAX_N; i++) {
		delete Graph[i];
	}
	return 0;
}

 

 

图的深度优先遍历

实现代码:

#include<stdio.h>
#include<vector>

struct GraphNode {
	int label;
	std::vector<GraphNode*>neighbors;
	GraphNode(int x) :label(x) {};
};

//递归调用深度优先遍历函数DFS_graph
void DFS_graph(GraphNode* node, int visit[]) {
	visit[node->label] = 1;
	printf("%d", node->label);
	for (int i = 0; i < node->neighbors.size(); i++) {
		//如果该临解点并未被访问过
		if (visit[node->neighbors[i]->label] == 0) {
			//进行深度优先遍历
			DFS_graph(node->neighbors[i], visit);
		}
	}
}

int main(int argc, char *argv[]) {
	const int MAX_N = 5;
	GraphNode *Graph[MAX_N];
	
	for (int i = 0; i < MAX_N; i++) {
		Graph[i] = new GraphNode(i);
	}

	Graph[0]->neighbors.push_back(Graph[4]);
	Graph[0]->neighbors.push_back(Graph[2]);
	Graph[1]->neighbors.push_back(Graph[0]);
	Graph[1]->neighbors.push_back(Graph[2]);
	Graph[2]->neighbors.push_back(Graph[3]);
	Graph[3]->neighbors.push_back(Graph[4]);
	Graph[4]->neighbors.push_back(Graph[3]);

	//标记已访问的顶点
	int visit[MAX_N] = { 0 };

	for (int i = 0; i < MAX_N; i++) {
		if (visit[i] == 0) {
			printf("From label (%d):", Graph[i]->label);
			DFS_graph(Graph[i], visit);
			printf("\n");
		}		
	}
	for (int i = 0; i < MAX_N; i++) {
		delete Graph[i];
	}
	return 0;
}

 

 

 

图的广度优先搜索

实现代码:

#include<stdio.h>
#include<queue>
#include<vector>

struct GraphNode {
	int label;
	std::vector<GraphNode*>neighbors;
	GraphNode(int x) :label(x) {};
};

//广度优先遍历
void BFS_graph(GraphNode* node, int visit[]) {
	std::queue<GraphNode*>Q;
	Q.push(node);
	visit[node->label] = 1;
	while (!Q.empty()) {
		GraphNode *node = Q.front();
		Q.pop();
		printf("%d", node->label);
		//将当前访问的节点的每一个可到达的且为被访问过的点依次压入队列中
		for (int i = 0; i < node->neighbors.size(); i++) {
			if (visit[node->neighbors[i]->label] == 0) {
				Q.push(node->neighbors[i]);
				visit[node->neighbors[i]->label] = 1;
			}
		}
	}
}

int main(int argc, char *argv[]) {
	const int MAX_N = 5;
	GraphNode *Graph[MAX_N];
	
	for (int i = 0; i < MAX_N; i++) {
		Graph[i] = new GraphNode(i);
	}

	Graph[0]->neighbors.push_back(Graph[4]);
	Graph[0]->neighbors.push_back(Graph[2]);
	Graph[1]->neighbors.push_back(Graph[0]);
	Graph[1]->neighbors.push_back(Graph[2]);
	Graph[2]->neighbors.push_back(Graph[3]);
	Graph[3]->neighbors.push_back(Graph[4]);
	Graph[4]->neighbors.push_back(Graph[3]);

	//标记已访问的顶点
	int visit[MAX_N] = { 0 };

	for (int i = 0; i < MAX_N; i++) {
		if (visit[i] == 0) {
			printf("From label (%d):", Graph[i]->label);
			BFS_graph(Graph[i], visit);
			printf("\n");
		}		
	}
	for (int i = 0; i < MAX_N; i++) {
		delete Graph[i];
	}
	return 0;
}

 

 

 

leetcode 207 课程表——考察判断图是否有环

现在你总共有 n 门课需要选,记为 0 到 n-1

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

说明:

  1. 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法
  2. 你可以假定输入的先决条件中没有重复的边。

提示:

  1. 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
  2. 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
  3. 拓扑排序也可以通过 BFS 完成。

思路:

 

方法一:深度优先搜索

我们将节点设置为三种状态:

  • -1表示没被访问过
  • 0代表正在访问
  • 1代表已经完成访问

然后采用深度优先搜索遍历图的节点。如果访问某个节点时,发现该节点处于被访问的状态,说明该节点被重复访问,因此说明图有环。

struct GraphNode {
	int label;
	std::vector<GraphNode*>neighbors;
	GraphNode(int x) :label(x) {};
};

class Solution {
public:
    //-1表示没被访问过, 0代表正在访问, 1代表已经完成访问
    bool DFS_graph(GraphNode* node, vector<int>&visit) {
        //正在访问node,将其visit值设置为0
        visit[node->label] = 0;
        for (int i = 0; i < node->neighbors.size(); i++) {
            //如果该临解点并未被访问过
            if (visit[node->neighbors[i]->label] == -1) {
                //将函数的返回值——false    递归的往回抛
                if(DFS_graph(node->neighbors[i], visit)==0){
                    return false;
                }
            }
            //访问到了正在被访问的节点
            else if(visit[node->neighbors[i]->label]==0){
                return false;               
            }
        }
        visit[node->label]=1;
        return true;
    }
    
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<GraphNode*>graph;
        vector<int>visit;
        //初始化邻接表,并将访问状态设置为-1
        for(int i=0; i<numCourses; i++){
            graph.push_back(new GraphNode(i));
            visit.push_back(-1);
        }
        //创建图,链接图的顶点
        for(int i=0; i<prerequisites.size(); i++){
            GraphNode *begin=graph[prerequisites[i].second];
            GraphNode *end=graph[prerequisites[i].first];
            //使得课程2指向课程1
            begin->neighbors.push_back(end);
        }
        for(int i=0; i<graph.size(); i++){
            //有环
            if(visit[i]==-1 && !DFS_graph(graph[i], visit)){
                return false;
            }
        }
        //释放内存
        for(int i=0; i<numCourses; i++){
            delete graph[i];
        }
        return true;
    }
};

 

方法二——拓扑排序(宽度搜索)

struct GraphNode {
	int label;
	std::vector<GraphNode*>neighbors;
	GraphNode(int x) :label(x) {};
};

class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<GraphNode*>graph;
        //存储入度
        vector<int>degree;
        queue<GraphNode*>Q;
        
        //初始化邻接表,初始化入度的值为0
        for(int i=0; i<numCourses; i++){
            graph.push_back(new GraphNode(i));
            degree.push_back(0);
        }
        //创建图,链接图的顶点
        for(int i=0; i<prerequisites.size(); i++){
            GraphNode *begin=graph[prerequisites[i].second];
            GraphNode *end=graph[prerequisites[i].first];
            //使得课程2指向课程1
            begin->neighbors.push_back(end);
            //课程1的入度++
            degree[prerequisites[i].first]++;
        }

        //初始化队列中的元素
        for(int i=0; i<numCourses; i++){
            if(degree[i]==0){
                Q.push(graph[i]);
            }
        }
        
        //宽搜
        while(!Q.empty()){
            GraphNode* node=Q.front();
            Q.pop();
            //把队列中元素所指的每个节点的入度减1
            for(int i=0; i<node->neighbors.size(); i++){
                degree[node->neighbors[i]->label]--;
                //将入度为0的节点加入队列中
                if(degree[node->neighbors[i]->label]==0){
                    Q.push(node->neighbors[i]);
                }
            }
        }
               
        //释放内存
        for(int i=0; i<numCourses; i++){
            delete graph[i];
        }

        //存在入度不为0的节点,说明有环
        for(int i=0; i<numCourses; i++){
            if(degree[i]){
                return false;
            }
        }
               
        return true;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值