Jan. 26, 2022
Here is the link.
C# | DFS algorithm | Study Leetcode discuss C# code | Jan. 26, 2022
Jan. 26, 2022
Introduction
It is so interesting to read C# code with meaningful comments and good variable names. I did learn a few things from studying one of C# discuss post using DFS. What I can learn from 20 minutes to read and also running C# code? I like to write a post first, and then come back to review later.
C# | DFS algorithm | Simple to detect cycle | Style talk
Two variables are added to help DFS algorithm, isVisited and isAdded.
I like to learn from a few discuss using C# and see what I can do better this time.
case 2: 4, [[1,0],[2,0],[3,1],[3,2]]
output: [0,2,1,3]
A few things to look into more carefully, write down in the following:
- Only need to find one path. So in DFS, no need to apply backtracking; I like to question this statement in the comment written by the user: https://leetcode.com/bacon/;
- Use private variables, so that function runDepthFirstSearch has less than five arguments. Two are private variables to store courses taken in the order to meet prerequisite requirement.
- Work on test case II in main function, start from course 3 to run DFS, 3 -> 1 -> 0, so how to mark those three nodes using isVisited and isAdded? Think about more carefully in the design stage.
- More on step 3, if first visit is node 3, and then start DFS from node 3, mark isVisited[3] = true, and let the search go through 1 and then go through 0. Node 0 is found and then isAdded[0] = true, reset all isVisited to false; isVisited is helpful to detect cycle.
- More on step 4, isAdded is helpful to terminate DFS search as a base case.
- More on step 3, path A: from 1 -> 0, path B: from 3->1->0, node 1 will be visited more than once, so isVisited has to be reset all the time.
Improvements:
- DFS algorithm - make base case the first statement - clear and not hidden in the if the statement, and also save cost to avoid visit removed edges in the graph.
- Move isVisited variable inside for loop.
Challenge to be expert on algorithm | Take risk | Do my best
It takes time for me to ask questions on reviewing C# code written by others. I do think that this DFS approach is very good example for me to learn from my own experience.
The following code passes online judge.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _210_course_schedule_2
{
class Program
{
static void Main(string[] args)
{
// case 2: 4, [[1,0],[2,0],[3,1],[3,2]]
// output: [0,2,1,3]
var case2 = new int[4][];
case2[0] = new int[] { 1, 0 };
case2[1] = new int[] { 2, 0 };
case2[2] = new int[] { 3, 1 };
case2[3] = new int[] { 3, 2 };
var test = new Program();
var output = test.FindOrder(4, case2);
Debug.Assert(string.Join("->", output).CompareTo("0->2->1->3") == 0);
}
// Style talk: Using private variables, good comment about DFS, cycle detection, meaningful variable names
// do not need backtracking because we only need one solution.
private int[] coursesInOrder;
private int index = 0;
/// <summary>
/// Jan. 26, 2022
/// DFS algorithm
/// Learn C# code from https://leetcode.com/problems/course-schedule-ii/discuss/294815/C-DFS
/// </summary>
/// <param name="numCourses"></param>
/// <param name="prerequisites"></param>
/// <returns></returns>
public int[] FindOrder(int numCourses, int[][] prerequisites)
{
coursesInOrder = new int[numCourses];
var adjacencyMatrix = new HashSet<int>[numCourses]; // adjacencyMatrix is better name compared to takenFirst
for (int i = 0; i < numCourses; i++)
{
adjacencyMatrix[i] = new HashSet<int>();
}
foreach (var fromTo in prerequisites)
{
var from = fromTo[0];
var to = fromTo[1];
adjacencyMatrix[from].Add(to); // from, to variables are more meaningful than first, next
}
//var isVisited = new bool[numCourses]; // move into for loop
var isAdded = new bool[numCourses];
for (int i = 0; i < numCourses; i++)
{
var isVisited = new bool[numCourses];
if (isAdded[i])
{
continue;
}
var noCyclic = runDepthFirstSearch(i, adjacencyMatrix, isVisited, isAdded);
if (!noCyclic)
{
return new int[0];
}
}
return coursesInOrder;
}
/// <summary>
/// Jan. 26, 2022
/// Return false - no cycle in the graph
/// true - no cycle
/// </summary>
/// <param name="courseId"></param>
/// <param name="adjacencyMatrix"></param>
/// <param name="isVisited"></param>
/// <param name="isAdded"></param>
/// <returns></returns>
private bool runDepthFirstSearch(int courseId, HashSet<int>[] adjacencyMatrix, bool[] isVisited, bool[] isAdded)
{
if(isAdded[courseId]) // I added the statement - this should be base case, make it clear!
{
return true;
}
if (isVisited[courseId]) // detect a cycle - quit!
{
return false;
}
isVisited[courseId] = true;
var nextCourses = adjacencyMatrix[courseId];
foreach (var next in nextCourses)
{
var noCycle = runDepthFirstSearch(next, adjacencyMatrix, isVisited, isAdded);
if (!noCycle)
{
return false;
}
}
// backtracking is challenging, think about more since I did not write original C# code - Jan. 26, 2022
// if (!isAdded[courseId]) // play with if statement, argue that not visited must be not added ? not true
{
coursesInOrder[index] = courseId;
index++;
isAdded[courseId] = true;
}
isVisited[courseId] = false; // play with this reset, why?
return true;
}
}
}
No comments:
Post a Comment