Complete solution to the curriculum problem [implemented in pure C language]

Posted by cjconnor24 on Fri, 17 Dec 2021 07:50:18 +0100

The four questions here have different ideas:

  1. The first two are topological sorting. The concept of penetration is used to judge whether there are rings in the graph. Queue and stack are used respectively.
  2. When creating a graph, you can use dynamic capacity expansion to build a data structure similar to the adjacency table
  3. The third problem uses priority queue + greed. The time complexity of inserting elements in priority queue (heap) is O(logn)
  4. The fourth question is floyd to judge whether the two points in the graph are connected. In fact, it is the idea of dynamic programming, and k should be written outside the loop

Course Schedule II

stem

Now you have a total of numCourses to choose, which are recorded as 0 to numCourses - 1. Give you an array of prerequisites, where prerequisites[i] = [ai, bi], indicating that bi must be selected before taking the elective course ai.

For example, to learn course 0, you need to complete course 1 first. We use a match to represent: [0,1].
Return to the learning sequence you arranged to complete all courses. There may be multiple correct orders. You just need to return any one. If it is not possible to complete all courses, an empty array is returned.

Example 1:

Input: numCourses = 2, prerequisites = [[1,0]]
Output:[0,1]
Explanation: there are 2 courses in total. To learn course 1, you need to complete course 0 first. Therefore, the correct course sequence is [0,1] . 

Example 2:

Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output:[0,2,1,3]
Explanation: there are 4 courses in total. To study course 3, you should complete course 1 and course 2 first. And both course 1 and course 2 should be ranked after course 0.
Therefore, a correct course sequence is [0,1,2,3] . Another correct sort is [0,2,1,3] . 

Example 3:

Input: numCourses = 1, prerequisites = []
Output:[0]

Tips:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • All [ai, bi] are different from each other

analysis

The top node in topology sorting must not have any edges, that is, it does not have any requirements for prerequisite courses. When we add a node to the answer, we can remove all its edges, which means that its adjacent nodes lack the requirement of a prerequisite course. If an adjacent node becomes a "node without any edges", it means that the course can begin. According to this process, we continue to add nodes without edges to the answer until the answer contains all nodes (a topological sort is obtained) or there are no nodes without edges (the graph contains rings)

  • Topological sorting is an algorithm specially applied to directed graphs;
  • This problem can be completed with BFS and DFS. You only need to master the writing method of BFS. The writing method of BFS is very classic;
  • The way BFS is written is called "topological sorting", and the idea of greedy algorithm is also used here. The greedy points are: Currently, those nodes with a rank of 0 are allowed to join the queue;
  • The result of topology sorting is not unique;
  • The operation of deleting nodes is reflected through the "penetration array". This skill should be mastered;
  • An additional effect of "topological sorting" is that it can detect whether there are rings in a directed graph. This knowledge point is very important if it is encountered during the interview
  • When it comes to this problem, we should say this.
  • Algorithms with similar additional functions include:
    • The additional function of Bellman Ford algorithm is to detect whether there is a negative weight ring (it is not expanded here, and I am not familiar with it).

code

int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize){
    int n = numCourses;
    // Adjacency table
    int **edge = (int **)malloc(sizeof(int *) * n);
    for (int i = 0; i < n; i++) {
        // The initial length is 0. In this way, using the edgeColSize array and dynamic capacity expansion, we can get a data structure similar to the adjacency table
        edge[i] = (int *)malloc(0);
    }
    // Length of each line
    int edgeColSize[n];
    memset(edgeColSize, 0, sizeof(edgeColSize));
    for (int i = 0; i < n; i++)
        printf("%d ", edgeColSize[i]); 
    // Penetration
    int inDeg[n];
    memset(inDeg, 0, sizeof(inDeg));
    for (int i = 0; i < n; i++)
        printf("%d ", inDeg[i]); 
    for (int i = 0; i < prerequisitesSize; i++) {
        int a = prerequisites[i][1];
        int b = prerequisites[i][0];
        edgeColSize[a]++;// Length increased by 1
        edge[a] = (int *)realloc(edge[a], sizeof(int) * edgeColSize[a]); // Dynamic capacity expansion
        // The elements in the edge[a] array are the successor courses of A
        // edge[a][i] = b indicates that a is the leading course of b
        edge[a][edgeColSize[a] - 1] = b; 
        // Increased penetration
        inDeg[b]++;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < edgeColSize[i]; j++) {
            printf("%d ", edge[i][j]);
        }
        printf("\n");
    }
    // The queue stores all courses with a grade of 0
    int q[n];
    int left = 0, right = -1;
    for (int i = 0; i < n; i++) {
        if (inDeg[i] == 0) {
            q[++right] = i;
        }
    }
    int *result = (int *)malloc(sizeof(int) * n);
    int resultSize = 0;
    int visited = 0;
    while (left <= right) {
        // Out of line, count
        ++visited;
        int u = q[left++];
        result[resultSize++] = u;
        for (int i = 0; i < edgeColSize[u]; i++) {
            --inDeg[edge[u][i]];
            if (inDeg[edge[u][i]] == 0)
                q[++right] = edge[u][i];
        }
    }
    for (int i = 0; i < n; i++) {
        free(edge[i]);
    }
    free(edge);
    // If all of them have entered the queue, it is topological sorting, and there is no ring
    if (visited == n) {
        *returnSize = n;
    } else {
        *returnSize = 0;
    }
    return result;
}

