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