207. Course Schedule
207. Course Schedule
문제
There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.
For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. Return true if you can finish all courses. Otherwise, return false.
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]] Output: true Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.
Example 2:
Input: numCourses = 2, prerequisites = [[1,0],[0,1]] Output: false Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
Constraints:
- 1 <= numCourses <= 2000
- 0 <= prerequisites.length <= 5000
- prerequisites[i].length == 2
- 0 <= ai, bi < numCourses
- All the pairs prerequisites[i] are unique.
문제 해석
- 전형적인 위상 정렬 중 선수 과목에 관한 문제이다.
- 이 같은 위상 정렬 문제의 핵심은 Example 2 에서 볼 수 있는 사이클 이 존재하면 위상 정렬을 할 수 없다 라는 지식이 있어야한다.
구현(BFS)
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> degree(numCourses, 0);
vector<vector<int>> adj(numCourses, vector<int>());
for(auto& p : prerequisites)
{
adj[p[1]].push_back(p[0]);
degree[p[0]]++;
}
queue<int> q;
for(int i = 0; i < numCourses; i++)
{
if(degree[i] == 0)
{
q.push(i);
}
}
while(!q.empty())
{
int curr = q.front();
q.pop();
numCourses--;
for(auto next : adj[curr])
{
if(--degree[next] == 0)
{
q.push(next);
}
}
}
return numCourses == 0;
}
};
코드 해설
- 위상 정렬 문제를 풀 때는 여러 방법이 있지만 Kahn 알고리즘 방식을 알면 쉽게 해결이 가능하다.
- Kahn 알고리즘 은 위상 정렬 문제에 쓰이는 알고리즘으로 진입 차수(degree) 와 BFS(큐) 방식을 사용하는 알고리즘 이다.
vector<int> degree(numCourses, 0);
vector<vector<int>> adj(numCourses, vector<int>());
// ex) adj = [ [], [] ] 이렇게 생겼다.
- degree는 진입 차수를 저장하기 위한 벡터로 numCourses(들어야 하는 과목) 만큼 만들어둔다.
- adj 는 과목마다 연결되어있는 노드를 저장하기 위한 벡터로 numsCourses 만큼 만들어두고 각 칸마다 vector
() 로 초기화한다.(**이는 위상정렬에서 노드를 만들때 자주 사용하는 방식이므로 꼭 익혀두자**)
for(auto p : prerequisites)
{
adj[p[1]].push_back(p[0]);
degree[p[0]]++;
}
- p[1]은 먼저 들어야하는 과목 이고 p[0] 은 p[1] 을 들어야 수강할 수 있는 과목 이다.
- Example 1) prerequisites = [ [1, 0] ] 이다. ‘0’ 과목을 들어야 ‘1’ 과목을 들을 수 있기 때문에 ‘0’ -> ‘1’ 이런 방향 그래프로 나타낼 수 있다.
- ‘0’은 선수 과목없이 바로 들을 수 있는 과목이므로 진입 차수는 ‘0’ 이라고 할 수 있고 ‘1’은 ‘0’의 선수 과목이 있기 때문에 진입 차수는 ‘1’ 이라고 할 수 있다.
queue<int> q;
for(int i = 0; i < numCourses; i++)
{
if(degree[i] == 0)
{
q.push(i);
}
}
- BFS 방식으로 풀 것이기 때문에 큐를 선언해준다.
- 진입 차수들이 저장되어 있는 degree에서 언제든 들을 수 있는 진입 차수 0 들을 q에 먼저 넣어준다.
for(auto next : adj[curr])
{
if(--degree[next] == 0)
{
q.push(next);
}
}
- adj[curr] 은 진입차수가 0인 과목의 선수 과목인(진입차수가 ‘1’ 이상) 다음 과목들이 들어있다.
- 전 과목은 수강했으므로 이제 연관된 다음 선수 과목의 진입차수는 당연히 ‘1’이 감소할 것이다.
- 만약 감소한 진입 차수가 0 이라면 다음으로 들어야할 과목이므로 이를 큐에 넣어 다시 똑같이 반복하면 된다.
return numCourses == 0;
- 위상 정렬의 핵심은 사이클이 존재해서는 안된다 라는 것이다.
- 만약 큐가 비게된다면 사이클이 존재하는 것이므로 numCourses 는 0이 될 수 없을 것이다.