Class Schedule Card

stem

Ibid., but the problem becomes:

Please judge whether it is possible to complete all courses? If yes, return true; Otherwise, false is returned.

The dataset becomes:

  • 1 <= numCourses <= 10^5

analysis

This means that we can only use the following data structure similar to adjacency table.

Next, I changed the queue into a stack.

code

bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize){
    int n = numCourses;
    int **graph = (int **)malloc(sizeof(int *) * n);
    for (int i = 0; i < n; i++)
        graph[i] = (int *)malloc(0);
    int *inDeg = (int *)malloc(sizeof(int) * n);
    memset(inDeg, 0, sizeof(int) * n);
    int *graphColSize = (int *)malloc(sizeof(int) * n);
    memset(graphColSize, 0, sizeof(int) * n);
    for (int i = 0; i < prerequisitesSize; i++) {
        int a = prerequisites[i][1];
        int b = prerequisites[i][0];
        graphColSize[a]++;
        graph[a] = (int *)realloc(graph[a], sizeof(int) * graphColSize[a]);
        graph[a][graphColSize[a] - 1] = b;
        inDeg[b]++;
    } 
    int stack[n];
    int top = -1;
    for (int i = 0; i < n; i++)
        if (inDeg[i] == 0)
            stack[++top] = i;
    int visited = 0;
    while (top != -1) {
        visited++;
        int x = stack[top--];
        for (int i = 0; i < graphColSize[x]; i++) {
            inDeg[graph[x][i]]--;
            if (inDeg[graph[x][i]] == 0)
                stack[++top] = graph[x][i];
        }
    }
    // If you remember, please release
    for (int i = 0; i < n; i++) {
        free(graph[i]);
    }
    free(graph);
    free(inDeg);
    free(graphColSize);
    
    printf("%d", visited);
    if (visited == n)
        return true;
    else
        return false;
}

ps

If we use deep search, because this is a directed graph, if we find ourselves, there must be a ring. We can mark it with an array.

Course schedule III

stem

There are n different online courses, numbered from 1 to n. Give you an array of courses, where courses [i] = [duration, lastDayi] means that the i-th course will last for the duration day and must be completed no later than lastDayi.

Your term begins on day 1. And cannot take two or more courses at the same time.

Returns the maximum number of courses you can take.

Example 1:

