leetcode图论专题

207.课程表

在这里插入图片描述

方法一:Kahn算法(基于BFS)

思路

用map记录每门课的后续课程,创建入度表,入度为0要入队。每个元素依次出队,最后看出队数是否等于总课程数,就可以判断图是否有环,有环证明逻辑错误

复杂度

时间复杂度 O(N+M)O(N + M)O(N+M): 遍历一个图需要访问所有节点和所有临边,N 和 M 分别为节点数量和临边数量;
空间复杂度 O(N+M)O(N + M)O(N+M): 为建立邻接表所需额外空间,adjacency 长度为 N ,并存储 M 条临边的数据。

cpp

class Solution {
public:
	bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
		map<int, set<int>>map;//(邻接表):课程号,该课程的后续课程(集合表示)
		vector<int>indegree(numCourses, 0);//入度表,初始化为0
		queue<int>q;
		
		for (auto& v : prerequisites) {
			int pre = v[1];//先修课程
			int cur = v[0];//当前课程
			++indegree[cur];//当前课程入度加1
			map[pre].insert(cur);//先修课程的后续课程表加上当前课程

		}

		for (int i = 0; i < numCourses; ++i) { //入度为0的课入队
			if (!indegree[i])
				q.push(i);
		}
		int count = 0;
		while (!q.empty()) {//依次出队
			int top = q.front();
			q.pop();
			++count;
			for (auto& v : map[top]) {//该课程的后续课程入度减1
				--indegree[v];
				if (!indegree[v])
					q.push(v);			
			}
		}
		return count == numCourses;//如果全都入队且出队了,则表示无环图,true
	}
};

python3

from collections import deque
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        indegrees = [0 for _ in range(numCourses)]  # 入度表
        adjacency = [[] for _ in range(numCourses)]  # 邻接表
        queue = deque()
        # Get the indegree and adjacency of every course.
        for cur, pre in prerequisites:
            indegrees[cur] += 1  # 当前课程入度加1
            adjacency[pre].append(cur)  # 前课程的后续课程加上当前课程
        # 入度为0的入队
        for i in range(len(indegrees)):
            if not indegrees[i]: queue.append(i)
        # BFS TopSort.
        while queue:
            pre = queue.popleft()  # 出队
            numCourses -= 1
            for cur in adjacency[pre]:
                indegrees[cur] -= 1
                if not indegrees[cur]: queue.append(cur)
        return not numCourses  # 判断是否减到0

方法二:DFS

思路

在这里插入图片描述

复杂度

时间复杂度 O(N+M)O(N + M)O(N+M): 遍历一个图需要访问所有节点和所有临边,N 和 M 分别为节点数量和临边数量;
空间复杂度 O(N+M)O(N + M)O(N+M): 为建立邻接表所需额外空间,adjacency 长度为 N ,并存储 M 条临边的数据。

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        def dfs(i, adjacency, flags):
            if flags[i] == -1: return True # 被其他节点dfs访问了
            if flags[i] == 1: return False # 被自己节点dfs访问了,说明有环
            flags[i] = 1
            for j in adjacency[i]:
                if not dfs(j, adjacency, flags): return False
            flags[i] = -1
            return True

        adjacency = [[] for _ in range(numCourses)]
        flags = [0 for _ in range(numCourses)]
        for cur, pre in prerequisites:
            adjacency[pre].append(cur)
        for i in range(numCourses):
            if not dfs(i, adjacency, flags): return False
        return True

208.课程表二

在这里插入图片描述

方法一:拓扑排序Kahn算法(基于BFS)

复杂度

时间复杂度O(E+V)O(E + V)O(E+V)。这里 E 表示邻边的条数,V 表示结点的个数。初始化入度为 0 的集合需要遍历整张图,具体做法是检查每个结点和每条边,因此复杂度为O(E+V)O(E + V)O(E+V),然后对该集合进行操作,又需要遍历整张图中的每个结点和每条边,复杂度也为 O(E+V)O(E + V)O(E+V)
空间复杂度O(V)O(V)O(V):入度数组、邻接表的长度都是结点的个数 V,即使使用队列,队列最长的时候也不会超过 V,因此空间复杂度是 O(V)O(V)O(V)

