├── README.md ├── go ├── adjacency_list.go ├── adjacency_matrix.go ├── binary_search_tree.go ├── circular_linked_list.go ├── doubly_linked_list.go ├── floyd_algorithm.go ├── hash.go ├── heap.go ├── linked_list.go ├── monotonic_stack.go ├── old_and_new_state.go ├── queue.go ├── sliding_window.go └── stack.go └── python ├── circular_linked_list.py ├── Backtracking ├── permutations.py └── subsets.py ├── __init__.py ├── adjacency_list.py ├── adjacency_matrix.py ├── backtracking.py ├── bellman_ford.py ├── binary_search.py ├── binary_search_bisect.py ├── binary_search_tree.py ├── binary_tree.py ├── bottom_up_dp.py ├── breadth_first_search.py ├── bubble_sort.py ├── counting_sort.py ├── cycle.py ├── depth_first_search.py ├── dijkstra.py ├── disjoint_set.py ├── divide_and_conquer.py ├── doubly_linked_list.py ├── dp_bit_masking.py ├── edge_list.py ├── euclid_algo.py ├── eularian.py ├── exponentiation_algo.py ├── floyd_algorithm.py ├── floyd_warshall.py ├── greedy.py ├── hash_table.py ├── heap.py ├── insertion_sort.py ├── kosaraju.py ├── krushkal.py ├── linked_list.py ├── merge_sort.py ├── monotonic_stack.py ├── old_new_state.py ├── prim.py ├── queue.py ├── quick_select.py ├── quick_sort.py ├── recursion.py ├── rolling_hash.py ├── selection_sort.py ├── sieve_of_eratosthenes.py ├── sliding_window.py ├── stack.py ├── top_down_dp.py ├── topological_sort.py ├── traversal.py └── trie.py /README.md: -------------------------------------------------------------------------------- 1 | # Basic Data Structures And Algorithms 2 | 3 | ## Central idea behind this project 4 | 5 | The idea is to focus on implementing basic important data structures and algorithms on very basic problems. These data structures are not exhaustive, but these are the algorithms that we occasionally come across. 6 | This would help in two ways: 7 | 8 | - Learn the exact insights and mechanisms about algorithms by heart. 9 | - Developing Template for the common data structures and algorithms for later reuse. 10 | 11 | Note: 12 | 13 | 1. When using python, use [pypy](https://www.pypy.org/) in contests, if available, when solving questions because it's a faster alternative to cpython. Just read about it. 14 | 15 | 2. All FAANG companies mostly focus on these algorithms only. This are high leverage algorithms from the interview perspective. 16 | 17 | ## List of data structures and algorithms 18 | 19 | - [x] (Two Pointers) Floyd's Algorithm (The Tortoise and the Hare) 20 | - [x] (Two Pointers) Old and new state 21 | - [x] (Two Pointers) Start and end of sliding window 22 | - [x] Linked List 23 | - [x] Doubly Linked List 24 | - [x] Circular Linked List 25 | - [x] Hash Table 26 | - [x] Stack 27 | - [x] Monotonic stack 28 | - [x] Queue 29 | - [x] Recursion example 30 | - [x] Divide and Conquer 31 | - [x] Insertion Sort 32 | - [x] Merge Sort/(Two Pointers) Pointer-1 and pointer-2 from two sequences 33 | - [x] Bubble Sort 34 | - [x] Selection Sort 35 | - [x] Quick Sort 36 | - [x] Counting Sort 37 | - [x] (Order Statistics) Quick Select 38 | - [x] Binary Search/ (Two Pointers) Left and right boundary 39 | - [x] Binary Search(using bisect) 40 | - [x] Heap/Priority Queue 41 | - [x] Trie 42 | - [x] Rolling hash 43 | - [x] Disjoint Set 44 | - [x] Binary Tree 45 | - [x] Binary Search Tree 46 | - [x] Adjacency List 47 | - [x] Adjacency Matrix 48 | - [x] Edge List 49 | - [x] Breadth First Search 50 | - [x] Depth First Search 51 | - [x] Tree Traversal (Inorder, Preorder, Postorder, Level Order) 52 | - [x] Dijkstra's Algorithm (Priority queue) 53 | - [x] Bellman Ford Algoritm 54 | - [x] Floyd Warshall Algorithm 55 | - [x] Prim's Algorithm 56 | - [x] Krushkal's Algorithm 57 | - [x] Eular Path/Circuit 58 | - [x] Kosaraju’s algorithm(Strongly connected components) 59 | - [x] Detecting Cycle in graph(Back/Forward/tree edge) 60 | - [x] Topological Sort 61 | - [x] Backtracking 62 | - [x] Greedy Algorithm Knapsack 63 | - [x] Dynamic Programming Top down DP 64 | - [x] Bottom Up DP 65 | - [x] DP with bit masking 66 | - [x] Euclid's Algorithm (Greatest common divisor) 67 | - [x] Exponentiation algorithm (Exponentiation by squaring D&C) 68 | - [x] Sieve of Eratosthenes 69 | 70 | For more advanced topics and deep dive into competitive programming, refer this book: https://cses.fi/book/book.pdf as suggested by top coders. 71 | 72 | I came across some new things about python while learning. You might also come across these at some time. Read these blogs for help understanding them, beforehand, so that you don't get stuck like me: 73 | 74 | 1. https://book.pythontips.com/en/latest/for_-_else.html 75 | 2. https://stackoverflow.com/questions/1907565/c-and-python-different-behaviour-of-the-modulo-operation 76 | 3. https://www.programiz.com/python-programming/global-local-nonlocal-variables#:~:text=Nonlocal%20variables%20are%20used%20in,keywords%20to%20create%20nonlocal%20variables. 77 | 4. https://stackoverflow.com/a/9674327/6725646 78 | 79 | ## Input size and time complexity reference 80 | 81 | | Input size | Target time complexity | 82 | | --------------- | -------------------------------- | 83 | | n ≤ 12 | O(n!) | 84 | | n ≤ 25 | O(2^n) | 85 | | n ≤ 100 | O(n^4) | 86 | | n ≤ 500 | O(n^3) | 87 | | n ≤ 10 000 | O(n^2) | 88 | | n ≤ 1 000 000 | O(n log n) | 89 | | n ≤ 100 000 000 | O(n) | 90 | | n > 100 000 000 | O(1) or O(log n) or O(n ^ (1/2)) | 91 | 92 | ## Suggestions 93 | 94 | Are there any other algorithm that I might be missing? Please open a pull request and contribute! Let's learn together. 95 | 96 | ## References 97 | 98 | - https://www.programiz.com/ 99 | - https://www.geeksforgeeks.org/ 100 | - https://codeforces.com/blog/entry/21344 101 | - https://www.pluralsight.com/guides/algorithm-templates:-introduction 102 | - https://leetcode.com/problems/sum-of-subarray-minimums/discuss/178876/stack-solution-with-very-detailed-explanation-step-by-step 103 | - https://leetcode.com/problems/binary-tree-right-side-view/solution/ 104 | - https://realpython.com/python-recursion/ 105 | - https://www.youtube.com/watch?v=pPiSMPWKZ3E 106 | - https://medium.com/techtofreedom/algorithms-for-interview-2-monotonic-stack-462251689da8 107 | - https://www.youtube.com/watch?v=rf6uf3jNjbo 108 | - https://towardsdatascience.com/implementing-a-trie-data-structure-in-python-in-less-than-100-lines-of-code-a877ea23c1a1 109 | - https://codeforces.com/blog/entry/60445 110 | - https://www.youtube.com/watch?v=wU6udHRIkcc 111 | - https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/ 112 | - https://www.hackerearth.com/practice/algorithms/graphs/minimum-spanning-tree/tutorial/ 113 | - https://www.youtube.com/watch?v=5M-m62qTR-s 114 | - https://www.youtube.com/watch?v=AamHZhAmR7o 115 | - https://www.geeksforgeeks.org/strongly-connected-components/ 116 | - https://favtutor.com/blogs/topological-sort-python 117 | - https://medium.com/algorithms-and-leetcode/backtracking-e001561b9f28 118 | - https://www.geeksforgeeks.org/greedy-algorithms/ 119 | - https://www.youtube.com/watch?v=YBSt1jYwVfU&list=PLl0KD3g-oDOGJUdmhFk19LaPgrfmAGQfo 120 | - https://www.youtube.com/playlist?list=PL_z_8CaSLPWekqhdCPmFohncHwz8TY2Go 121 | - https://medium.com/analytics-vidhya/bits-bitmasking-62277789f6f5 122 | - https://www.youtube.com/watch?v=B5HKW99AvV0 123 | - https://codecrucks.com/exponential-problem-solving-using-divide-and-conquer/ 124 | - https://python.plainenglish.io/prime-numbers-using-sieve-of-eratosthenes-in-python-b917ae6188c7 125 | - https://www.geeksforgeeks.org/sieve-of-eratosthenes/ 126 | - https://gobyexample.com/ 127 | 128 | ## Thank You. :) 129 | -------------------------------------------------------------------------------- /go/adjacency_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func addEdge(adjList map[int][]int, source int, destination int) { 8 | adjList[source] = append(adjList[source], destination) 9 | } 10 | 11 | func main() { 12 | adjList := make(map[int][]int) 13 | addEdge(adjList, 1, 2) 14 | addEdge(adjList, 2, 3) 15 | addEdge(adjList, 5, 6) 16 | addEdge(adjList, 2, 1) 17 | fmt.Println(adjList) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /go/adjacency_matrix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func addEdge(adjMatrix [][]bool, source int, destination int) { 8 | adjMatrix[source][destination] = true 9 | } 10 | 11 | func main() { 12 | adjMatrix := [][]bool{} 13 | for idx := 0; idx < 4; idx++ { 14 | adjMatrix = append(adjMatrix, make([]bool, 4)) 15 | } 16 | addEdge(adjMatrix, 1, 2) 17 | addEdge(adjMatrix, 2, 3) 18 | addEdge(adjMatrix, 3, 2) 19 | addEdge(adjMatrix, 0, 1) 20 | fmt.Println(adjMatrix) 21 | } 22 | -------------------------------------------------------------------------------- /go/binary_search_tree.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Node struct { 6 | value int 7 | left *Node 8 | right *Node 9 | } 10 | 11 | type BinarySearchTree struct { 12 | root *Node 13 | } 14 | 15 | func search(root *Node, key int) *Node { 16 | if root == nil || root.value == key { 17 | return root 18 | } 19 | 20 | if root.value < key { 21 | return search(root.right, key) 22 | } 23 | 24 | return search(root.left, key) 25 | } 26 | 27 | func insert(root *Node, key int) *Node { 28 | if root == nil { 29 | return &Node{ 30 | value: key, 31 | } 32 | } 33 | 34 | if root.value < key { 35 | root.right = insert(root.right, key) 36 | } else { 37 | root.left = insert(root.left, key) 38 | } 39 | 40 | return root 41 | } 42 | 43 | func findSuccessor(root *Node) *Node { 44 | curr := root.right 45 | 46 | for curr.left != nil { 47 | curr = curr.left 48 | } 49 | 50 | return curr 51 | } 52 | 53 | func delete(root *Node, key int) *Node { 54 | if root == nil { 55 | return root 56 | } else if root.value < key { 57 | root.right = delete(root.right, key) 58 | } else if root.value > key { 59 | root.left = delete(root.left, key) 60 | } else { 61 | if root.left == nil && root.right == nil { 62 | return nil 63 | } else if root.left == nil { 64 | return root.right 65 | } else if root.right == nil { 66 | return root.left 67 | } else { 68 | successor := findSuccessor(root) 69 | root.value = successor.value 70 | delete(root.right, successor.value) 71 | return root 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func main() { 79 | 80 | root := &Node{ 81 | value: 50, 82 | } 83 | 84 | binarySearchTree := &BinarySearchTree{ 85 | root: root, 86 | } 87 | 88 | root = insert(binarySearchTree.root, 30) 89 | root = insert(binarySearchTree.root, 20) 90 | root = insert(binarySearchTree.root, 40) 91 | root = insert(binarySearchTree.root, 70) 92 | root = insert(binarySearchTree.root, 60) 93 | root = insert(binarySearchTree.root, 80) 94 | 95 | fmt.Println(search(binarySearchTree.root, 46) != nil) 96 | fmt.Println(search(binarySearchTree.root, 60) != nil) 97 | 98 | } 99 | -------------------------------------------------------------------------------- /go/circular_linked_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Node struct { 8 | key int 9 | next *Node 10 | } 11 | 12 | type CircularLinkedList struct { 13 | head *Node 14 | } 15 | 16 | func (circularLinkedList *CircularLinkedList) push(value int) { 17 | 18 | newNode := &Node{ 19 | key: value, 20 | } 21 | 22 | if circularLinkedList.head == nil { 23 | circularLinkedList.head = newNode 24 | newNode.next = newNode 25 | } 26 | 27 | newNode.next = circularLinkedList.head 28 | 29 | curr := circularLinkedList.head 30 | 31 | for curr != nil { 32 | if curr.next == circularLinkedList.head { 33 | curr.next = newNode 34 | break 35 | } 36 | curr = curr.next 37 | } 38 | 39 | circularLinkedList.head = newNode 40 | 41 | } 42 | 43 | func (circularLinkedList *CircularLinkedList) print() { 44 | curr := circularLinkedList.head 45 | 46 | for curr.next != circularLinkedList.head { 47 | fmt.Println(curr.key) 48 | curr = curr.next 49 | } 50 | 51 | fmt.Println(curr.key) 52 | 53 | fmt.Println("-") 54 | } 55 | 56 | func main() { 57 | circularLinkedList := CircularLinkedList{} 58 | 59 | circularLinkedList.push(6) 60 | circularLinkedList.print() 61 | circularLinkedList.push(7) 62 | circularLinkedList.print() 63 | circularLinkedList.push(1) 64 | circularLinkedList.print() 65 | } 66 | -------------------------------------------------------------------------------- /go/doubly_linked_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Node struct { 8 | key int 9 | prev *Node 10 | next *Node 11 | } 12 | 13 | type DoublyLinkedList struct { 14 | head *Node 15 | } 16 | 17 | func (doublyLinkedList *DoublyLinkedList) push(value int) { 18 | newNode := &Node{ 19 | key: value, 20 | } 21 | newNode.next = doublyLinkedList.head 22 | 23 | if doublyLinkedList.head != nil { 24 | doublyLinkedList.head.prev = newNode 25 | } 26 | 27 | doublyLinkedList.head = newNode 28 | } 29 | 30 | func (doublyLinkedList *DoublyLinkedList) insertAfter(prevNode *Node, value int) { 31 | newNode := &Node { 32 | key: value, 33 | } 34 | newNode.next = prevNode.next 35 | prevNode.next = newNode 36 | newNode.prev = prevNode 37 | 38 | if newNode.next != nil { 39 | newNode.next.prev = newNode 40 | } 41 | } 42 | 43 | func (doublyLinkedList *DoublyLinkedList) append(value int) { 44 | 45 | newNode := &Node { 46 | key: value, 47 | } 48 | curr := doublyLinkedList.head 49 | 50 | if curr == nil { 51 | doublyLinkedList.head = newNode 52 | return 53 | } 54 | 55 | for curr.next != nil { 56 | curr = curr.next 57 | } 58 | 59 | curr.next = newNode 60 | newNode.prev = curr 61 | 62 | } 63 | 64 | func (doublyLinkedList *DoublyLinkedList) print() { 65 | curr := doublyLinkedList.head 66 | 67 | for curr != nil { 68 | fmt.Println(curr.key) 69 | curr = curr.next 70 | } 71 | fmt.Println("-") 72 | } 73 | 74 | func (doublyLinkedList *DoublyLinkedList) delete(value int) { 75 | curr := doublyLinkedList.head 76 | 77 | if curr != nil && curr.key == value { 78 | doublyLinkedList.head = curr.next 79 | if doublyLinkedList.head != nil { 80 | doublyLinkedList.head.prev = nil 81 | } 82 | return 83 | } 84 | 85 | for curr != nil { 86 | if curr.key == value { 87 | curr.prev.next = curr.next 88 | if curr.next != nil { 89 | curr.next.prev = curr.prev 90 | } 91 | } 92 | curr = curr.next 93 | } 94 | 95 | } 96 | 97 | func main() { 98 | 99 | doublyLinkedList := DoublyLinkedList{} 100 | 101 | doublyLinkedList.push(2) 102 | doublyLinkedList.print() 103 | doublyLinkedList.push(7) 104 | doublyLinkedList.print() 105 | doublyLinkedList.push(8) 106 | doublyLinkedList.print() 107 | doublyLinkedList.push(10) 108 | doublyLinkedList.print() 109 | doublyLinkedList.append(4) 110 | doublyLinkedList.print() 111 | doublyLinkedList.delete(8) 112 | doublyLinkedList.print() 113 | } -------------------------------------------------------------------------------- /go/floyd_algorithm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func slowMovingCondition(arr []int, slow int, fast int) bool { 8 | return arr[slow] != arr[fast] 9 | } 10 | 11 | func processLogic(arr []int, slow int, fast int) { 12 | arr[slow] = arr[fast] 13 | } 14 | 15 | func floydAlgorithm(arr []int) int { 16 | slow := 0 17 | n := len(arr) 18 | 19 | for fast := 0; fast < n; fast++ { 20 | if slowMovingCondition(arr, slow, fast) { 21 | slow += 1 22 | } 23 | 24 | processLogic(arr, slow, fast) 25 | } 26 | 27 | return slow + 1 28 | } 29 | 30 | func main() { 31 | 32 | intArray := []int{1, 1, 2, 2, 2, 3, 4, 5} 33 | 34 | end_idx := floydAlgorithm(intArray) 35 | 36 | fmt.Println(intArray[:end_idx]) 37 | } 38 | -------------------------------------------------------------------------------- /go/hash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Set struct { 8 | m map[int]struct{} 9 | } 10 | 11 | var exists = struct{}{} 12 | 13 | func newSet() *Set { 14 | set := &Set{} 15 | set.m = make(map[int]struct{}) 16 | return set 17 | } 18 | 19 | func (set *Set) add(value int) { 20 | set.m[value] = exists 21 | } 22 | 23 | func (set *Set) delete(value int) { 24 | delete(set.m, value) 25 | } 26 | 27 | func (set *Set) contains(value int) bool { 28 | _, ok := set.m[value] 29 | 30 | return ok 31 | } 32 | func main() { 33 | hash_table := make(map[int]int) 34 | 35 | hash_table[1] = 2 36 | hash_table[2] = 3 37 | 38 | fmt.Println(hash_table) 39 | 40 | delete(hash_table, 2) 41 | fmt.Println(hash_table) 42 | 43 | hash_set := newSet() 44 | 45 | hash_set.add(2) 46 | hash_set.add(3) 47 | fmt.Println(hash_set.m) 48 | 49 | hash_set.delete(3) 50 | fmt.Println(hash_set.m) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /go/heap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | ) 7 | 8 | type Element struct { 9 | priority int 10 | value int 11 | } 12 | 13 | type PriorityQueue []Element 14 | 15 | func (priorityQueue PriorityQueue) Len() int { 16 | return len(priorityQueue) 17 | } 18 | 19 | func (priorityQueue PriorityQueue) Swap(i, j int) { 20 | priorityQueue[i], priorityQueue[j] = priorityQueue[j], priorityQueue[i] 21 | } 22 | 23 | func (priorityQueue PriorityQueue) Less(i, j int) bool { 24 | return priorityQueue[i].priority >= priorityQueue[j].priority 25 | } 26 | 27 | func (priorityQueue *PriorityQueue) Push(element interface{}) { 28 | newElement := element.(Element) 29 | *priorityQueue = append(*priorityQueue, newElement) 30 | } 31 | 32 | func (priorityQueue *PriorityQueue) Pop() interface{} { 33 | temp := *priorityQueue 34 | l := len(temp) 35 | res := temp[l-1] 36 | temp[l-1] = Element{} 37 | *priorityQueue = temp[:l-1] 38 | return res 39 | } 40 | 41 | func topKFrequent(nums []int, k int) (res []int) { 42 | counter := make(map[int]int, 0) 43 | 44 | for idx := 0; idx < len(nums); idx++ { 45 | counter[nums[idx]] += 1 46 | } 47 | 48 | priorityQueue := make(PriorityQueue, 0) 49 | 50 | idx := 0 51 | 52 | for value, count := range counter { 53 | priorityQueue = append(priorityQueue, Element{ 54 | value: value, 55 | priority: count, 56 | }) 57 | idx += 1 58 | } 59 | 60 | heap.Init(&priorityQueue) 61 | 62 | for i := 0; i < k; i++ { 63 | num := heap.Pop(&priorityQueue).(Element) 64 | res = append(res, num.value) 65 | } 66 | 67 | return res 68 | } 69 | 70 | func main() { 71 | a := []int{1, 1, 2, 4, 5, 1, 2} 72 | k := 3 73 | fmt.Println(topKFrequent(a, k)) 74 | } 75 | -------------------------------------------------------------------------------- /go/linked_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Node struct { 8 | next *Node 9 | key int 10 | } 11 | 12 | type LinkedList struct { 13 | head *Node 14 | } 15 | 16 | func (linkedList *LinkedList) push(value int) { 17 | newNode := &Node{ 18 | key: value, 19 | } 20 | 21 | newNode.next = linkedList.head 22 | linkedList.head = newNode 23 | } 24 | 25 | func (linkedList *LinkedList) insertAfter(prevNode *Node, value int) { 26 | if prevNode == nil { 27 | return 28 | } 29 | 30 | newNode := &Node{ 31 | key: value, 32 | } 33 | 34 | newNode.next = prevNode.next 35 | prevNode.next = newNode 36 | } 37 | 38 | func (linkedList *LinkedList) append(value int) { 39 | 40 | newNode := &Node{ 41 | key: value, 42 | } 43 | 44 | if linkedList.head == nil { 45 | linkedList.head = newNode 46 | return 47 | } 48 | 49 | last := linkedList.head 50 | for last.next != nil { 51 | last = last.next 52 | } 53 | 54 | last.next = newNode 55 | 56 | } 57 | 58 | func (linkedList *LinkedList) print() { 59 | 60 | curr := linkedList.head 61 | for curr != nil { 62 | fmt.Println(curr.key) 63 | curr = curr.next 64 | } 65 | } 66 | 67 | func (linkedList *LinkedList) delete(value int) { 68 | curr := linkedList.head 69 | 70 | if curr != nil && curr.key == value { 71 | linkedList.head = curr.next 72 | return 73 | } 74 | 75 | prev := &Node{} 76 | 77 | for curr != nil { 78 | if curr.key == value { 79 | prev.next = curr.next 80 | return 81 | } 82 | 83 | prev = curr 84 | curr = curr.next 85 | } 86 | } 87 | 88 | func main() { 89 | linkedList := LinkedList{} 90 | linkedList.append(6) 91 | linkedList.print() 92 | fmt.Println("-") 93 | linkedList.push(7) 94 | linkedList.print() 95 | fmt.Println("-") 96 | linkedList.push(1) 97 | linkedList.print() 98 | fmt.Println("-") 99 | linkedList.append(8) 100 | linkedList.print() 101 | fmt.Println("-") 102 | linkedList.insertAfter(linkedList.head.next.next, 2) 103 | linkedList.print() 104 | fmt.Println("-") 105 | linkedList.delete(1) 106 | linkedList.print() 107 | fmt.Println("-") 108 | } 109 | -------------------------------------------------------------------------------- /go/monotonic_stack.go: -------------------------------------------------------------------------------- 1 | /* Monotonic stack is basically a stack, which maintains the list of elements 2 | from left to right in a sorted order. It's good for problems like 3 | next/previous greatest/smallest element/range query. (e.g. left limit/right limit) 4 | E.g. Given an array of integers heights representing the histogram's bar height 5 | where the width of each bar is 1, return the area of the largest rectangle in the histogram. 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "errors" 13 | ) 14 | 15 | 16 | type Arr struct { 17 | stack []int 18 | } 19 | 20 | func newStack() *Arr { 21 | arr := &Arr{} 22 | arr.stack = make([]int, 0) 23 | return arr 24 | } 25 | 26 | func (arr *Arr) push(value int) { 27 | arr.stack = append(arr.stack, value) 28 | } 29 | 30 | func (arr *Arr) isEmpty() bool { 31 | n := len(arr.stack) 32 | return n == 0 33 | } 34 | 35 | func (arr *Arr) pop() (int, error) { 36 | if arr.isEmpty() { 37 | return -1, errors.New("Stack underflow") 38 | } 39 | n := len(arr.stack) - 1 40 | el := arr.stack[n] 41 | arr.stack = append(arr.stack[:n]) 42 | return el, nil 43 | } 44 | 45 | func (arr *Arr) top() (int, error) { 46 | if arr.isEmpty() { 47 | return -1, errors.New("Stack underflow") 48 | } 49 | n := len(arr.stack) - 1 50 | return arr.stack[n], nil 51 | } 52 | 53 | func Max(x, y int) int { 54 | if x < y { 55 | return y 56 | } 57 | return x 58 | } 59 | 60 | func calcPrevSmaller(arr []int) []int { 61 | prevSmaller := make([]int, len(arr)) 62 | stack := newStack() 63 | for idx:= 0; idx < len(arr); idx++ { 64 | top, _ := stack.top() 65 | for(!stack.isEmpty() && arr[top] >= arr[idx]) { 66 | stack.pop() 67 | } 68 | if stack.isEmpty() { 69 | prevSmaller[idx] = -1 70 | top, _ = stack.top() 71 | } else { 72 | top, _ = stack.top() 73 | prevSmaller[idx] = top 74 | } 75 | stack.push(idx) 76 | } 77 | 78 | return prevSmaller 79 | } 80 | 81 | func calcNextSmaller(arr []int) []int { 82 | nextSmaller := make([]int, len(arr)) 83 | stack := newStack() 84 | for idx:= len(arr) - 1; idx >= 0; idx-- { 85 | top, _ := stack.top() 86 | for(!stack.isEmpty() && arr[top] >= arr[idx]) { 87 | stack.pop() 88 | top, _ = stack.top() 89 | } 90 | if stack.isEmpty() { 91 | nextSmaller[idx] = len(arr) 92 | } else { 93 | top, _ = stack.top() 94 | nextSmaller[idx] = top 95 | } 96 | stack.push(idx) 97 | } 98 | 99 | return nextSmaller 100 | } 101 | 102 | /* Basic idea is to pop elements until we find an element equal or more than 103 | its value. Then push the element. Such that an increasing order is maintained. 104 | We can perform action at every pop. 105 | */ 106 | func calculateMaximumHistrogramArea(arr []int) int { 107 | 108 | maxArea := 0 109 | prevSmaller := calcPrevSmaller(arr) 110 | nextSmaller := calcNextSmaller(arr) 111 | 112 | for idx := 0; idx < len(arr); idx++ { 113 | area := arr[idx] * (nextSmaller[idx] - prevSmaller[idx] - 1) 114 | maxArea = Max(maxArea, area) 115 | } 116 | 117 | return maxArea 118 | 119 | } 120 | 121 | func main() { 122 | arr := []int{2, 1, 5, 6, 2, 3} 123 | fmt.Println(calculateMaximumHistrogramArea(arr)) 124 | } -------------------------------------------------------------------------------- /go/old_and_new_state.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func fib(n int) int { 8 | old, new := 0, 1 9 | 10 | for idx := 0; idx < n; idx++ { 11 | old, new = new, old+new 12 | } 13 | 14 | return old 15 | } 16 | 17 | func main() { 18 | fmt.Print(fib(10)) 19 | } 20 | -------------------------------------------------------------------------------- /go/queue.go: -------------------------------------------------------------------------------- 1 | /* Implemented using doubly linked list 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "container/list" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | queue := list.New() 13 | 14 | queue.PushBack(8) 15 | queue.PushBack(6) 16 | 17 | front := queue.Front() 18 | fmt.Println(front.Value) 19 | 20 | queue.Remove(front) 21 | 22 | front = queue.Front() 23 | fmt.Println(front.Value) 24 | 25 | } -------------------------------------------------------------------------------- /go/sliding_window.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const MaxUint = ^uint(0) 8 | const MinUint = 0 9 | const MaxInt = int(MaxUint >> 1) 10 | const MinInt = -MaxInt - 1 11 | 12 | // Given a string S and a string T, find the minimum window in S which will contain all the characters in T. 13 | 14 | func findMinSubstring(s1 string, s2 string) int { 15 | start, end := 0, 0 16 | 17 | counter := make(map[string]int) 18 | 19 | for _, ch := range s2 { 20 | chStr := string(ch) 21 | _, exists := counter[chStr] 22 | if exists { 23 | counter[chStr] += 1 24 | } else { 25 | counter[chStr] = 1 26 | } 27 | } 28 | 29 | count := len(s2) 30 | countS1 := len(s1) 31 | 32 | res := int(MaxUint >> 1) 33 | 34 | for end < countS1 { 35 | counter[string(s1[end])] -= 1 36 | 37 | if counter[string(s1[end])] >= 0 { 38 | count -= 1 39 | } 40 | 41 | end += 1 42 | 43 | for count == 0 { 44 | if end-start < res { 45 | res = end - start 46 | } 47 | 48 | counter[string(s1[start])] += 1 49 | 50 | if counter[string(s1[start])] > 0 { 51 | count += 1 52 | } 53 | start += 1 54 | } 55 | } 56 | 57 | return res 58 | } 59 | 60 | func main() { 61 | fmt.Println(findMinSubstring("ADOBECODEBANC", "ABC")) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /go/stack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type Arr struct { 9 | stack []int 10 | } 11 | 12 | func newStack() *Arr { 13 | arr := &Arr{} 14 | arr.stack = make([]int, 0) 15 | return arr 16 | } 17 | 18 | func (arr *Arr) push(value int) { 19 | arr.stack = append(arr.stack, value) 20 | } 21 | 22 | func (arr *Arr) isEmpty() bool { 23 | n := len(arr.stack) 24 | return n == 0 25 | } 26 | 27 | func (arr *Arr) pop() (int, error) { 28 | if arr.isEmpty() { 29 | return -1, errors.New("Stack underflow") 30 | } 31 | n := len(arr.stack) - 1 32 | el := arr.stack[n] 33 | arr.stack = append(arr.stack[:n]) 34 | return el, nil 35 | } 36 | 37 | func (arr *Arr) top() (int, error) { 38 | if arr.isEmpty() { 39 | return -1, errors.New("Stack underflow") 40 | } 41 | n := len(arr.stack) - 1 42 | return arr.stack[n], nil 43 | } 44 | 45 | func main() { 46 | stack := newStack() 47 | stack.push(1) 48 | fmt.Println(stack) 49 | fmt.Println("-") 50 | stack.push(2) 51 | fmt.Println(stack) 52 | fmt.Println("-") 53 | top, err := stack.top() 54 | if err != nil { 55 | fmt.Println("Error:", err) 56 | } else { 57 | fmt.Println(top) 58 | } 59 | fmt.Println("-") 60 | el, err := stack.pop() 61 | if err != nil { 62 | fmt.Println("Error:", err) 63 | } else { 64 | fmt.Println(el) 65 | } 66 | fmt.Println("-") 67 | fmt.Println(stack) 68 | fmt.Println("-") 69 | el, err = stack.pop() 70 | if err != nil { 71 | fmt.Println("Error:", err) 72 | } else { 73 | fmt.Println(el) 74 | } 75 | fmt.Println("-") 76 | fmt.Println(stack) 77 | fmt.Println("-") 78 | el, err = stack.pop() 79 | if err != nil { 80 | fmt.Println("Error:", err) 81 | } else { 82 | fmt.Println(el) 83 | } 84 | fmt.Println("-") 85 | fmt.Println(stack) 86 | fmt.Println("-") 87 | } 88 | -------------------------------------------------------------------------------- /python/ circular_linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | class CircularLinkedList: 9 | 10 | def __init__(self): 11 | self.head = None 12 | 13 | def push(self, val): 14 | node = Node(val) 15 | 16 | node.next = self.head 17 | 18 | if self.head: 19 | temp = self.head 20 | while(temp.next != self.head): 21 | temp = temp.next 22 | temp.next = node 23 | else: 24 | node.next = node 25 | 26 | self.head = node 27 | 28 | def print_list(self): 29 | temp = self.head 30 | 31 | while(temp.next != self.head): 32 | print(temp.val) 33 | temp = temp.next 34 | 35 | print(temp.val) 36 | 37 | 38 | if __name__ == '__main__': 39 | llist = CircularLinkedList() 40 | llist.push(6) 41 | llist.push(7) 42 | llist.push(1) 43 | llist.print_list() 44 | -------------------------------------------------------------------------------- /python/Backtracking/permutations.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | def permute(self, nums): 3 | def backtrack(first): 4 | if first == n: 5 | output.append(nums[:]) 6 | for i in range(first, n): 7 | nums[first], nums[i] = nums[i], nums[first] 8 | backtrack(first+1) 9 | nums[first], nums[i] = nums[i], nums[first] 10 | n = len(nums) 11 | output = [] 12 | backtrack(0) 13 | return output 14 | 15 | s = Solution() 16 | print(s.permute([1,2,3])) -------------------------------------------------------------------------------- /python/Backtracking/subsets.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | def subsets(self, nums): 3 | def backtrack(first, curr): 4 | if len(curr) == k: 5 | output.append(curr[:]) 6 | return 7 | for i in range(first, n): 8 | curr.append(nums[i]) 9 | backtrack(i+1, curr) 10 | curr.pop() 11 | n = len(nums) 12 | output = [] 13 | for k in range(n+1): 14 | backtrack(0, []) 15 | return output 16 | 17 | s = Solution() 18 | print(s.subsets([1,2,3])) -------------------------------------------------------------------------------- /python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Achint08/algo-kit/eab091a10e9d7f87da3ff39838f59a68ca12d87e/python/__init__.py -------------------------------------------------------------------------------- /python/adjacency_list.py: -------------------------------------------------------------------------------- 1 | # def add_edge_weighted(u, v, w): 2 | # adj_list[u] = adj_list.get(u, []) + [(v, w)] 3 | # # For undirected graph 4 | # # adj_list[v] = adj_list.get(v, []) + [(u, w)] 5 | 6 | 7 | def add_edge(u, v): 8 | adj_list[u] = adj_list.get(u, []) + [v] 9 | # For undirected graph 10 | # adj_list[v] = adj_list.get(v, []) + [u] 11 | 12 | 13 | if __name__ == '__main__': 14 | adj_list = {} 15 | edge_set = [edge for edge in input().split(' ')] 16 | for edge in edge_set: 17 | nodes = [int(node) for node in edge.split(',')] 18 | add_edge(nodes[0], nodes[1]) 19 | print(adj_list) 20 | -------------------------------------------------------------------------------- /python/adjacency_matrix.py: -------------------------------------------------------------------------------- 1 | # def add_edge_weighted(u, v, w): 2 | # adj_matrix[u - 1][v - 1] = w 3 | # # For undirected graph, 4 | # # adj_matrix[v - 1][u - 1] = w 5 | 6 | 7 | def add_edge(u, v): 8 | adj_matrix[u - 1][v - 1] = 1 9 | # For undirected graph, 10 | # adj_matrix[v - 1][u - 1] = 1 11 | 12 | 13 | if __name__ == '__main__': 14 | nodes = int(input()) 15 | adj_matrix = [[0 for _ in range(nodes)] for _ in range(nodes)] 16 | edge_set = [edge for edge in input().split(' ')] 17 | for edge in edge_set: 18 | nodes = [int(node) for node in edge.split(',')] 19 | add_edge(nodes[0], nodes[1]) 20 | print(adj_matrix) 21 | -------------------------------------------------------------------------------- /python/backtracking.py: -------------------------------------------------------------------------------- 1 | # Backtracking is used, when we have to explore possibilities incrementally 2 | # and make a decision based on solution of partial subproblem if we should continue 3 | # exploring the possibility or not. In short, there are two fundamental properties: 4 | # 1. No repetition and completion 5 | # 2. Search Pruning - Make a decision incrementally 6 | # Classic example - N-Queen Problem 7 | 8 | 9 | # Check if move was safe 10 | def isSafe(row, col): 11 | 12 | for i in range(n): 13 | if board[row][i] == 1: 14 | return False 15 | 16 | for i, j in zip(range(row, -1, -1), range(col, -1, -1)): 17 | if board[i][j] == 1: 18 | return False 19 | 20 | for i, j in zip(range(row, n, 1), range(col, -1, -1)): 21 | if board[i][j] == 1: 22 | return False 23 | 24 | return True 25 | 26 | 27 | def place_queen(col): 28 | global board 29 | if col >= n: 30 | return True 31 | 32 | for row in range(n): 33 | # Search Pruning 34 | if isSafe(row, col): 35 | board[row][col] = 1 36 | result = place_queen(col + 1) 37 | if result: 38 | return True 39 | # Backtracking 40 | board[row][col] = 0 41 | 42 | return False 43 | 44 | 45 | if __name__ == '__main__': 46 | n = int(input()) 47 | board = [[0 for _ in range(n)] for _ in range(n)] 48 | result = place_queen(0) 49 | if not result: 50 | print("No possible state") 51 | else: 52 | print(board) 53 | -------------------------------------------------------------------------------- /python/bellman_ford.py: -------------------------------------------------------------------------------- 1 | # Single Soure Shortest Path Algorithm 2 | # Bellman Ford Algorithm 3 | # Note - 4 | # 1. It works on negative weight. 5 | # 2. Can detect negative cycle 6 | 7 | # More inclined towards dynamic programming approach. 8 | # The key idea is that it would take at most | V -1 | steps 9 | # to know the minimum distance. 10 | def bellman_ford(source, vertices, adj_list): 11 | dist = {} 12 | parent = {} 13 | for vertice in vertices: 14 | dist[vertice] = float('inf') 15 | parent[vertice] = None 16 | 17 | dist[source] = 0 18 | 19 | n = len(vertices) 20 | for _ in range(n - 1): 21 | 22 | for u in adj_list: 23 | for v in adj_list[u]: 24 | new_dist = adj_list[u][v] 25 | if new_dist + dist[u] < dist[v]: 26 | dist[v] = dist[u] + new_dist 27 | parent[v] = u 28 | 29 | for u in adj_list: 30 | for v in adj_list[u]: 31 | new_dist = adj_list[u][v] 32 | if new_dist + dist[u] < dist[v]: 33 | return True, {}, {} 34 | 35 | return False, dist, parent 36 | 37 | 38 | adj_list = { 39 | 'U': {'V': 2, 'W': 5, 'X': 1}, 40 | 'V': {'U': 2, 'X': 2, 'W': 3}, 41 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 42 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 43 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 44 | 'Z': {'W': 5, 'Y': 1}, 45 | } 46 | 47 | vertices = adj_list.keys() 48 | 49 | print(bellman_ford('U', vertices, adj_list)) 50 | 51 | 52 | # Negative Weight Cycle. 53 | adj_list = { 54 | 'U': {'V': -2, 'W': 5, 'X': 1}, 55 | 'V': {'U': -2, 'X': 2, 'W': 3}, 56 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 57 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 58 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 59 | 'Z': {'W': 5, 'Y': 1}, 60 | } 61 | 62 | print(bellman_ford('U', vertices, adj_list)) 63 | -------------------------------------------------------------------------------- /python/binary_search.py: -------------------------------------------------------------------------------- 1 | # def binary_search(arr, l, r, x): 2 | # if l > r: 3 | # return None 4 | 5 | # mid = l + (r - l) // 2 6 | 7 | # if arr[mid] == x: 8 | # return mid 9 | # elif arr[mid] > x: 10 | # return binary_search(arr, l, mid - 1, x) 11 | # else: 12 | # return binary_search(arr, mid + 1, r, x) 13 | 14 | 15 | def binary_search_iterative(arr, l, r, x): 16 | 17 | while(l <= r): 18 | mid = l + (r - l) // 2 19 | if arr[mid] == x: 20 | return mid 21 | elif arr[mid] > x: 22 | r = mid - 1 23 | else: 24 | l = mid + 1 25 | 26 | 27 | arr = [2, 2, 3, 4, 10, 40] 28 | 29 | print(binary_search_iterative(arr, 0, len(arr) - 1, 50)) 30 | -------------------------------------------------------------------------------- /python/binary_search_bisect.py: -------------------------------------------------------------------------------- 1 | import bisect 2 | # Always assume array is sorted 3 | 4 | # Basics 5 | # bisect_left 6 | # Find a position where the element should be inserted to a position just left of it 7 | # arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 8 | # print(bisect.bisect_left(arr, 41)) 9 | 10 | # O(n) time operation 11 | # arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 12 | # bisect.insort_left(arr, 41) 13 | # print(arr) 14 | 15 | 16 | # arr = [1.1, 2.2, 3.3, 4.6, 5.5, 6.9] 17 | # print(bisect.bisect_left(arr, 4.4)) 18 | 19 | 20 | # arr = ["aaa", "bbb", "ccc"] 21 | # print(bisect.bisect_left(arr, "bug")) 22 | 23 | # bisect_right 24 | # Find a position where the element should be inserted to a position just right of it 25 | # arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 26 | # print(bisect.bisect_right(arr, 41)) 27 | 28 | # # O(n) time operation 29 | # arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 30 | # bisect.insort_right(arr, 41) 31 | # print(arr) 32 | 33 | 34 | # arr = [1.1, 2.2, 3.3, 4.6, 5.5, 6.9] 35 | # print(bisect.bisect_right(arr, 4.4)) 36 | 37 | 38 | # arr = ["aaa", "bbb", "ccc"] 39 | # print(bisect.bisect_right(arr, "bug")) 40 | 41 | 42 | # bisect 43 | # Find a position where the element should be inserted to a position just right of it 44 | arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 45 | print(bisect.bisect(arr, 41)) 46 | 47 | # O(n) time operation 48 | arr = [24, 33, 41, 41, 45, 50, 53, 59, 62, 66, 69] 49 | bisect.insort(arr, 41) 50 | print(arr) 51 | 52 | arr = [1.1, 2.2, 3.3, 4.6, 5.5, 6.9] 53 | print(bisect.bisect(arr, 4.4)) 54 | 55 | arr = ["aaa", "bbb", "ccc"] 56 | print(bisect.bisect(arr, "bug")) 57 | 58 | # In short, bisect = bisect_right 59 | -------------------------------------------------------------------------------- /python/binary_search_tree.py: -------------------------------------------------------------------------------- 1 | # Binary Search Tree is a binary tree with following properties for each node in the tree: 2 | # 1. The left subtree has keys less than the current node 3 | # 2. The right subtree has keys more than the current node 4 | # 3. There are no duplicate nodes. 5 | 6 | class Node: 7 | 8 | def __init__(self, val): 9 | self.val = val 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | def search(root, key): 15 | if not root or root.val == key: 16 | return root 17 | 18 | if root.val < key: 19 | return search(root.right, key) 20 | 21 | return search(root.left, key) 22 | 23 | 24 | def insert(root, key): 25 | if not root: 26 | return Node(key) 27 | 28 | if root.val < key: 29 | root.right = insert(root.right, key) 30 | elif root.val > key: 31 | root.left = insert(root.left, key) 32 | 33 | return root 34 | 35 | 36 | def find_successor(root): 37 | curr = root.right 38 | 39 | while(curr.left): 40 | curr = curr.left 41 | 42 | return curr 43 | 44 | 45 | def delete(root, key): 46 | if not root: 47 | return root 48 | 49 | if root.val < key: 50 | root.right = delete(root.right, key) 51 | elif root.val > key: 52 | root.left = delete(root.left, key) 53 | else: 54 | if not root.left and not root.right: 55 | return None 56 | elif not root.left: 57 | temp = root.right 58 | root.right = None 59 | return temp 60 | 61 | elif not root.right: 62 | temp = root.left 63 | root.left = None 64 | return temp 65 | else: 66 | successor = find_successor(root) 67 | root.val = successor.val 68 | delete(root.right, successor.val) 69 | return root 70 | 71 | 72 | if __name__ == '__main__': 73 | r = Node(50) 74 | r = insert(r, 30) 75 | r = insert(r, 20) 76 | r = insert(r, 40) 77 | r = insert(r, 70) 78 | r = insert(r, 60) 79 | r = insert(r, 80) 80 | print(search(r, 46) == None) 81 | print(search(r, 60) == None) 82 | print(delete(r, 70)) 83 | -------------------------------------------------------------------------------- /python/binary_tree.py: -------------------------------------------------------------------------------- 1 | # A binary tree is tree, in which each node has at most 2 children. 2 | # From the definition of tree, it is a graph with no cycle. 3 | # Root -> A node with no parent. 4 | # Leaf -> A node with no children. 5 | # Properties: (Not important!!!!!!!) 6 | # 1. The maximum number of nodes at level ‘l’ of a binary tree is 2^l. 7 | # 2. The maximum number of nodes in a binary tree of height ‘h’ is 2^h – 1. Height of root = 1 8 | # 3. In a Binary Tree with N nodes, minimum possible height or the minimum number of levels is Log (N+1). 9 | # 4. A Binary Tree with L leaves has at least | Log L |+ 1 levels. 10 | # 5. In Binary tree where every node has 0 or 2 children, the number of leaf nodes is always one more than nodes with two children. 11 | # Types of Binary tree(Not important!!!!) 12 | # 1. Complete Tree - If all the levels are completely 13 | # filled except possibly the last level and the last level has all keys as left as possible 14 | # 2. Full Tree - If every node has 0 or 2 children. 15 | # 3. Perfect Tree - All the internal nodes have two children and all leaf nodes are at the same level. 16 | 17 | # Note: 18 | # 1. Always remember that accessing the node will return the memory reference. So access the value. 19 | # 2. But always remember to check if there is Node before accessing the value. 20 | 21 | class Node: 22 | 23 | def __init__(self, val): 24 | # Generally, we don't store reference to parent, but we could if question demands 25 | self.val = val 26 | self.left = None 27 | self.right = None 28 | 29 | 30 | if __name__ == '__main__': 31 | root = Node(1) 32 | 33 | root.left = Node(2) 34 | root.right = Node(3) 35 | root.right.left = Node(4) 36 | if root: 37 | print(root.val) 38 | else: 39 | print(None) 40 | if root.left: 41 | print(root.left.val) 42 | else: 43 | print(None) 44 | if root.right: 45 | print(root.right.val) 46 | else: 47 | print(None) 48 | if root.left.right: 49 | print(root.left.right.val) 50 | else: 51 | print(None) 52 | if root.right.left: 53 | print(root.right.left.val) 54 | else: 55 | print(None) 56 | -------------------------------------------------------------------------------- /python/bottom_up_dp.py: -------------------------------------------------------------------------------- 1 | # Please check top-down_dp before bottom_up. 2 | # Intuition of bottom up comes, generally, after finding the state definition in 3 | # top-down DP. 4 | # 5 | # Key points: 6 | # 1. We could have a lot of dimensions (state variables) using which we could solve 7 | # in a bottom up manner. We should try to reduce the dimension, whenever possible 8 | # and avoid dimensions which have a lot of states. Try to choose state variables 9 | # which have limited states. For example, in 0-1 knapsack, we have 2 state variable 10 | # capacity and curr_idx, both of which are limited. 11 | # 2. Start with thinking of base cases, such as, in knapsack problem, 12 | # let's assume we only have 1 item initially and try to think incrementally, what would 13 | # happen if we know have two items, how would the solution to this problem depend of the 14 | # solution of problem with 1 item. (Induction theorm comes into play here!) 15 | # 3. We usually have a grid to store the space solutions, so as to use pre-computed results. 16 | # Remember to initialize the grid. 17 | # 18 | # Learning DP takes time! Watch videos and read articles, practice makes perfect!! I am still in process, 19 | # but that's what I have learned. 20 | # My favorite playlists so far: 21 | # 22 | # 1. https://www.youtube.com/watch?v=YBSt1jYwVfU&list=PLl0KD3g-oDOGJUdmhFk19LaPgrfmAGQfo 23 | # 2. https://www.youtube.com/playlist?list=PL_z_8CaSLPWekqhdCPmFohncHwz8TY2Go 24 | # 25 | # 0-1 Knapsack using bottom-up. 26 | 27 | def knapsack(capacity): 28 | 29 | # It might help to create sentinels to help us in writing general solution. 30 | dp = [[0 for _ in range(capacity + 1)] for _ in range(total_items + 1)] 31 | 32 | for item in range(1, total_items + 1): 33 | for curr_cap in range(1, capacity + 1): 34 | if curr_cap < total_wt[item - 1]: 35 | dp[item][curr_cap] = dp[item - 1][curr_cap] 36 | else: 37 | dp[item][curr_cap] = max( 38 | dp[item - 1][curr_cap], 39 | total_val[item - 1] + 40 | dp[item - 1][curr_cap - total_wt[item - 1]] 41 | ) 42 | return dp[total_items][capacity] 43 | 44 | 45 | if __name__ == "__main__": 46 | total_wt = [10, 40, 20, 30] 47 | total_val = [60, 40, 100, 120] 48 | total_items = len(total_wt) 49 | cap = 50 50 | print(knapsack(cap)) 51 | -------------------------------------------------------------------------------- /python/breadth_first_search.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def bfs(node): 5 | queue = deque() 6 | queue.append(node) 7 | # Other way is to create a dictionary and initialize it as false. Set it to true once visited 8 | # It should be in process, not visited, because in process will eventually be visited, unless breaked 9 | visited = set() 10 | visited.add(node) 11 | 12 | while(queue): 13 | node = queue.popleft() 14 | print(node) 15 | for next_node in adj_list.get(node, []): 16 | if next_node not in visited: 17 | visited.add(next_node) 18 | queue.append(next_node) 19 | 20 | 21 | def add_edge(u, v): 22 | adj_list[u] = adj_list.get(u, []) + [v] 23 | 24 | 25 | if __name__ == '__main__': 26 | adj_list = {} 27 | edge_set = [edge for edge in input().split(' ')] 28 | for edge in edge_set: 29 | nodes = [int(node) for node in edge.split(',')] 30 | add_edge(nodes[0], nodes[1]) 31 | print(adj_list) 32 | bfs(7) 33 | -------------------------------------------------------------------------------- /python/bubble_sort.py: -------------------------------------------------------------------------------- 1 | def bubble_sort(arr): 2 | n = len(arr) 3 | 4 | for i in range(n - 1): 5 | for j in range(n - i - 1): 6 | if arr[j] > arr[j + 1]: 7 | arr[j], arr[j + 1] = arr[j + 1], arr[j] 8 | 9 | 10 | if __name__ == '__main__': 11 | arr = [int(i) for i in input().split(' ')] 12 | bubble_sort(arr) 13 | print(arr) 14 | 15 | 16 | # Time complexity - O(n^2) 17 | # Space complexity - O(1) 18 | # In place algorithm 19 | # Stable algorithm 20 | -------------------------------------------------------------------------------- /python/counting_sort.py: -------------------------------------------------------------------------------- 1 | def counting_sort(arr): 2 | n = len(arr) 3 | 4 | min_el = arr[0] 5 | max_el = arr[0] 6 | 7 | for i in range(n): 8 | if arr[i] < min_el: 9 | min_el = arr[i] 10 | if arr[i] > max_el: 11 | max_el = arr[i] 12 | 13 | count = [0] * (max_el - min_el + 1) 14 | output = [0] * n 15 | 16 | for i in range(n): 17 | count[arr[i] - min_el] += 1 18 | 19 | for i in range(1, max_el - min_el + 1): 20 | count[i] += count[i - 1] 21 | 22 | i = n - 1 23 | 24 | while(i >= 0): 25 | output[count[arr[i] - min_el] - 1] = arr[i] 26 | count[arr[i] - min_el] -= 1 27 | i -= 1 28 | 29 | for i in range(n): 30 | arr[i] = output[i] 31 | 32 | 33 | if __name__ == '__main__': 34 | arr = [int(i) for i in input().split(' ')] 35 | counting_sort(arr) 36 | print(arr) 37 | 38 | 39 | # Time complexity - O(n) 40 | # Space complexity - O(n) 41 | # Not In place algorithm 42 | # Stable algorithm 43 | -------------------------------------------------------------------------------- /python/cycle.py: -------------------------------------------------------------------------------- 1 | # Classification of edges: 2 | # Tree edge: An edge (u, v) such that v is descendant of u in the dfs-tree 3 | # Forward edge: An edge (u, v) such that v is descendant of u but not part of dfs-tree 4 | # Back edge - An edge (u, v) such that u is descendant of v is the dfs-tree 5 | # Cross edge - An edge (u, v) such that u is not related to v anyway in dfs-tree. Belongs to different tree in forest. 6 | # 7 | # Note - 8 | # 1. For cycle detection - check if there is any back edge 9 | # 2. For classification between cross and forward edge: 10 | # a. If start time of the curr node is less than the neighbouring node, this means neigbouring 11 | # node is the descendant of curr node, therefore it is a forward edge. 12 | # b. If start time of the curr node is more than the neighbouring node, this means neigbouring 13 | # node can already been visited before and not a descendant for sure, therefore it is a cross edge. 14 | # 3. Forest = Set of trees. 15 | 16 | 17 | def dfs(node): 18 | global color, back_edge, curr_time, start_time, finish_time, forward_edge, cross_edge, tree_edge 19 | color[node] = 'grey' 20 | start_time[node] = curr_time 21 | curr_time += 1 22 | 23 | for neighbour in adj_list[node]: 24 | if color[neighbour] == 'grey': 25 | back_edge += 1 26 | elif color[neighbour] == 'white': 27 | tree_edge += 1 28 | dfs(neighbour) 29 | elif color[neighbour] == 'black': 30 | if start_time[node] < start_time[neighbour]: 31 | forward_edge += 1 32 | else: 33 | cross_edge += 1 34 | 35 | color[node] = 'black' 36 | finish_time[node] = curr_time 37 | curr_time += 1 38 | 39 | 40 | if __name__ == '__main__': 41 | adj_list = { 42 | 'U': {'V': 2, 'W': 5, 'X': 1}, 43 | 'V': {'U': 2, 'X': 2, 'W': 3}, 44 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 45 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 46 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 47 | 'Z': {'W': 5, 'Y': 1}, 48 | } 49 | 50 | vertices = adj_list.keys() 51 | 52 | color = {} 53 | start_time = {} 54 | finish_time = {} 55 | curr_time = 0 56 | back_edge = cross_edge = forward_edge = tree_edge = 0 57 | for vertice in vertices: 58 | color[vertice] = 'white' 59 | start_time[vertice] = -1 60 | finish_time[vertice] = -1 61 | 62 | for vertice in vertices: 63 | if color[vertice] == 'white': 64 | dfs(vertice) 65 | 66 | if back_edge >= 1: 67 | print('Cycle detected') 68 | 69 | print(back_edge, forward_edge, cross_edge, tree_edge) 70 | -------------------------------------------------------------------------------- /python/depth_first_search.py: -------------------------------------------------------------------------------- 1 | # Visited can be maintained centrally to visited all components of graph 2 | # def dfs_iterative(node): 3 | # stack = [] 4 | # visited = set() 5 | # stack.append(node) 6 | # visited.add(node) 7 | # while(stack): 8 | # node = stack.pop() 9 | # print(node) 10 | # for next_node in adj_list.get(node, []): 11 | # if next_node not in visited: 12 | # stack.append(next_node) 13 | # visited.add(next_node) 14 | 15 | # Color logic doesn't work in dfs iterative 16 | def dfs_recursive(node): 17 | print(node) 18 | color[node] = 'gray' 19 | for next_node in adj_list.get(node, []): 20 | if color[next_node] == 'white': 21 | dfs_recursive(next_node) 22 | 23 | color[node] = 'black' 24 | 25 | 26 | def add_edge(u, v): 27 | adj_list[u] = adj_list.get(u, []) + [v] 28 | # For undirected graph 29 | # adj_list[v] = adj_list.get(v, []) + [u] 30 | 31 | 32 | if __name__ == '__main__': 33 | adj_list = {} 34 | inp = "1,2 2,3 2,4 4,6 4,1 4,5 7,8" 35 | # edge_set = [edge for edge in input().split(' ')] 36 | edge_set = [edge for edge in inp.split(' ')] 37 | color = {} 38 | for edge in edge_set: 39 | nodes = [int(node) for node in edge.split(',')] 40 | add_edge(nodes[0], nodes[1]) 41 | color[nodes[0]] = color[nodes[1]] = 'white' 42 | print(adj_list) 43 | # dfs_iterative(1) 44 | dfs_recursive(1) 45 | -------------------------------------------------------------------------------- /python/dijkstra.py: -------------------------------------------------------------------------------- 1 | # Single Source Shortest Path Algorithm 2 | # Dijkstra Algorithm 3 | # Note - 4 | # 1. It works only on graphs with no negative cycle. 5 | # 2. Don't use it for graphs with negative weight. (Especially with undirected graph). 6 | # Technically, a undirected graph with negative weight is a negative cycle. 7 | 8 | import heapq 9 | 10 | 11 | # More inclined toward greedy approach. 12 | def dijkstra(source, vertices, adj_list): 13 | 14 | dist = {} 15 | parent = {} 16 | 17 | for vertice in vertices: 18 | dist[vertice] = float('inf') 19 | parent[vertice] = None 20 | 21 | dist[source] = 0 22 | 23 | priority_queue = [(0, source)] 24 | 25 | while(priority_queue): 26 | curr_dist, curr_node = heapq.heappop(priority_queue) 27 | 28 | # Not equal to, to avoid source not to fail first source case. 29 | # There is a possibility that a node be there in priority queue multiple 30 | # times. let's say, we added the element in queue the first time and we 31 | # got a better distance before we pop, in that case, the node with 32 | # with larger distance will just be ignored. That's what we want. 33 | if curr_dist > dist[curr_node]: 34 | continue 35 | 36 | for adj_node in adj_list[curr_node]: 37 | new_dist = curr_dist + adj_list[curr_node][adj_node] 38 | 39 | if dist[adj_node] > new_dist: 40 | dist[adj_node] = new_dist 41 | parent[adj_node] = curr_node 42 | heapq.heappush(priority_queue, (new_dist, adj_node)) 43 | 44 | return dist, parent 45 | 46 | 47 | adj_list = { 48 | 'U': {'V': 2, 'W': 5, 'X': 1}, 49 | 'V': {'U': 2, 'X': 2, 'W': 3}, 50 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 51 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 52 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 53 | 'Z': {'W': 5, 'Y': 1}, 54 | } 55 | 56 | vertices = adj_list.keys() 57 | 58 | print(dijkstra('U', vertices, adj_list)) 59 | -------------------------------------------------------------------------------- /python/disjoint_set.py: -------------------------------------------------------------------------------- 1 | # Disjoint set Find - Union using union by rank and path compression. 2 | # The idea of union by rank and path compression is to 3 | # reduce time complexity while doing union between two 4 | # disjoint set. We do union in such a way that we attach shorter 5 | # tree to a longer tree always. 6 | # 7 | # Disjoint set has application in cycle detection. 8 | # The idea is to perform the following operations: 9 | # 1. form individual sets during initialization 10 | # 2. Performing union of sets if two elements belong to same set 11 | # 3. Checking if two elements belong to same disjoint set. 12 | 13 | def find(x): 14 | if parent[x] == x: 15 | return x 16 | return find(parent[x]) 17 | 18 | 19 | def union(x, y): 20 | parent_x = find(x) 21 | parent_y = find(y) 22 | 23 | if size[parent_x] < size[parent_y]: 24 | parent[parent_x] = parent_y 25 | size[parent_y] += size[parent_x] 26 | else: 27 | parent[parent_y] = parent_x 28 | size[parent_x] += size[parent_y] 29 | 30 | 31 | def is_same_set(x, y): 32 | parent_x = find(x) 33 | if parent_x == find(y): 34 | return True 35 | else: 36 | return False 37 | 38 | 39 | if __name__ == '__main__': 40 | parent = [i for i in range(10)] 41 | size = [1 for i in range(10)] 42 | 43 | print(is_same_set(1, 2)) 44 | union(1, 2) 45 | print(is_same_set(1, 2)) 46 | union(2, 3) 47 | print(is_same_set(2, 3)) 48 | print(is_same_set(1, 7)) 49 | -------------------------------------------------------------------------------- /python/divide_and_conquer.py: -------------------------------------------------------------------------------- 1 | # General pattern for divide and conquer algorithms: 2 | # def divide_and_conquer(input_list, l, r): 3 | # Some base condition 4 | # some_div_idx = divide(input_list) 5 | # some_conquer_1 = divide_and_conquer(input_list, l, some_div_idx) 6 | # some_conquer_2 = divide_and_conquer(input_list, some_div_idx + 1, r) 7 | # some_combine_step = combine(some_conquer_1, some_conquer_2) 8 | # return some_combine_step 9 | 10 | # Divide means to break down the problem into smaller subproblems 11 | # Conquer means to solve the subproblems 12 | # Combine means to combine the solutions of subproblems or do some other operation in the problem 13 | 14 | # Basically divide and conquer is just like recursion, but with few extra steps 15 | # where we divide and combine the results. 16 | # Important to learn induction 17 | # Base case 18 | # Assume f(n - 1) works 19 | # Show that f(n) works using f(n - 1) 20 | 21 | # Best example: Tower of hanoi 22 | # Watch this video: https://www.youtube.com/watch?v=rf6uf3jNjbo 23 | 24 | # Pro tip: Always remember to return, if applicable 25 | 26 | 27 | def toh(n, source, aux, dest): 28 | if n == 1: 29 | print(source, dest) 30 | return 31 | # Divide and conquer 32 | toh(n - 1, source, dest, aux) 33 | # Combine 34 | print(source, dest) 35 | # Divide and conquer again 36 | toh(n - 1, aux, source, dest) 37 | 38 | 39 | if __name__ == '__main__': 40 | toh(3, 'A', 'B', 'C') 41 | -------------------------------------------------------------------------------- /python/doubly_linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, prev=None, next=None, data=None): 4 | self.data = data 5 | self.prev = prev 6 | self.next = next 7 | 8 | 9 | class DoublyLinkedList: 10 | 11 | def __init__(self): 12 | self.head = None 13 | 14 | def push(self, new_data): 15 | new_node = Node(data=new_data) 16 | new_node.next = self.head 17 | 18 | if self.head: 19 | self.head.prev = new_node 20 | 21 | self.head = new_node 22 | 23 | def insert_after(self, new_data, prev_node): 24 | new_node = Node(data=new_data) 25 | new_node.next = prev_node.next 26 | prev_node.next = new_node 27 | new_node.prev = prev_node 28 | 29 | if new_node.next: 30 | new_node.next.prev = new_node 31 | 32 | def append(self, new_data): 33 | new_node = Node(data=new_data) 34 | if not self.head: 35 | self.head = new_node 36 | return 37 | 38 | last = self.head 39 | while(last.next): 40 | last = last.next 41 | 42 | last.next = new_node 43 | new_node.prev = last 44 | 45 | def print_list(self): 46 | temp = self.head 47 | while(temp): 48 | print(temp.data) 49 | temp = temp.next 50 | 51 | def delete_node(self, key): 52 | temp = self.head 53 | 54 | if temp and temp.data == key: 55 | self.head = temp.next 56 | if self.head: 57 | self.head.prev = None 58 | temp = None 59 | return 60 | 61 | while(temp): 62 | if temp.data == key: 63 | break 64 | temp = temp.next 65 | 66 | if not temp: 67 | return 68 | 69 | temp.prev.next = temp.next 70 | if temp.next: 71 | temp.next.prev = temp.prev 72 | temp = None 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | dll = DoublyLinkedList() 78 | dll.push(2) 79 | dll.push(7) 80 | dll.push(8) 81 | dll.push(10) 82 | dll.append(4) 83 | dll.insert_after(3, dll.head.next) 84 | dll.print_list() 85 | dll.delete_node(10) 86 | print('-') 87 | dll.print_list() 88 | -------------------------------------------------------------------------------- /python/dp_bit_masking.py: -------------------------------------------------------------------------------- 1 | # DP with bit masking is technique to store state in a way 2 | # that can be handled easily. We would be playing around 3 | # bit mask all the time. This also helps us to reduce the dimension 4 | # of the state variables, which is what we need. ( For example, 5 | # in the below case, we have reduced state variable to 1 from 2.) 6 | # 7 | # 8 | # Bitmasking details - https://medium.com/analytics-vidhya/bits-bitmasking-62277789f6f5 9 | # 10 | # 11 | # Job assignment problem 12 | # 13 | 14 | 15 | def get_set_bits(mask): 16 | count = 0 17 | while(mask): 18 | if mask & 1: 19 | count += 1 20 | mask >>= 1 21 | return count 22 | 23 | 24 | def assign_job(cost_matrix): 25 | dp = [] 26 | tasks = len(cost_matrix) 27 | for i in range(2**tasks): 28 | dp.append(float('inf')) 29 | 30 | dp[0] = 0 31 | 32 | for mask in range(2**tasks): 33 | x = get_set_bits(mask) 34 | for j in range(0, tasks): 35 | if not mask & 1 << j: 36 | dp[mask | (1 << j)] = min( 37 | dp[mask | (1 << j)], cost_matrix[x][j] + dp[mask] 38 | ) 39 | 40 | return dp[2**tasks - 1] 41 | 42 | 43 | if __name__ == '__main__': 44 | n = 3 45 | cost = [[10, 20, 30], [50, 10, 64], [76, 50, 54]] 46 | print(assign_job(cost)) 47 | -------------------------------------------------------------------------------- /python/edge_list.py: -------------------------------------------------------------------------------- 1 | # # For weighted graph 2 | # def add_edge(u, v, w): 3 | # edge_list.append((u, v, w)) 4 | # # For undirected graph, 5 | # edge_list.append((v, u, w)) 6 | 7 | 8 | def add_edge(u, v): 9 | edge_list.append((u, v)) 10 | # For undirected graph, 11 | edge_list.append((v, u)) 12 | 13 | 14 | if __name__ == '__main__': 15 | nodes = int(input()) 16 | edge_list = [] 17 | adj_matrix = [[0 for _ in range(nodes)] for _ in range(nodes)] 18 | edge_set = [edge for edge in input().split(' ')] 19 | for edge in edge_set: 20 | nodes = [int(node) for node in edge.split(',')] 21 | add_edge(nodes[0], nodes[1]) 22 | print(adj_matrix) 23 | -------------------------------------------------------------------------------- /python/euclid_algo.py: -------------------------------------------------------------------------------- 1 | # Euclid algoritm is based on the idea that 2 | # let's say we have to find gcd of a and b, i.e. x. 3 | # here, let's say a is greater than b, then, if x divides 4 | # a and b, then x also divides a % b since a can also be written 5 | # as a = quo * b + remain, i.e., for a to be divisible by x, remain should be 6 | # divisible by x. We can now, reduce the problem recursively to a base case, when 7 | # one of either x or y is zero, in that case, other number is the GCD. 8 | # 9 | # Link - https://www.youtube.com/watch?v=B5HKW99AvV0 10 | 11 | 12 | def euclid_gcd(x, y): 13 | if y == 0: 14 | return x 15 | 16 | return euclid_gcd(y, x % y) 17 | 18 | 19 | if __name__ == '__main__': 20 | print(euclid_gcd(15, 10)) 21 | -------------------------------------------------------------------------------- /python/eularian.py: -------------------------------------------------------------------------------- 1 | # Definitions: 2 | # Eularian path - Path that visited every edge exactly once. 3 | # Eularian circuit - Eularian path that starts and ends at the same vertex. 4 | # How to identify? 5 | # Identify add connected components 6 | # Total no. of odd degree vertices can't never be odd. 7 | # Find all the connected vertices and check if: 8 | # 1. Total odd degree vertices more than 2: None 9 | # 2. 2 odd degree vertices - Path. 10 | # 4. 0 odd degree vertices - Circuit 11 | # 12 | # 13 | # Extra knowledge - Hamoltian Path is a path that visits every vertex exactly once. 14 | # Likewise for hamoltian circuit, the source and destination vertex should match. 15 | # There is no way to identify if graph has hamoltian path. Moreover, this forms the 16 | # base question for Travelling salesman problem, such that the weights in minimal. 17 | 18 | 19 | def eularian(adj_list): 20 | 21 | odd_count = 0 22 | 23 | for vertice in adj_list: 24 | if adj_list[vertice] and len(adj_list[vertice]) & 1: 25 | odd_count += 1 26 | 27 | if odd_count == 0: 28 | return "Circuit" 29 | elif odd_count == 2: 30 | return "Path" 31 | else: 32 | return "None" 33 | 34 | 35 | if __name__ == '__main__': 36 | # None 37 | adj_list = { 38 | 'U': {'V': 2, 'W': 5, 'X': 1}, 39 | 'V': {'U': 2, 'X': 2, 'W': 3}, 40 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 41 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 42 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 43 | 'Z': {'W': 5, 'Y': 1}, 44 | } 45 | 46 | print(eularian(adj_list)) 47 | 48 | # Path 49 | adj_list = { 50 | 'U': {'W': 5, 'X': 1}, 51 | 'V': {'X': 2, 'W': 3}, 52 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 53 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 54 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 55 | 'Z': {'W': 5, 'Y': 1}, 56 | } 57 | 58 | print(eularian(adj_list)) 59 | 60 | # Path 61 | adj_list = { 62 | 'U': {'W': 5, 'X': 1}, 63 | 'V': {'X': 2, 'W': 3}, 64 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Z': 5}, 65 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 66 | 'Y': {'X': 1, 'Z': 1}, 67 | 'Z': {'W': 5, 'Y': 1}, 68 | } 69 | 70 | print(eularian(adj_list)) 71 | -------------------------------------------------------------------------------- /python/exponentiation_algo.py: -------------------------------------------------------------------------------- 1 | # The idea is to use D&C to reduce the complexity of the algorithm, by 2 | # dividing the problem into two halves. 3 | # 4 | # https://codecrucks.com/exponential-problem-solving-using-divide-and-conquer/ 5 | 6 | def cal_pow(x, n): 7 | if n == 0: 8 | return 1 9 | 10 | # Here, we are performing AND operation of 1 with n, 11 | # to know if the number is odd or even. This is computationally 12 | # better than using mod. 13 | half_expo = cal_pow(x, n//2) 14 | ans = half_expo * half_expo 15 | if n & 1: 16 | 17 | return x * ans 18 | else: 19 | return ans 20 | 21 | 22 | if __name__ == '__main__': 23 | print(cal_pow(2, 10)) 24 | print(cal_pow(2, 11)) 25 | -------------------------------------------------------------------------------- /python/floyd_algorithm.py: -------------------------------------------------------------------------------- 1 | # ## Template 2 | # def slow_moving_condition(el1, el2): 3 | # # some condition 4 | 5 | # def process_logic(s, f): 6 | # # some condition 7 | 8 | # def slow_fast_pointer(arr): 9 | # slow = 0 10 | # for fast in range(len(arr)): 11 | # if slow_moving_condition(slow, fast): 12 | # slow += 1 13 | # process_logic(slow, fast) 14 | 15 | # Can be used vica-versa as well 16 | 17 | # Given a sorted array nums, remove the duplicates in place 18 | # such that each element appear only once and return the new length. 19 | 20 | def slow_moving_condition(arr, slow, fast): 21 | return arr[slow] != arr[fast] 22 | 23 | 24 | def process_logic(arr, slow, fast): 25 | arr[slow] = arr[fast] 26 | 27 | 28 | def floyd_algo(arr): 29 | slow = 0 30 | n = len(arr) 31 | for fast in range(1, n): 32 | if slow_moving_condition(arr, slow, fast): 33 | slow += 1 34 | 35 | process_logic(arr, slow, fast) 36 | 37 | return slow + 1 38 | 39 | 40 | if __name__ == '__main__': 41 | arr = [int(el) for el in input().split(' ')] 42 | end_idx = floyd_algo(arr) 43 | print(arr[:end_idx]) 44 | -------------------------------------------------------------------------------- /python/floyd_warshall.py: -------------------------------------------------------------------------------- 1 | # All source Shortest Path Algorithm 2 | # Floyd Warshall Algorithm 3 | # Note: 4 | # 1. Can detect negative cycle. 5 | 6 | # Based on the idea of dynamic programming 7 | def floyd_warshall(vertices, adj_list): 8 | 9 | dist = {} 10 | 11 | for vertice1 in vertices: 12 | dist[vertice1] = {} 13 | for vertice2 in vertices: 14 | dist[vertice1][vertice2] = float('inf') 15 | 16 | for vertice in vertices: 17 | dist[vertice][vertice] = 0 18 | 19 | for u in adj_list: 20 | for v in adj_list[u]: 21 | dist[u][v] = adj_list[u][v] 22 | 23 | for s in vertices: 24 | for m in vertices: 25 | for d in vertices: 26 | dist[s][d] = min(dist[s][d], dist[s][m] + dist[m][d]) 27 | 28 | # If distance from node to itself is negative, then there is a negative cycle. 29 | 30 | for u in vertices: 31 | if dist[u][u] < 0: 32 | return True, [] 33 | 34 | return False, dist 35 | 36 | 37 | adj_list = { 38 | 'U': {'V': 2, 'W': 5, 'X': 1}, 39 | 'V': {'U': 2, 'X': 2, 'W': 3}, 40 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 41 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 42 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 43 | 'Z': {'W': 5, 'Y': 1}, 44 | } 45 | 46 | vertices = adj_list.keys() 47 | 48 | print(floyd_warshall(vertices, adj_list)) 49 | 50 | 51 | # Negative Weight Cycle. 52 | adj_list = { 53 | 'U': {'V': -2, 'W': 5, 'X': 1}, 54 | 'V': {'U': -2, 'X': 2, 'W': 3}, 55 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 56 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 57 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 58 | 'Z': {'W': 5, 'Y': 1}, 59 | } 60 | 61 | print(floyd_warshall(vertices, adj_list)) 62 | -------------------------------------------------------------------------------- /python/greedy.py: -------------------------------------------------------------------------------- 1 | # How to identify problems, at first place? 2 | # If the solution to a problem depends on the solutions of further sub-problems, then 3 | # we should check that instead of checking solutions to all sub-problems, if we can 4 | # make decision based on a local optimal solution, then we have greedy solution here. 5 | # It's important to verify that our locally optimal solution will be the best solution 6 | # from the solutions of sub-problems. 7 | # 8 | # Fractional Knapsack problem: 9 | # 10 | # Given weights and values of n items, we need to put these items in a knapsack of capacity 11 | # W to get the maximum total value in the knapsack. We can break items for maximizing 12 | # the total value of knapsack. 13 | # 14 | # Note: 15 | # 1. You need to convert the problem in a way that it can be solved by greedy step. Greedy step 16 | # won't be intuitive in first look. For example, in case of fractional knapsack, sorting unit weight 17 | # and flexibility to choose fractional weight converts problem into greedy algo, otherwise, 18 | # we would have to search the entire search space. 19 | # 20 | 21 | def fractional_knapsack(unit_val, weight, capacity): 22 | 23 | max_val = 0 24 | 25 | zipped_list = sorted(list(zip(unit_val, weight)), key=lambda x: x[0]) 26 | 27 | for curr_unit_val, curr_weight in zipped_list[::-1]: 28 | if capacity - curr_weight >= 0: 29 | capacity -= curr_weight 30 | max_val += curr_weight * curr_unit_val 31 | else: 32 | max_val += capacity * curr_unit_val 33 | break 34 | 35 | return max_val 36 | 37 | 38 | if __name__ == "__main__": 39 | total_wt = [10, 40, 20, 30] 40 | total_val = [60, 40, 100, 120] 41 | unit_per_val = [val//wt for wt, val in zip(total_wt, total_val)] 42 | cap = 50 43 | print(fractional_knapsack(unit_per_val, total_wt, cap)) 44 | -------------------------------------------------------------------------------- /python/hash_table.py: -------------------------------------------------------------------------------- 1 | # Can be implemented using set() or dict() depending on the use-case 2 | # If you want to store metadata, use dict() 3 | # else, use set() 4 | 5 | # Hash table as a dict, 6 | hash_table = {} 7 | 8 | m = int(input()) 9 | for _ in range(m): 10 | key, val = [int(i) for i in input().split(' ')] 11 | # Single value, but chances of updation 12 | # hash_table[key] = val 13 | 14 | # Just like adjacency list 15 | hash_table[key] = hash_table.get(key, []) + [val] 16 | 17 | 18 | # To remove element 19 | hash_table.pop(1) 20 | 21 | print(hash_table) 22 | 23 | # Hash table as a set 24 | hash_set = set() 25 | for _ in range(m): 26 | key = int(input()) 27 | # Keys only get added once 28 | hash_set.add(key) 29 | 30 | hash_set.remove(1) 31 | 32 | print(hash_set) 33 | -------------------------------------------------------------------------------- /python/heap.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | # This is a min heap. 4 | # To use it as max heap, just add -ve sign to every element and use accordingly 5 | a = [2, 4, 5, 1, 8, 9] 6 | heapq.heapify(a) 7 | print(a) 8 | # push 9 | heapq.heappush(a, 5) 10 | print(a) 11 | # pop 12 | heapq.heappop(a) 13 | print(a) 14 | # push pop 15 | heapq.heappushpop(a, 9) 16 | print(a) 17 | # replace - pop push 18 | heapq.heapreplace(a, 10) 19 | print(a) 20 | # nlargest 21 | print(heapq.nlargest(2, a)) 22 | # nsmallest 23 | print(heapq.nsmallest(5, a)) 24 | # merge two unsorted arrays (returns heap) 25 | m = list(heapq.merge(a, [5, 3])) 26 | print(m) 27 | -------------------------------------------------------------------------------- /python/insertion_sort.py: -------------------------------------------------------------------------------- 1 | def insertion_sort(arr): 2 | n = len(arr) 3 | for i in range(n): 4 | key = arr[i] 5 | j = i - 1 6 | while(j >= 0 and arr[j] > key): 7 | arr[j + 1] = arr[j] 8 | j -= 1 9 | arr[j + 1] = key 10 | 11 | 12 | if __name__ == '__main__': 13 | arr = [int(i) for i in input().split(' ')] 14 | insertion_sort(arr) 15 | print(arr) 16 | 17 | 18 | # Time complexity - O(n^2) 19 | # Space complexity - O(1) 20 | # In place algorithm 21 | # Stable algorithm 22 | -------------------------------------------------------------------------------- /python/kosaraju.py: -------------------------------------------------------------------------------- 1 | # Strongly connected graph is a graph which has path from every vertex 2 | # to the other. 3 | # For example, a sink vertex is a SCC in itself. 4 | 5 | 6 | def dfs_util(node): 7 | global color, ft, finish_time 8 | color[node] = 'grey' 9 | for neighbour in adj_list[node]: 10 | if color[neighbour] == 'white': 11 | dfs_util(neighbour) 12 | 13 | color[node] = 'black' 14 | finish_time.append(node) 15 | ft += 1 16 | 17 | 18 | def get_transpose_graph(adj_list): 19 | transpose_adj_list = {} 20 | 21 | for source in adj_list: 22 | for destination in adj_list[source]: 23 | if not destination in transpose_adj_list: 24 | transpose_adj_list[destination] = {} 25 | transpose_adj_list[destination][source] = adj_list[source][destination] 26 | 27 | return transpose_adj_list 28 | 29 | 30 | if __name__ == '__main__': 31 | adj_list = { 32 | '0': {'2': 1, '3': 1}, 33 | '1': {'0': 1}, 34 | '2': {'1': 1}, 35 | '3': {'4': 1}, 36 | '4': {} 37 | } 38 | vertices = adj_list.keys() 39 | color = {} 40 | finish_time = [] 41 | for vertice in vertices: 42 | color[vertice] = 'white' 43 | 44 | ft = 0 45 | 46 | for vertice in vertices: 47 | if color[vertice] == 'white': 48 | dfs_util(vertice) 49 | 50 | adj_list = get_transpose_graph(adj_list) 51 | 52 | total_scc = 0 53 | 54 | for vertice in vertices: 55 | color[vertice] = 'white' 56 | 57 | for vertice in finish_time[::-1]: 58 | if color[vertice] == 'white': 59 | dfs_util(vertice) 60 | total_scc += 1 61 | 62 | print(total_scc) 63 | -------------------------------------------------------------------------------- /python/krushkal.py: -------------------------------------------------------------------------------- 1 | def find(x): 2 | while(parent[x] != x): 3 | x = parent[x] 4 | 5 | return x 6 | 7 | 8 | def union(x, y): 9 | x_parent = find(x) 10 | y_parent = find(y) 11 | 12 | if count[y_parent] > count[x_parent]: 13 | parent[x_parent] = y_parent 14 | count[y_parent] += count[x_parent] 15 | else: 16 | parent[y_parent] = x_parent 17 | count[x_parent] += count[y_parent] 18 | 19 | 20 | def krushkal(edges): 21 | 22 | min_cost = 0 23 | for weight, source, destination in edges: 24 | 25 | if find(source) != find(destination): 26 | min_cost += weight 27 | union(source, destination) 28 | 29 | return min_cost 30 | 31 | 32 | if __name__ == '__main__': 33 | 34 | adj_list = { 35 | 'U': {'V': 2, 'W': 5, 'X': 1}, 36 | 'V': {'U': 2, 'X': 2, 'W': 3}, 37 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 38 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 39 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 40 | 'Z': {'W': 5, 'Y': 1}, 41 | } 42 | 43 | edge_list = [] 44 | 45 | for source in adj_list: 46 | for destination in adj_list[source]: 47 | edge_list.append( 48 | (adj_list[source][destination], source, destination)) 49 | 50 | edge_list.sort() 51 | vertices = adj_list.keys() 52 | 53 | count = {} 54 | parent = {} 55 | 56 | for vertice in vertices: 57 | count[vertice] = 1 58 | parent[vertice] = vertice 59 | 60 | print(krushkal(edge_list)) 61 | -------------------------------------------------------------------------------- /python/linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | 7 | class LinkedList: 8 | 9 | def __init__(self): 10 | self.head = None 11 | 12 | def push(self, new_data): 13 | new_node = Node(new_data) 14 | new_node.next = self.head 15 | self.head = new_node 16 | 17 | def insert_after(self, prev_node, new_data): 18 | 19 | if not prev_node: 20 | return 21 | 22 | new_node = Node(new_data) 23 | new_node.next = prev_node.next 24 | prev_node.next = new_node 25 | 26 | def append(self, new_data): 27 | new_node = Node(new_data) 28 | if not self.head: 29 | self.head = new_node 30 | return 31 | 32 | last = self.head 33 | 34 | while(last.next): 35 | last = last.next 36 | 37 | last.next = new_node 38 | 39 | def print_list(self): 40 | temp = self.head 41 | while(temp): 42 | print(temp.data) 43 | temp = temp.next 44 | 45 | def delete_node(self, key): 46 | temp = self.head 47 | 48 | if temp and temp.data == key: 49 | self.head = temp.next 50 | temp = None 51 | return 52 | 53 | prev = None 54 | while(temp): 55 | if temp.data == key: 56 | break 57 | 58 | prev = temp 59 | temp = temp.next 60 | 61 | if not temp: 62 | return 63 | else: 64 | prev.next = temp.next 65 | temp = None 66 | 67 | 68 | if __name__ == '__main__': 69 | llist = LinkedList() 70 | llist.append(6) 71 | llist.push(7) 72 | llist.push(1) 73 | llist.append(4) 74 | llist.insert_after(llist.head.next, 8) 75 | print('Created linked list is:') 76 | llist.print_list() 77 | llist.delete_node(7) 78 | print('-') 79 | llist.print_list() 80 | -------------------------------------------------------------------------------- /python/merge_sort.py: -------------------------------------------------------------------------------- 1 | def merge_sort(arr): 2 | 3 | n = len(arr) 4 | 5 | if n <= 1: 6 | return 7 | 8 | mid = n // 2 9 | 10 | L = arr[:mid] 11 | R = arr[mid:] 12 | 13 | merge_sort(L) 14 | merge_sort(R) 15 | 16 | i = j = k = 0 17 | 18 | while(i < len(L) and j < len(R)): 19 | if L[i] <= R[j]: 20 | arr[k] = L[i] 21 | i += 1 22 | else: 23 | arr[k] = R[j] 24 | j += 1 25 | k += 1 26 | 27 | while(i < len(L)): 28 | arr[k] = L[i] 29 | i += 1 30 | k += 1 31 | 32 | while(j < len(R)): 33 | arr[k] = R[j] 34 | j += 1 35 | k += 1 36 | 37 | 38 | if __name__ == '__main__': 39 | arr = [int(i) for i in input().split(' ')] 40 | merge_sort(arr) 41 | print(arr) 42 | 43 | 44 | # Time complexity - O(n lg n) 45 | # Space complexity - O(n) 46 | # Not In place algorithm 47 | # Stable algorithm 48 | -------------------------------------------------------------------------------- /python/monotonic_stack.py: -------------------------------------------------------------------------------- 1 | # Monotonic stack is basically a stack, which maintains the list of elements 2 | # from left to right in a sorted order. It's good for problems like 3 | # next/previous greatest/smallest element/range query. (e.g. left limit/right limit) 4 | # E.g. Given an array of integers heights representing the histogram's bar height 5 | # where the width of each bar is 1, return the area of the largest rectangle in the histogram. 6 | 7 | def calc_prev_smaller(arr): 8 | ''' 9 | Calculates prev smaller for each index 10 | ''' 11 | prev_smaller = [] 12 | stack = [] 13 | for idx, _ in enumerate(arr): 14 | while (stack and arr[stack[-1]] >= arr[idx]): 15 | stack.pop() 16 | 17 | if not stack: 18 | prev_smaller.append(-1) 19 | else: 20 | prev_smaller.append(idx) 21 | 22 | stack.append(idx) 23 | 24 | return prev_smaller 25 | 26 | 27 | def calc_next_smaller(arr): 28 | ''' 29 | Calculates next smaller for each index 30 | ''' 31 | next_smaller = [] 32 | stack = [] 33 | for idx, _ in enumerate(arr): 34 | while (stack and arr[stack[-1]] >= arr[idx]): 35 | stack.pop() 36 | 37 | if not stack: 38 | next_smaller.append(len(arr)) 39 | else: 40 | next_smaller.append(idx) 41 | 42 | stack.append(idx) 43 | 44 | return next_smaller 45 | 46 | 47 | def cal_max_histogram_area(arr): 48 | ''' 49 | Basic idea is to pop elements until we find an element equal or more than 50 | its value. Then push the element. Such that an increasing order is maintained. 51 | We can perform action at every pop. 52 | ''' 53 | 54 | max_area = 0 55 | prev_smaller = calc_prev_smaller(arr) 56 | next_smaller = calc_next_smaller(arr) 57 | for i, _ in enumerate(arr): 58 | area = arr[i] * (next_smaller[i] - prev_smaller[i] - 1) 59 | max_area = max(max_area, area) 60 | 61 | return max_area 62 | 63 | 64 | if __name__ == '__main__': 65 | A = [2, 1, 5, 6, 2, 3] 66 | print(cal_max_histogram_area(A)) 67 | -------------------------------------------------------------------------------- /python/old_new_state.py: -------------------------------------------------------------------------------- 1 | # Template - Very commonly used in DP 2 | # def process_old(i, old, new): 3 | # pass 4 | 5 | # def old_new_state(arr): 6 | # old, new = 0, 1 7 | # for i in range(len(arr)): 8 | # old, new = new, process_old(i, old, new) 9 | 10 | # Calculate fibonacci number iteratively. 11 | 12 | def fibonacci(n): 13 | old, new = 0, 1 14 | for _ in range(n + 1): 15 | old, new = new, old + new 16 | 17 | return old 18 | 19 | 20 | if __name__ == '__main__': 21 | print(fibonacci(4)) 22 | -------------------------------------------------------------------------------- /python/prim.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | 4 | def prim(adj_list, vertices, source): 5 | 6 | visited = set() 7 | min_cost = 0 8 | priority_queue = [] 9 | 10 | for destination in adj_list[source]: 11 | heapq.heappush( 12 | priority_queue, 13 | (adj_list[source][destination], destination) 14 | ) 15 | 16 | visited.add(source) 17 | 18 | while(len(visited) < len(vertices)): 19 | 20 | weight, source = heapq.heappop(priority_queue) 21 | 22 | if source in visited: 23 | continue 24 | 25 | min_cost += weight 26 | visited.add(source) 27 | 28 | for destination in adj_list[source]: 29 | 30 | heapq.heappush( 31 | priority_queue, (adj_list[source][destination], destination)) 32 | 33 | return min_cost 34 | 35 | 36 | if __name__ == '__main__': 37 | 38 | adj_list = { 39 | 'U': {'V': 2, 'W': 5, 'X': 1}, 40 | 'V': {'U': 2, 'X': 2, 'W': 3}, 41 | 'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5}, 42 | 'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1}, 43 | 'Y': {'X': 1, 'W': 1, 'Z': 1}, 44 | 'Z': {'W': 5, 'Y': 1}, 45 | } 46 | 47 | vertices = adj_list.keys() 48 | 49 | print(prim(adj_list, vertices, source='U')) 50 | -------------------------------------------------------------------------------- /python/queue.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | # Queue is the abstreact data structure 5 | # which is used to order elements in FIFO order. 6 | 7 | if __name__ == '__main__': 8 | queue = deque() 9 | arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] 10 | for el in arr: 11 | queue.append(el) 12 | 13 | while(queue): 14 | print(queue.popleft()) 15 | -------------------------------------------------------------------------------- /python/quick_select.py: -------------------------------------------------------------------------------- 1 | def partition(arr, low, high): 2 | pivot = arr[high] 3 | i = low - 1 4 | for j in range(low, high): 5 | 6 | if arr[j] <= pivot: 7 | i += 1 8 | arr[i], arr[j] = arr[j], arr[i] 9 | 10 | i += 1 11 | arr[i], arr[high] = arr[high], arr[i] 12 | return i 13 | 14 | 15 | def quick_select(arr, low, high, k): 16 | 17 | if k < 0 or k > high - low + 1: 18 | return 19 | 20 | pivot = partition(arr, low, high) 21 | if pivot - low == k - 1: 22 | return arr[pivot] 23 | elif pivot - low > k - 1: 24 | return quick_select(arr, low, pivot - 1, k) 25 | else: 26 | return quick_select(arr, pivot + 1, high, k + low - (pivot + 1)) 27 | 28 | 29 | if __name__ == '__main__': 30 | arr = [10, 4, 5, 8, 6, 11, 26] 31 | print(quick_select(arr, 0, len(arr) - 1, 3)) 32 | -------------------------------------------------------------------------------- /python/quick_sort.py: -------------------------------------------------------------------------------- 1 | def partition(arr, low, high): 2 | pivot = arr[high] 3 | 4 | i = low - 1 5 | 6 | for j in range(low, high): 7 | 8 | if arr[j] <= pivot: 9 | i += 1 10 | arr[i], arr[j] = arr[j], arr[i] 11 | 12 | i += 1 13 | arr[i], arr[high] = arr[high], arr[i] 14 | return i 15 | 16 | 17 | def quick_sort(arr, low, high): 18 | 19 | if low >= high: 20 | return 21 | 22 | p = partition(arr, low, high) 23 | 24 | quick_sort(arr, low, p - 1) 25 | quick_sort(arr, p + 1, high) 26 | 27 | 28 | if __name__ == '__main__': 29 | arr = [int(i) for i in input().split(' ')] 30 | quick_sort(arr, 0, len(arr) - 1) 31 | print(arr) 32 | 33 | 34 | # Time complexity - O(n lg n) 35 | # Space complexity - O(1) 36 | # In place algorithm 37 | # Not Stable algorithm 38 | -------------------------------------------------------------------------------- /python/recursion.py: -------------------------------------------------------------------------------- 1 | # General pattern of recursion 2 | # 1. There are one or more base cases that are directly solvable without the need for further recursion. 3 | # 2. Each recursive call moves the solution progressively closer to a base case. 4 | 5 | def fact(n): 6 | if n == 1: 7 | return 1 8 | else: 9 | return n * fact(n - 1) 10 | 11 | 12 | # Writing recursive function: 13 | # 1. First figure out base cases and build upon that. 14 | # 2. Figure out recursive steps. Remember, the subproblem should be smaller and eventually reach to some base case. 15 | if __name__ == '__main__': 16 | print(fact(9)) 17 | -------------------------------------------------------------------------------- /python/rolling_hash.py: -------------------------------------------------------------------------------- 1 | # The purpose of rolling hash is to store the string efficiently 2 | # in a manner that we can reduce the time of string matching to 3 | # linear time complexity. If we use brute force or naive approach, 4 | # the time complexity is O(n^2). 5 | # Checkout - https://codeforces.com/blog/entry/60445 6 | # Hash matching never guarantees string exists as substring. We need to 7 | # perform the action to ensure that hypothesis actually exists. 8 | # Note - 9 | # 1. It's a good time to revise some basic properties about modulus. 10 | # https://math.stackexchange.com/questions/147140/what-are-the-properties-of-the-modulus 11 | 12 | 13 | def count_rolling_hash(s1, s2): 14 | 15 | P1 = 51 16 | P2 = 57 17 | m1 = 2 ** 32 18 | m2 = 2 ** 31 - 1 19 | l1_hash_val = 0 20 | l2_hash_val = 0 21 | n = len(s1) 22 | check_l1_hash_val = 0 23 | check_l2_hash_val = 0 24 | count = 0 25 | 26 | for idx, c in enumerate(s1): 27 | l1_hash_val += (((ord(c) - ord('a') + 1) * (P1 ** idx))) % m1 28 | l2_hash_val += (((ord(c) - ord('a') + 1) * (P2 ** idx))) % m2 29 | 30 | for idx, c in enumerate(s2[:n]): 31 | check_l1_hash_val += (((ord(c) - ord('a') + 1) * (P1 ** idx))) % m1 32 | check_l2_hash_val += (((ord(c) - ord('a') + 1) * (P2 ** idx))) % m2 33 | 34 | if check_l1_hash_val == l1_hash_val and check_l2_hash_val == l2_hash_val: 35 | for i in range(n): 36 | if s1[i] != s2[i]: 37 | break 38 | else: 39 | count += 1 40 | 41 | for idx, c in enumerate(s2[n:]): 42 | comp1 = ((ord(c) - ord('a') + 1) * (P1 ** n)) % m1 43 | comp2 = ((ord(s2[idx]) - ord('a') + 1)) % m1 44 | check_l1_hash_val = ((check_l1_hash_val - comp2 + comp1) // P1) % m1 45 | comp1 = ((ord(c) - ord('a') + 1) * (P2 ** n)) % m2 46 | comp2 = ((ord(s2[idx]) - ord('a') + 1)) % m2 47 | 48 | check_l2_hash_val = ((check_l2_hash_val - comp2 + comp1) // P2) % m2 49 | 50 | if check_l1_hash_val == l1_hash_val and check_l2_hash_val == l2_hash_val: 51 | for i in range(n): 52 | if s1[i] != s2[n:][i - n + 1 + idx]: 53 | break 54 | else: 55 | count += 1 56 | 57 | return count 58 | 59 | 60 | if __name__ == '__main__': 61 | 62 | print(count_rolling_hash('ach', 'achintach')) 63 | -------------------------------------------------------------------------------- /python/selection_sort.py: -------------------------------------------------------------------------------- 1 | def selection_sort(arr): 2 | n = len(arr) 3 | 4 | for i in range(n): 5 | min_idx = i 6 | for j in range(i + 1, n): 7 | if arr[min_idx] > arr[j]: 8 | min_idx = j 9 | 10 | arr[min_idx], arr[i] = arr[i], arr[min_idx] 11 | 12 | 13 | if __name__ == '__main__': 14 | arr = [int(i) for i in input().split(' ')] 15 | selection_sort(arr) 16 | print(arr) 17 | 18 | # Time complexity - O(n^2) 19 | # Space complexity - O(1) 20 | # In place algorithm 21 | # Stable algorithm 22 | -------------------------------------------------------------------------------- /python/sieve_of_eratosthenes.py: -------------------------------------------------------------------------------- 1 | # Seive of eraosthenes is used to get prime numbers uptil number n. 2 | # There are two central ideas here: 3 | # 1. We can loop over multiple of prime number encountered to mark them as non-prime, incrementally. 4 | # 2. We only need to check the multiples till square root of n. 5 | # 6 | # https://www.geeksforgeeks.org/sieve-of-eratosthenes/ 7 | 8 | 9 | def soe(n): 10 | is_prime = [True for _ in range(n + 1)] 11 | is_prime[0] = is_prime[1] = False 12 | 13 | for p in range(2, int(n**0.5) + 1): 14 | 15 | if is_prime[p]: 16 | for k in range(p * 2, n + 1, p): 17 | is_prime[k] = False 18 | 19 | return is_prime 20 | 21 | 22 | if __name__ == '__main__': 23 | for idx, k in enumerate(soe(1000)): 24 | if k: 25 | print(idx) 26 | -------------------------------------------------------------------------------- /python/sliding_window.py: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | # end moves in outer template while, start moves in inner template 4 | # def some_start_condition(s, e): 5 | # pass 6 | 7 | # def process_logic_2(s, e): 8 | # pass 9 | 10 | # def process_logic_1(s, e): 11 | # pass 12 | 13 | # def sliding_window(arr): 14 | 15 | # n = len(arr) 16 | # start = end = 0 17 | # while(end < n): 18 | # end += 1 19 | # while(some_start_condition(start, end)): 20 | # process_logic_1(start, end) 21 | # start += 1 22 | 23 | # process_logic_2(start, end) 24 | 25 | 26 | # Given a string S and a string T, find the minimum window in S which will contain all the characters in T. 27 | 28 | from collections import Counter 29 | 30 | 31 | def find_min_substring(s, t): 32 | start = end = 0 33 | counter = Counter(t) 34 | count = len(t) 35 | res = float('inf') 36 | while(end < len(s)): 37 | counter[s[end]] -= 1 38 | 39 | if counter[s[end]] >= 0: 40 | count -= 1 41 | end += 1 42 | 43 | while(count == 0): 44 | if end - start < res: 45 | res = end - start 46 | 47 | counter[s[start]] += 1 48 | if counter[s[start]] > 0: 49 | count += 1 50 | start += 1 51 | 52 | return res 53 | 54 | 55 | if __name__ == '__main__': 56 | s = "ADOBECODEBANC" 57 | t = "ABC" 58 | print(find_min_substring(s, t)) 59 | -------------------------------------------------------------------------------- /python/stack.py: -------------------------------------------------------------------------------- 1 | # We assume we don't have any stack capacity 2 | stack = [] 3 | # Push to stack 4 | stack.append(12) 5 | stack.append(23) 6 | stack.append(43) 7 | stack.append(63) 8 | stack.append(15) 9 | # Stack order from right to left 10 | print(stack) 11 | 12 | # Check the top element 13 | top = stack[-1] 14 | print(top) 15 | 16 | # General way to pop 17 | if(stack): 18 | print(stack.pop()) 19 | 20 | print(stack) 21 | 22 | # Empty the stack 23 | while(stack): 24 | stack.pop() 25 | 26 | print(stack) 27 | 28 | if(stack): 29 | print(stack.pop()) 30 | -------------------------------------------------------------------------------- /python/top_down_dp.py: -------------------------------------------------------------------------------- 1 | # Dynamic programming looks like backtracking problem, just that, 2 | # some subproblems are getting repeated, i.e., some states occur again 3 | # and again, so we need to avoid these repetition. Moreover, the solution 4 | # to a problem at some state, depends on the solution of sub-problems, i.e., checking 5 | # all the solutions from that state. In short, there are two properties: 6 | # 7 | # 1. Repetitions 8 | # 2. For global optimal solution at some state, we need to 9 | # check entire state sub-problem space orginating from that state. Sub-problem looks 10 | # similar to the original problem. 11 | # 12 | # How to approach? 13 | # 1. Define the state. The state should similar at each stage, so that it can be solved repetition. 14 | # Also, define a sequence to reduce dimension and make solution more organized. 15 | # 2. Store the result of some state, to avoid repetitions. 16 | # 17 | # 0-1 Knapsack problem: 18 | # 19 | # 20 | # Given weights and values of n items, put these items in a knapsack of capacity W to get 21 | # the maximum total value in the knapsack. 22 | 23 | 24 | from functools import cache 25 | 26 | 27 | # DP(we are storing results of a subproblem) 28 | 29 | # A state is defined as knapsack with capacity C and the items starting from 30 | # the index idx. 31 | @cache 32 | def knapsack(capacity, curr_item_idx): 33 | 34 | if capacity == 0 or curr_item_idx == total_items: 35 | return 0 36 | 37 | if capacity - total_wt[curr_item_idx] < 0: 38 | return knapsack(capacity, curr_item_idx + 1) 39 | 40 | return max( 41 | total_val[curr_item_idx] + 42 | knapsack(capacity - total_wt[curr_item_idx], curr_item_idx + 1), 43 | knapsack(capacity, curr_item_idx + 1) 44 | ) 45 | 46 | 47 | if __name__ == "__main__": 48 | total_wt = [10, 40, 20, 30] 49 | total_val = [60, 40, 100, 120] 50 | total_items = len(total_wt) 51 | cap = 50 52 | print(knapsack(cap, 0)) 53 | -------------------------------------------------------------------------------- /python/topological_sort.py: -------------------------------------------------------------------------------- 1 | # Topological sort, basically sorts vertices in a manner, such that, 2 | # if u comes before v, then either it's not connected, or if its' connected in graph, 3 | # there is an edge from u to v. 4 | # Key points - 5 | # 6 | # Topological sort works only on Directed Acyclic Graph(DAG). 7 | 8 | def dfs_util(node): 9 | global color, finished 10 | 11 | color[node] = 'grey' 12 | for neighbour in adj_list[node]: 13 | if color[neighbour] == 'white': 14 | dfs_util(neighbour) 15 | color[node] = 'black' 16 | finished.append(node) 17 | 18 | 19 | if __name__ == '__main__': 20 | adj_list = {'a': {'b': 1, 'd': 1}, 'b': {'c': 1}, 'd': { 21 | 'e': 1}, 'c': {'e': 1, 'd': 1}, 'f': {}, 'e': {}} 22 | vertices = adj_list.keys() 23 | color = {} 24 | for vertice in vertices: 25 | color[vertice] = 'white' 26 | 27 | finished = [] 28 | for vertice in vertices: 29 | if color[vertice] == 'white': 30 | dfs_util(vertice) 31 | 32 | print(finished) 33 | -------------------------------------------------------------------------------- /python/traversal.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | # Application: 12 | # 1. Gives element in non-decreasing order 13 | 14 | 15 | def inorder(root): 16 | if not root: 17 | return 18 | inorder(root.left) 19 | print(root.val) 20 | inorder(root.right) 21 | 22 | # Application: 23 | # 1. To copy the tree. 24 | # 2. To get prefix of expression tree 25 | 26 | 27 | def preorder(root): 28 | if not root: 29 | return 30 | print(root.val) 31 | preorder(root.left) 32 | preorder(root.right) 33 | 34 | 35 | # Application: 36 | # 1. To delete the tree. 37 | # 2. To get postfix of expression tree 38 | 39 | 40 | def postorder(root): 41 | if not root: 42 | return 43 | postorder(root.left) 44 | postorder(root.right) 45 | print(root.val) 46 | 47 | 48 | # Specs: Unlike all traversal above, it doesn't follow DFS 49 | # Application: 50 | # 1. To perform some operation in level order 51 | 52 | def levelorder(root): 53 | queue = deque() 54 | queue.append(root) 55 | while(queue): 56 | node = queue.popleft() 57 | 58 | print(node.val) 59 | 60 | if node.left: 61 | queue.append(node.left) 62 | 63 | if node.right: 64 | queue.append(node.right) 65 | 66 | 67 | if __name__ == '__main__': 68 | 69 | root = Node(1) 70 | root.left = Node(2) 71 | root.right = Node(3) 72 | root.left.left = Node(4) 73 | root.left.right = Node(5) 74 | 75 | print('In order') 76 | inorder(root) 77 | print('Pre order') 78 | preorder(root) 79 | print('Post order') 80 | postorder(root) 81 | print('Level order') 82 | levelorder(root) 83 | -------------------------------------------------------------------------------- /python/trie.py: -------------------------------------------------------------------------------- 1 | # Inspired by blog: 2 | # https://towardsdatascience.com/implementing-a-trie-data-structure-in-python-in-less-than-100-lines-of-code-a877ea23c1a1 3 | 4 | class TrieNode: 5 | 6 | def __init__(self, ch): 7 | self.ch = ch 8 | self.children = [] 9 | self.word_finished = False 10 | self.counter = 1 11 | 12 | 13 | def add(root, word): 14 | 15 | node = root 16 | for c in word: 17 | ch_found = False 18 | for child in node.children: 19 | if child.ch == c: 20 | child.counter += 1 21 | node = child 22 | ch_found = True 23 | break 24 | 25 | if not ch_found: 26 | child = TrieNode(c) 27 | node.children.append(child) 28 | node = child 29 | 30 | node.word_finished = True 31 | 32 | 33 | def find_prefix(root, prefix): 34 | 35 | node = root 36 | 37 | for ch in prefix: 38 | ch_found = False 39 | 40 | for child in node.children: 41 | if child.ch == ch: 42 | ch_found = True 43 | break 44 | 45 | if not ch_found: 46 | return False, 0 47 | 48 | node = child 49 | 50 | return True, node.counter 51 | 52 | 53 | def word_exists(root, word): 54 | 55 | node = root 56 | 57 | for c in word: 58 | ch_found = False 59 | 60 | for child in node.children: 61 | if child.ch == c: 62 | ch_found = True 63 | node = child 64 | break 65 | 66 | if not ch_found: 67 | return False 68 | 69 | return child.word_finished 70 | 71 | 72 | if __name__ == '__main__': 73 | root = TrieNode('*') 74 | add(root, "hackathon") 75 | add(root, 'hack') 76 | 77 | print(find_prefix(root, 'hac')) 78 | print(find_prefix(root, 'hack')) 79 | print(find_prefix(root, 'hackathon')) 80 | print(find_prefix(root, 'ha')) 81 | print(find_prefix(root, 'hammer')) 82 | print(word_exists(root, 'hammer')) 83 | print(word_exists(root, 'hackathon')) 84 | print(word_exists(root, 'hack')) 85 | --------------------------------------------------------------------------------