Input: courses = [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
Output: 3
 Explanation:
There are four courses here, but you can take up to three:
First, take the first course, which takes 100 days, complete it on the 100th day, and start the next course on the 101st day.
Second, take the third course, which takes 1000 days, complete it on day 1100, and start the next course on day 1101.
Third, take the second course, which takes 200 days and is completed on day 1300.
Course 4 cannot be taken now because it will be completed on day 3300, which is beyond the closing date.

Example 2:

Input: courses = [[1,2]]
Output: 1

Example 3:

Input: courses = [[3,2],[4,3]]
Output: 0

Tips:

  • 1 <= courses.length <= 10^4
  • 1 <= durationi, lastDayi <= 10^4

analysis

Priority queue + greed

For two courses (t_1, d_1) and (t_2, d_2), if the latter closes later, i.e. D_ 1 <= d_ 2. It is always optimal for us to learn the former first and then the latter.

Therefore, we can sort all the courses in ascending order according to the closing time d, and then select the courses in turn and study in order.

algorithm

code

// The return value of this place is written as bool. debug has been looking for a long time
int comp(const void *a, const void *b)
{
    int *a1 = *(int**)a;
    int *b1 = *(int**)b;
	// ddl is equal, and it takes a short time to do it first
    if (a1[1] == b1[1]) {
        return a1[0] - b1[0];
    }
    // ddl near priority
    return a1[1] - b1[1];
}
// Maximum heap
int *nums;
int heapSize;
// Insert element
void push(int x)
{
    int i = heapSize;
    // Insert at the end and move up
    nums[heapSize++] = x;
    bool judge = false;
    while (i > 0 && !judge) {
        if (nums[i] > nums[(i - 1) / 2]) {
            int temp = nums[i];
            nums[i] = nums[(i - 1) / 2];
            nums[(i - 1) / 2] = temp;
        } else {
            judge = true;
        }
        i = (i - 1) / 2;
    }
}
// Delete the maximum value and insert a value (we will replace the maximum value with the inserted value, and then move down
void replace(int x)
{
    nums[0] = x;
    bool judge = false;
    int i = 0;
    while (!judge) {
        int left = 2 * i + 1, right = 2 * i + 2;
        if (left >= heapSize)   return;
        int key = nums[left] > nums[right] ? left : right;
        if (nums[i] < nums[key]) {
            int temp = nums[i];
            nums[i] = nums[key];
            nums[key] = temp;
        } else {
            judge = true;
        }
        i = key;
    }
}
int scheduleCourse(int** courses, int coursesSize, int* coursesColSize){
    // Come on, ddl, the closer you get, the better
    qsort(courses, coursesSize, sizeof(int *), comp);
    // Priority queue
    nums = (int *)malloc(sizeof(int) * coursesSize);
    heapSize = 0;
    // Required class hours
    int total = 0;
    for (int i = 0; i < coursesSize; i++) {
        int ti = courses[i][0], di = courses[i][1];
        // If this class can be taken directly
        if (total + ti <= di) {
            total += ti;
            push(ti);
        } 
        // Total + Ti > Di, indicating that there is no way to take another course now
        // If num [0] (the longest class in the class) > ti, we can change that course to ti
        // Although we can't have more classes now, we can make more time for later classes (greedy thought)
        else if (heapSize > 0 && nums[0] > ti) {
            total = total - nums[0] + ti;
            replace(ti);
        }
    }
    return heapSize;
}

Curriculum IV

You need to take a total of N courses, with course numbers from 0 to n-1.

Some courses will have direct prerequisite courses. For example, if you want to take course 0, you must take course 1 first, and the number pairs of prerequisite courses will be given in the form of [1,0] number pairs.

Give you the total number of courses n, a list of direct prerequisite courses and a list of query pairs.

For each query pair, please judge whether queries[i][0] is a prerequisite course for queries[i][1].

Please return a list of Boolean values. Each element in the list corresponds to the judgment results of each query pair of queries in turn.

Note: if course a is a prerequisite for Course b and Course b is a prerequisite for Course c, then course a is also a prerequisite for course c.

Example 1:

Input: n = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
Output:[false,true]
Explanation: course 0 is not a prerequisite for course 1, but course 1 is a prerequisite for course 0.

Example 2:

Input: n = 2, prerequisites = [], queries = [[1,0],[0,1]]
Output:[false,false]
Explanation: there are no prerequisite courses, so each course is independent.

Example 3:

Input: n = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
Output:[true,true]

Example 4:

Input: n = 3, prerequisites = [[1,0],[2,0]], queries = [[0,1],[2,0]]
Output:[false,true]

Example 5:

Input: n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]
Output:[true,false,true,false]

Tips:

  • 2 <= n <= 100
  • 0 <= prerequisite.length <= (n * (n - 1) / 2)
  • 0 <= prerequisite[i][0], prerequisite[i][1] < n
  • prerequisite[i][0] != prerequisite[i][1]
  • There is no ring in the prerequisite course map.
  • There are no duplicate edges in the prerequisite course map.
  • 1 <= queries.length <= 10^4
  • queries[i][0] != queries[i][1]
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
bool* checkIfPrerequisite(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int** queries, int queriesSize, int* queriesColSize, int* returnSize){
    int n = numCourses;
    // Mapping
    int dp[n][n];
    memset(dp, 0, sizeof(dp));
    // Prerequisite courses
    for (int i = 0; i < prerequisitesSize; i++) {
        dp[prerequisites[i][0]][prerequisites[i][1]] = 1;
    }
    // floyd algorithm
    // The essence of Floyd algorithm is DP, and k is the stage of DP, so it is necessary to write the outermost part
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                // i is the pilot course of k and k is the pilot course of j
                dp[i][j] |= (dp[i][k] && dp[k][j]); 
            }
        }
    }
    bool *ans = (bool *)malloc(sizeof(bool) * queriesSize);
    *returnSize = queriesSize;
    for (int i = 0; i < queriesSize; i++) {
        if (dp[queries[i][0]][queries[i][1]] == 1)
            ans[i] = true;
        else 
            ans[i] = false;
        // printf("%d", ans[i]);
    }
    return ans;
}

Summary

I've gained a lot, but I've spent a lot of time. I can't finish learning. I'm crying.

Topics: C Algorithm leetcode