From 4619f5bb0e1bd2cf1712a52a944f42422064083e Mon Sep 17 00:00:00 2001 From: Paridhi Malviya Date: Sun, 8 Feb 2026 19:15:36 -0600 Subject: [PATCH] Complete BFS-1 --- CourseSchedule.swift | 134 +++++++++++++++++++++++++++++ LevelOrderTraversal.swift | 174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 CourseSchedule.swift create mode 100644 LevelOrderTraversal.swift diff --git a/CourseSchedule.swift b/CourseSchedule.swift new file mode 100644 index 00000000..7b425c18 --- /dev/null +++ b/CourseSchedule.swift @@ -0,0 +1,134 @@ +// +// CourseSchedule.swift +// DSA-Practice +// +// Created by Paridhi Malviya on 1/19/26. +// + +/* + (1, 0) -> in order to do course 1, we should have finished course 0. + prerequisites array -> [[1,0], [], []] -> if we draw, we will figure out that it's edges array. + chekc if 0,1,2,3,4,5 -> courses can be finished. + initially we can + + => How to identidy if it's a graph problem or not ? + start drawing. If we find connections.. + + Different ways to represent graph - adjacency list or adjacency matrix, an array of edges. + + choose an independent one. process it. + others which becomes independet then next will b eindependent...so on and so forth - + Topological sort + indegrees array -> how many sharp edges are incoming to a new node. + + all the babies of an independent node has to be processed. + use BFS. use queue. + how to find that what all items should be added to the queue when doing BFS. + + --> if we process 0 then search the edges array for dependent once. + search is leanr. + so for search optimization , use hashing baed data structure. Hence, hash map. + + use independetn to dependent list. + if added children t queue. reduce indegree by 1. if it turned into 0 means it has become independent, If not, not yet become independent. + + Taking inside the queue means we can complete the course. If all indegrees are 0 then + + -> two ways of representing a graph. + hash map - 1 is dependt on 0. + 4 has outgoinf to 5 + + which format is applkicable for out problem is critical decision. + + + level up -> + If indegree of two elements are 0, then + if we put both i queue -> then it will + if we don't put both - thne it will not. if all independent nodes are not going i queue then it will not work. + for BFS - all sould be processed. allnodes of particular level hosul be processed together. + DFS - we can use any + + ********** all nodes of particular level hosul be processed together in BFS. + + Toplological sort is used to check a cycle. Somenodes will not be gong inside the queue. If thetre is cyclic dependency then we can identify it. + + Using DFS also, we can detect the cycle. + topological sort -> + + + time complexity of the solution -> O(V + E). which ever is bigger, that will be considered. + space compelxityt -> adjacency list O(V + E) + from hash map. we are picking all eges for a particular vertex, we are processing al, those edges. thats;w combinly called E + No of edges are greater than no of vertices. + In worst case, edges can go upto n^2. + E - consolidation of all children, all edges. + + */ + +//toplological sort solution +class CourseSchedule { + + init() { + + } + + func canFinish(_ numCourses: Int, _ prerequisites: [[Int]]) -> Bool { + + var indegrees = Array(repeating: 0, count: numCourses) + var adjacencyMap = [Int:[Int]]() + for course in prerequisites { + //for each course, set the indegrees + let independent = course[1] + let dependent = course[0] + + if (adjacencyMap[course[1]] != nil) { + adjacencyMap[course[1]]!.append(dependent) + } else { + adjacencyMap[course[1]] = [dependent] + } + indegrees[dependent] += 1 + } + + //topological sort started + //add the courses for which indegrees are 0 + var queue = QueueUsingLL() + var count = 0 //how many nodes are going inside the queue + for (index, value) in indegrees.enumerated() { + if (value == 0) { + queue.enqueue(index) + count += 1 + } + } + + if (count == numCourses) { + //if all courses has gone into the queue so all are taken. + return true + } + //If queue is empty initially, it means there is a cycle. Nothing will go inside the queue + if (queue.isEmpty) { + return false + } + + while (queue.size != 0) { + let curr = queue.dequeue() + if let curr = curr { + //check in adjacency list the connections from this node + if let children = adjacencyMap[curr] { + for val in children { + indegrees[val] -= 1 + if (indegrees[val] == 0) { + queue.enqueue(val) + //whenever putting inside the queue, increment the count by 1 + count += 1 + if (count == numCourses) { + return true + } + } + } + } + } + } + return false + } +} + diff --git a/LevelOrderTraversal.swift b/LevelOrderTraversal.swift new file mode 100644 index 00000000..1788024a --- /dev/null +++ b/LevelOrderTraversal.swift @@ -0,0 +1,174 @@ +// +// LevelOrderTraversal.swift +// DSA-Practice +// +// Created by Paridhi Malviya on 1/19/26. +// + +/* + Level order traversal - + + Inorder, preorder, postorder -> DFS based algorithm. + BFS based traversal. + + DFS is -> any one child is processed at a time + BFS -> all children are processed at once. + + In DFS, if I want to process all children at once, then we need to process level wise. FIFO. Hence, queue is used. with the vision of processing all children together, maintain FIFO order. + + On some BFS solution -> we maintain size. In some, we don't + siz evariable -> If we need distinction that what all nodes belong to which level, then maintain size variable of the queue. + *****When level finishes, then take the size of the queue. + If we are not sure if we should take the size of the queue or not, always take the size of the queue. + + Initial size of the queue - -> at level 1, we have 1 node. size = 1 + process all cihldren of the node -> at level 2, 3 and 1 -> current level finished. The take the sizeof the queue. + process all nodes of a level. then take the size of the queue. -> these are the number of nodes at the next level. + + Time complexity -> O(n) all nodes are touched once and going inside the queue. + space complexity -> maximum no of leaf nodes - (n/2) 50% of the nodes are at the leaf level. + 1/2 doesn't matter, so n/2 + + For each data structure -> size(), isEmpty() - these fucntions will always be O(1) + + */ + + +class LevelOrderTraversal { + + init() { + let listOfLevels = levelOrderTraversal(root: TreeNode(val: 5, left: TreeNode(val: 4, left: nil, right: nil), right: TreeNode(val: 3, left: nil, right: nil))) + print("list of levels \(listOfLevels)") + } + + func levelOrderTraversal(root: TreeNode?) -> [[Int]] { + var result: [[Int]] = [[Int]]() + guard let root = root else { + return [[]] + } + var queue = QueueUsingLL() + queue.enqueue(root) + + while (!queue.isEmpty) { + //maintain size of the queue + let size = queue.size + + var listOfNodesAtALevel: [Int] = [] + //process the level + + for i in 0..() + queue.enqueue(root) + var listOfNodes: [Int] = [] + + while (!queue.isEmpty) { + let currNode = queue.dequeue() + if let currNode = currNode { + listOfNodes.append(currNode.val) + + //process babies + if let leftNode = currNode.left { + queue.enqueue(leftNode) + } + if let rightNode = currNode.right { + queue.enqueue(rightNode) + } + } + } + print("listOfNodesAtALevel \(listOfNodes)") + } +} + + +/* + => Level order traversal using DFS -> + send out the level variable as a parameter of recursion, + Use hash map -> each level numbers as key of dictionary. That level's data as the value of that keys in a n array. + O(n) - extra space. challenge -> not sorted keys. + + + TreeMap and orderMaps are under the hood self-balanced BST. Time complexity to search for an element - log(n) + sorting the keys - n log n + using hashing map with priority queue- nlogn + + => maintain a minimum and maximum depth. I can linearly traverse through it then. -> Bucket sort. O(n) time complexity. + extra space -> O(n) + + build the above hashmap into the result array itself. + => maintain list of list. [[], [], []]. level 0 items will be added at 0th indexed list, level 1 items will be added to 1st indexed list and so on... + tmie complexity -. O(n) + space -> O(h) for recursion call stacks. over the hood - O(1) because only output array. + + */ +class LevelOrderTraversalUsingDFS { + + func levelOrder(_ root: TreeNode?) -> [[Int]] { + var result: [[Int]] = [[Int]]() + dfs(root: root, level: 0, result: &result) + return result + } + + private func dfs(root: TreeNode?, level: Int, result: inout [[Int]]) { + + //base + guard let root = root else { + return + } + //logic + if (level == result.count) { + //it means the array corresponding to this level doesn't exist in the 2-D array + result.append([]) + } + //can add the root at the post order, preorder, inorder level. But the list should be appended at the preorder level. + result[level].append(root.val) + + dfs(root: root.left, level: level + 1, result: &result) + dfs(root: root.right, level: level + 1, result: &result) + } + + private func levelOrderDeuplicate(_ root: TreeNode?) -> [[Int]] { + var result = [[Int]]() + dfsDuplicate(root, level: 0, result: &result) + return result + } + + private func dfsDuplicate(_ root: TreeNode?, level: Int, result: inout [[Int]]) { + + guard let root = root else { + return + } + + //logic + if (result.count == level) { + //means the array for a particular level is not present, so add it + result.append([]) + } + //action + result[level].append(root.val) + dfsDuplicate(root.left, level: level + 1, result: &result) + dfsDuplicate(root.right, level: level + 1, result: &result) + } +}