import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;

public class CourseSchedule {
    // 方法一:BFS
    public boolean canFinish1(int numCourses, int[][] prerequisites){
        // 定义数组保存所有节点入度
        int[] inDegrees = new int[numCourses];

        // 定义HashMap存储邻接矩阵
        HashMap<Integer, ArrayList<Integer>> followUpCourses = new HashMap<>();

        // 1. 遍历先决条件,计算入度和后续节点
        for (int[] prerequisite: prerequisites){
            inDegrees[prerequisite[0]] ++;    // 后修课程入度加1

            // 获取先修课程的后续节点列表
            ArrayList<Integer> followUpCourseList = followUpCourses.getOrDefault(prerequisite[1], new ArrayList<>());
            followUpCourseList.add(prerequisite[0]);    // 后修课程加入列表
            followUpCourses.put(prerequisite[1], followUpCourseList);
        }

        // 定义队列保存当前可以学习的课程,入度为0的课程
        LinkedList<Integer> selectableCourses = new LinkedList<>();

        // 2. 启动BFS,将入度为0的所有课程入队
        for (int i = 0; i < numCourses; i++){
            if (inDegrees[i] == 0)
                selectableCourses.offer(i);
        }

        // 用一个变量记录已学过的课程数量
        int finishedCoursesNum = 0;

        // 3. 不停地出队(学习课程),将后续课程入度减1,并将新的入度为0的课程入队
        while (!selectableCourses.isEmpty()){
            int course = selectableCourses.poll();    // 出队
            finishedCoursesNum ++;

            // 遍历当前课程的后续课程,入度减1
            for (int followUpCourse: followUpCourses.getOrDefault(course, new ArrayList<>())){
                inDegrees[followUpCourse] --;
                // 如果当前后续课程入度减成1,入队
                if (inDegrees[followUpCourse] == 0)
                    selectableCourses.offer(followUpCourse);
            }
        }

        // 4. 判断是否学完所有课程
        return finishedCoursesNum == numCourses;
    }

    // 方法二:DFS
    public boolean canFinish(int numCourses, int[][] prerequisites){
        // 定义HashMap存储邻接矩阵
        HashMap<Integer, ArrayList<Integer>> followUpCourses = new HashMap<>();
        // 1. 遍历先决条件,计算后续节点
        for (int[] prerequisite: prerequisites){
            // 获取先修课程的后续节点列表
            ArrayList<Integer> followUpCourseList = followUpCourses.getOrDefault(prerequisite[1], new ArrayList<>());
            followUpCourseList.add(prerequisite[0]);    // 后修课程加入列表
            followUpCourses.put(prerequisite[1], followUpCourseList);
        }

        // 定义一个栈,优先搜索最后要学习的课程
        Stack<Integer> lastCourses = new Stack<>();

        // 定义一个数组,保存课程是否在当前搜索路径上出现过
        boolean[] isSearched = new boolean[numCourses];

        boolean canFinish = true;

        // 2. 遍历每一个节点进行DFS
        for (int i = 0; i < numCourses && canFinish; i ++){
            if (!lastCourses.contains(i)){
                // 不在栈内就搜索,用一个递归方法返回当前路径是否有效
                canFinish = dfs(followUpCourses, lastCourses, isSearched, i);
            }
        }
        return canFinish;
    }

    // 实现辅助DFS方法
    public boolean dfs(HashMap<Integer, ArrayList<Integer>> followUpCourses, Stack<Integer> lastCourses, boolean[] isSearched, int i){
        // 当前节点在路径中出现
        isSearched[i] = true;

        // 遍历所有后续课程,递归调用做深度搜索
        for (int followUpCoures: followUpCourses.getOrDefault(i, new ArrayList<>())){
            if (isSearched[followUpCoures])
                return false;
            else {
                if(!dfs(followUpCourses, lastCourses, isSearched, followUpCoures))
                    return false;
            }
        }

        // 后续节点处理完毕,当前节点入栈
        lastCourses.push(i);

        // 状态回溯
        isSearched[i] = false;

        return true;
    }


    public static void main(String[] args) {
        int[][] prerequisites = {{1, 0}};
        int[][] prerequisites2 = {{1, 0}, {0, 1}};

        CourseSchedule courseSchedule = new CourseSchedule();

        System.out.println(courseSchedule.canFinish(2, prerequisites));
        System.out.println(courseSchedule.canFinish(2, prerequisites2));
    }
}