class Solution {
public:
	vector<int> res;
	vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
		vector<int>indegree(numCourses, 0);//入度表
		vector<vector<int>> adj(numCourses);//邻接表
		for (auto& v : prerequisites) {
			int pre = v[1];
			int cur = v[0];
			++indegree[cur];
			adj[pre].push_back(cur);
		}
		queue<int>q;
		//入度为0的入队
		for (int i = 0; i < numCourses; ++i) {
			if (!indegree[i])
				q.push(i);
		}
		int count = 0;
		while (!q.empty()) {
			int top = q.front();
			res.push_back(top);
			q.pop();
			++count;
			for (auto& v : adj[top]) {
				--indegree[v];//邻接边的入度减1
				if (!indegree[v])
					q.push(v);
			}
		}
		vector<int>null{};
		return count == numCourses ? res : null;
		
	}
};

方法二:DFS

思路:

节点走到无路可走了才会push_back进最后的res数组(即按照出度为0进入res数组)最后要把res倒序输出

复杂度

同上Kahn算法

class Solution {
public:
    vector<int> res;
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        if(!numCourses)
            return {};
        if(!prerequisites.size()){//没有先后顺序,任意输出
            for(int i = 0;i<numCourses;++i)
                res.push_back(i);
            return res;
        }
        vector<int>flag(numCourses,0);//0:没访问 1:被自己的父节点访问了,有环直接返回false -1:被其他节点访问过了,返回true
        vector<vector<int>> adj(numCourses);//邻接表
		for (auto& v : prerequisites) {
			int pre = v[1];
			int cur = v[0];
			adj[pre].push_back(cur);
		}
        
        for(int i= 0;i<numCourses; i++){
            if(!dfs(adj,flag,i))
                return{};
        }
        reverse(res.begin(),res.end());
        return res;
    }
    //检查是否有环
    bool dfs(vector<vector<int>>& adj, vector<int> &flag, int index){
       
        if(flag[index] == -1)
            return true;
        else if(flag[index] == 1)
            return false;
        else 
            flag[index] = 1;
        for(auto&v:adj[index]){
            if(!dfs(adj, flag, v)) {
                return false;
            }         
        }
        flag[index] = -1;
        res.push_back(index);//先把 index 这个结点的所有前驱结点都输出之后,再输出自己
        return true;
        
    }
};

126.单词接龙2

在这里插入图片描述

思路:Dijkstra最短路径

const int INF = 1 << 20;

class Solution {
private:
    unordered_map<string, int> wordId;//每个单词以及他们的序号id
    vector<string> idWord;      //存放所有单词(beginWord可能不在wordList中)
    vector<vector<int>> edges;//类邻接表,存放各个顶点的邻接顶点
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        int id = 0;
        //处理wordList
        for (const string& word : wordList) {
            if (!wordId.count(word)) {
                wordId[word] = id++;
                idWord.push_back(word);
            }
        }
        //endword不在wordlist直接返回
        if (!wordId.count(endWord)) {
            return {};
        }
        //beginword不在wordlist的话需要添加
        if (!wordId.count(beginWord)) {
            wordId[beginWord] = id++;
            idWord.push_back(beginWord);
        }
        //初始化邻接表
        edges.resize(idWord.size());
        for (int i = 0; i < idWord.size(); i++) {
            for (int j = i + 1; j < idWord.size(); j++) {
                if (transformCheck(idWord[i], idWord[j])) {
                    edges[i].push_back(j);
                    edges[j].push_back(i);
                }
            }
        }
        //终点id
        const int dest = wordId[endWord];
        vector<vector<string>> res;
        queue<vector<int>> q;
        vector<int> cost(id, INF);//beginword到其他点的最短路径
        q.push(vector<int>{wordId[beginWord]});
        cost[wordId[beginWord]] = 0;//自己到自己:0
        while (!q.empty()) {
            vector<int> now = q.front();
            q.pop();
            int last = now.back();
            if (last == dest) {//终点条件
                vector<string> tmp;
                for (int index : now) {
                    tmp.push_back(idWord[index]);
                }
                res.push_back(tmp);
            } else {
                for (int i = 0; i < edges[last].size(); i++) {
                    int to = edges[last][i];
                    if (cost[last] + 1 <= cost[to]) {
                        cost[to] = cost[last] + 1;
                        vector<int> tmp(now);
                        tmp.push_back(to);
                        q.push(tmp);
                    }
                }
            }
        }
        return res;
    }
    //判断两个单词是否只有一个字母不同
    bool transformCheck(const string& str1, const string& str2) {
        int differences = 0;
        for (int i = 0; i < str1.size() && differences < 2; i++) {
            if (str1[i] != str2[i]) {
                ++differences;
            }
        }
        return differences == 1;
    }
};

复杂度

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值