简单做个刷题记录,涉及到的图的基础知识:邻接表的建立,入度、出度,bfs,dfs
不邻接植花
https://leetcode-cn.com/problems/flower-planting-with-no-adjacent/solution/
思路:本题类似于着色问题,花的种类可以看成颜色的种类。因此建立无向图邻接表vector<vector> Graph(N)存储花园之间的路径,遍历每一个花园并着色,只要保证花园邻接的花园颜色不相同即可。每次遍历时,利用dir{1,2,3,4}存储当前可用的颜色,并查看其邻接点颜色,相同颜色则去除。最后使得路径相连的任何两个花园的颜色,即题目中的花的种类互不相同。
class Solution {
public:
vector<int> gardenNoAdj(int N, vector<vector<int>>& paths) {
//建立无向图的邻接表
vector<vector<int>> Graph(N);
for(int i = 0;i<paths.size(); i++){
Graph[paths[i][0]-1].push_back(paths[i][1]-1);
Graph[paths[i][1]-1].push_back(paths[i][0]-1);
}
vector<int> answer(N,0);//初始化,0代表未染色
for(int i = 0;i<N; i++){
set<int> dir{1,2,3,4};//存放颜色
//通过set的erase用法保证路径相连的花园中的花的种类不同
for(int j = 0;j<Graph[i].size();j++){
dir.erase(answer[Graph[i][j]]);//相同颜色则去除掉
}
answer[i]=*(dir.begin());
}
return answer;
}
};
找到小镇的法官
https://leetcode-cn.com/problems/find-the-town-judge/
思路:把小镇上的人看成图的一个个顶点,法官就是满足入度为N-1,出度为0的点。因此,用一个degree存储每一个顶点的入度和出度的差值。遍历trust数组,每次入度记为-1,出度记作+1,最后遍历degree数组,元素的值为N-1即为所求的法官,否则不存在法官满足题意。
class Solution {
public:
int findJudge(int N, vector<vector<int>>& trust) {
vector<int> degree(N+1,0);
for(int i = 0;i<trust.size();i++){
degree[trust[i][0]]--;//出度-1
degree[trust[i][1]]++;//入度+1
}
for(int i =1;i<=N;i++){
if(degree[i]==N-1)
return i;
}
return -1;
}
};
所有可能的路径
https://leetcode-cn.com/problems/all-paths-from-source-to-target/
思路:典型的dfs模板题。从起点0开始,对图进行深度优先遍历,用path记录每次尝试的路径经过的节点,当有一条路径可以抵达终点N-1时,把该路径加入res数组中
class Solution {
public:
vector<vector<int>>res;
vector<int> path;
int len;
void dfs(int x,vector<vector<int>>& graph,int visited[]){
visited[x] = 1;//标记该点已经在路径中了
path.push_back(x);
if(x==len-1){//判断是否到达目标点
res.push_back(path);
}else{
int size = graph[x].size();
for(int i = 0;i<size;i++){
dfs(graph[x][i],graph,visited);
}
}
path.pop_back();
visited[x] = 0;//之前的路径探索完毕之后,取消对该节点的标记
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
len = graph.size();
int visited[16]={0};
dfs(0,graph,visited);
return res;
}
};
克隆图
https://leetcode-cn.com/problems/clone-graph/
思路:采用BFS方式对图进行遍历,unoredered_map<Node*,Nodes*> visited是避免重复访问子节点。visited的key 是原来图中的节点,value 是克隆图中的对应节点,即A->A1,B->B1,C->C1,D->D1……,如果某个节点已经被访问过,则直接将该节点加入到邻居列表中,如果没有被访问过,则构造新的节点对它的邻居列表循环执行访问操作。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> neighbors;
Node() {
val = 0;
neighbors = vector<Node*>();
}
Node(int _val) {
val = _val;
neighbors = vector<Node*>();
}
Node(int _val, vector<Node*> _neighbors) {
val = _val;
neighbors = _neighbors;
}
};
*/
class Solution {
public:
void dfs(Node* node,vector<Node *>&neigh,unordered_map<Node*, Node*>&visited){
if(neigh.empty())
return;
for(int i = 0;i<neigh.size();i++){
if(!visited.count(neigh[i])){
//该节点没有被克隆过,构造新的节点
Node* pNew = new Node(neigh[i]->val,vector<Node*>{});
visited[neigh[i]] = pNew;
node->neighbors.push_back(pNew);
dfs(pNew,neigh[i]->neighbors,visited);
}else{
//如果该节点已经被访问过,则把它加到邻居列表中
node->neighbors.push_back(visited[neigh[i]]);
}
}
}
Node* cloneGraph(Node* node){
if(node==NULL){
return nullptr;
}
unordered_map<Node*, Node*>visited;
Node *copy = new Node(node->val);
visited[node] = copy;
dfs(copy,node->neighbors,visited);
return copy;
}
};
找到最终的安全状态
https://leetcode-cn.com/problems/find-eventual-safe-states/
思路:从题意可知,不安全的点绝对成环,因此利用深度搜索判断哪些点不在环上,即为所求的点。定义节点的访问状态:用0表示未访问过,1表示访问过但不确定是否是环上的点,2表示安全的点。每次访问将节点标记为1,如果在搜索过程中我们遇到节点的状态也为1(表示已经被访问过了),那么说明找到了一个环,此时退出搜索,如果搜索过程中,我们没有遇到成环的点,那么标记该点为安全的点。
class Solution {
public:
//用0表示未访问过,1表示访问过但不确定是否是环上的点,2表示安全的点
bool dfs(vector<vector<int>>& graph,vector<int>& visited,int n){
if(visited[n]!=0){
return visited[n]==2;
}
visited[n] = 1;
for(int i=0;i<graph[n].size();i++){
if(visited[graph[n][i]]==1||dfs(graph,visited,graph[n][i])==false){
return false;
}
}
visited[n] = 2;
return true;
}
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int N = graph.size();
vector<int> visited(N,0);
vector<int> res;
for(int i = 0;i<N; i++){
if(dfs(graph,visited,i)){
res.push_back(i);
}
}
return res;
}
};
课程表
https://leetcode-cn.com/problems/course-schedule/
思路:注意题目信息,完成课程A需要先完成课程B(B->A),完成课程B需要先完成课程A(A->B),这样就形成了一个循环,因此可以联想到一种类型题:判断有向图中是否存在环。把课程表构造成一个有向图,如果课程能够全部完成,有向图中就一定没有环。通过深度优先搜索遍历图中所有的点,来查找是否有环存在。跟上面一题(找到最终的安全状态)的思路是一样的,因此把上面一题的代码改写一下就可以了~~
class Solution {
public:
bool dfs(vector<vector<int>>& graph,vector<int>& visited,int n){
if(visited[n]!=0){
return visited[n]==2;
}
visited[n] = 1;
for(int i = 0;i<graph[n].size();i++){
if(visited[graph[n][i]]==1||dfs(graph,visited,graph[n][i])==false)
return false;
}
visited[n]=2;
return true;
}
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int> >graph(numCourses,vector<int>(0));
for(int i = 0;i<prerequisites.size();i++){
//学0之前得先完成1,那么就是1->0
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
//图的标签 0-numCourse-1
vector<int> visited(numCourses,0);
for(int i = 0;i<numCourses;i++){
if(!dfs(graph,visited,i))
return false;
}
return true;
}
};
最小高度树
https://leetcode-cn.com/problems/minimum-height-trees/
思路:BFS。本题中为了使得树的高度最小,root节点应该是尽量靠近中心节点(可以理解为离所有叶子节点相对较近的点)因此如果将最外层的叶子节点去除掉,每去一次就会有新的一层叶子节点出现,这样一直去除下去直到最后一层就是叶子节点
代码实现:定义degree[n]的数组,存储每个节点的度数,每次将度数为1的节点(叶子节点)入队列,循环去掉叶子节点部分,直到不能再去除为止
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if(n==0)
return {};
else if(n==1)
return {0};
else if(n==1)
return {0,1};
vector<int> graph[n];
int degree[n];
memset(degree,0x00,sizeof(int)*n);
for(int i = 0;i<edges.size();i++){
graph[edges[i][0]].push_back(edges[i][1]);
graph[edges[i][1]].push_back(edges[i][0]);
degree[edges[i][0]]++;
degree[edges[i][1]]++;
}
queue<int> q;
for(int i = 0;i<n;i++){
if(degree[i]==1){
q.push(i);//把叶子节点添加进队列中
}
}
vector<int> res;
while(!q.empty()){
res.clear();
int len = q.size();
for(int i = 0;i<len;i++){
int node = q.front();
res.push_back(node);
q.pop();
for(int j = 0;j<graph[node].size();j++){
degree[graph[node][j]]--;//删除叶子节点后,跟其相邻的节点的度数要减少
if(degree[graph[node][j]]==1)
q.push(graph[node][j]);
}
}
}
return res;
}
};
判断二分图
https://leetcode-cn.com/problems/is-graph-bipartite/
思路:BFS。由二分图的特点可以知道两个不同的顶点集合之间存在边,而每个顶点集合内部并没有边。因此,如果用两种颜色给顶点染色,那么相同集合的顶点染成相同颜色即可。例如,先找到一个有边的顶点v0,并给v0的邻接点v1,v2染上不同的颜色时,同理,再给v1,v2的邻接点v3,v4,v5染上v0的颜色,依次循环下去。在广度优先遍历过程中,若发现邻接点已经被染色且颜色与当前点相同,那么便不是二分图。
题目中的无向图不一定是连通,因此需要多次遍历,保证每一个节点都被染色或确定不是二分图为止。
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
int len = graph.size();
vector<int> visited(len,0);
queue<int> q;
for(int i = 0;i<len;i++){
if(visited[i]!=0)//已经被访问过
continue;
visited[i]=1;
q.push(i);
while(!q.empty()){
int num = q.front();
q.pop();
int flag = visited[num];
for(int j = 0;j<graph[num].size();j++){
if(visited[graph[num][j]]==0){//没有被访问过
q.push(graph[num][j]);
visited[graph[num][j]]=-flag;
}else if(visited[graph[num][j]]==flag){
return false;
}
}
}
}
return true;
}
};