├── .gitignore ├── 01. Resources ├── BigO-cheat-sheet.pdf ├── Books │ ├── Data Structures - Reema Thareja.pdf │ └── competitiveCoding.pdf ├── DAA Syllabus.pdf ├── Interview cheatsheet.pdf ├── Master Plan.pdf └── Master_the_Interview.pdf ├── 02. Big-O ├── O(1).py ├── O(m + n).py ├── O(m x n).py ├── O(n).py └── O(n^2).py ├── 03. Data Structures ├── Arrays │ ├── Contains_Duplicate.py │ ├── Implementation.py │ ├── Introduction.py │ ├── Longest_Word.py │ ├── Maximum_SubArray.py │ ├── Merging_sorted_arrays.py │ ├── Move_Zeroes.py │ ├── Reversing_String.py │ └── Rotate_Array.py ├── Graphs │ └── Undirected_Graph_Implementation.py ├── Hash Tables │ ├── First_Recurring_Character.py │ ├── Implementation.py │ ├── Introduction.py │ ├── Legacy │ │ └── hash_algo.py │ └── Pattern_Matching.py ├── Linked Lists │ ├── Doubly_Linked_Lists.py │ ├── Implementation.py │ ├── Legacy │ │ └── linked_list_template.py │ └── Reverse.py ├── Queues │ ├── LEARN │ ├── Legacy │ │ └── queue_demo.py │ ├── Linked_List_Implementation.py │ └── Queue_Using_Stacks.py ├── Stacks │ ├── Array_Implementation.py │ ├── Legacy │ │ └── stack_demo.py │ └── Linked_List_Implementation.py └── Trees │ ├── Binary_Search_Tree.py │ ├── Heap.py │ ├── Legacy │ └── bst_demo.py │ ├── Priority_Queues_Using_Heap.py │ └── Trie.py ├── 04. Algorithms ├── Divide and Conquer │ └── karatsuba_multiplication.py ├── Dynamic Programming │ ├── Fibonacci.py │ └── Memoization.py ├── Recursion │ ├── Factorial.py │ ├── Fibonacci.py │ └── Reverse_String.py ├── Searching │ └── Alt_Binary_Search.py ├── Sorting │ ├── Bubble_Sort.py │ ├── Heap_Sort.py │ ├── Insertion_Sort.py │ ├── Merge_Sort.py │ ├── Quick_Sort.py │ ├── Selection_Sort.py │ └── legacy │ │ ├── bubble_sort.py │ │ ├── insertion_sort.py │ │ ├── merge_sort.py │ │ ├── quick_sort.py │ │ └── selection_sort.py └── Traversals │ ├── BFS.py │ ├── DFS.py │ ├── bisection_iter.py │ └── bisection_recur.py ├── 05. File Handling and OOPS ├── data.txt └── file+classes.py ├── 06. Error Handling ├── error_handling.py ├── error_handling2.py ├── raise_error.py └── raise_error2.py ├── 07. Functional Programming ├── dict_comprehension.py ├── enumerate.py ├── filter().py ├── lambda_expressions.py ├── lambda_expressions2.py ├── list_set_comprehension.py ├── map().py ├── pure_functions.py ├── reduce().py └── zip().py ├── 08. Decorators ├── HOC.py ├── authentication.py ├── decorators.py ├── decorators0.py ├── decorators1.py ├── decorators2.py └── performance_decorator.py ├── 09. Debugging └── dubugging_pdb.py ├── 10. Generators ├── fibonacci_list.py ├── fibonacci_range.py ├── generator.py ├── our_own_forloop.py └── our_own_range.py ├── 11. Regular Expressions ├── email_password_regex.py └── regular_exp.py ├── 12. Unit Testing ├── guess_game.py ├── script.py ├── test.py ├── test2.py └── test_guess_game.py ├── 13. Mini-Projects ├── Job Scheduler │ ├── bst_demo.py │ ├── data.txt │ └── job_scheduler.py ├── data.txt ├── email_project │ ├── demo.py │ └── run.py ├── hash_project │ ├── hash_algo.py │ ├── project_script.py │ └── tempCodeRunnerFile.py ├── planeSimulator.py ├── recursion_miniprojects │ ├── countdown_timer.py │ ├── factorial.py │ ├── fibonacci.py │ ├── multi_tasker.py │ └── tempCodeRunnerFile.py └── runtime_analyser │ ├── analyser.py │ └── sorts.py ├── 14. Questions ├── barclays plc uk - online assesment.py ├── leetcode 01 - two sum.py ├── leetcode 02 - add two numbers.py ├── leetcode 03 - longest substring without repeating characters.py ├── leetcode 09 - palindrome number.py ├── leetcode 100 - same tree.py ├── leetcode 101 - symmetric tree.py ├── leetcode 1011 - capacity to ship packages.py ├── leetcode 102 - level order traversal of tree.py ├── leetcode 1022 - sum of root-leaf binary num.py ├── leetcode 103 - zigzag level order traversal.py ├── leetcode 104 - max depth of binary tree.py ├── leetcode 108 - sorted array to bst.py ├── leetcode 110 - balanced bst.py ├── leetcode 112 - path sum.py ├── leetcode 114 - binary tree preorder traversal.py ├── leetcode 1147 - largest number at least twice of others.py ├── leetcode 121 - best time to buy and sell stock.py ├── leetcode 128 - longest consecutive sequence.py ├── leetcode 13 - roman to integer.py ├── leetcode 1302 - deepest leaves sum.py ├── leetcode 1305 - all elements in two binary search trees.py ├── leetcode 133 - clone graph.py ├── leetcode 1379 - corresponding target in cloned tree.py ├── leetcode 1382 - balance a bst.py ├── leetcode 14 - longest common prefix.py ├── leetcode 15 - three sum.py ├── leetcode 162 - peak element.py ├── leetcode 167 - two sum II.py ├── leetcode 1971 - find if path exists in a graph.py ├── leetcode 1991 - find the middle index in array.py ├── leetcode 20 - valid parentheses.py ├── leetcode 207 - course schedule.py ├── leetcode 21 - merge two sorted lists.py ├── leetcode 217 - contains duplicates.py ├── leetcode 226 - invert binary tree.py ├── leetcode 2265 - count nodes equal to average of subtree.py ├── leetcode 2306 - naming a company.py ├── leetcode 2331 - evaluate boolean binary tree.py ├── leetcode 234 - palindrome linked list.py ├── leetcode 235 - lowest common ancestor in bst.py ├── leetcode 238 - product of array except self.py ├── leetcode 257 - binary tree paths.py ├── leetcode 26 - remove duplicates from sorted array.py ├── leetcode 27 - remove element.py ├── leetcode 28 - implement strStr().py ├── leetcode 28 - index of first occurrence.py ├── leetcode 290 - word pattern.py ├── leetcode 347 - top k frequent elements.py ├── leetcode 35 - search insert position.py ├── leetcode 350 - intersection of two arrays II.py ├── leetcode 36 - valid sudoku.py ├── leetcode 424 - longest repeating character replacement.py ├── leetcode 427 - construct quad tree.py ├── leetcode 435 - non-overlapping intervals.py ├── leetcode 438 - find all anagrams in string.py ├── leetcode 443 - string compression.py ├── leetcode 48 - rotate image (by rotation).py ├── leetcode 48 - rotate image (by transpose).py ├── leetcode 49 - group anagrams.py ├── leetcode 494 - target sum.py ├── leetcode 502 - ipo.py ├── leetcode 53 - maximum subarray.py ├── leetcode 530 - minimum absolute difference in BST.py ├── leetcode 540 - single element in a sorted array.py ├── leetcode 559 - max depth of n-ary tree.py ├── leetcode 58 - length of last word.py ├── leetcode 590 - n-ary tree postorder.py ├── leetcode 598 - n-ary tree preorder.py ├── leetcode 617 - merge binary trees.py ├── leetcode 637 - avg of levels in binary tree.py ├── leetcode 653 - two sum with binary tree.py ├── leetcode 66 - plus one.py ├── leetcode 67 - add binary.py ├── leetcode 69 - sqrt(x).py ├── leetcode 70 - climbing stairs.py ├── leetcode 705 - hash set.py ├── leetcode 724 - find pivot index.py ├── leetcode 783 - minimum distance between bst nodes.py ├── leetcode 797 - all paths from source to target.py ├── leetcode 83 - remove duplicates from sorted list.py ├── leetcode 872 - leaf-similar trees.py ├── leetcode 88 - merger sort array.py ├── leetcode 897 - increasing order bst.py ├── leetcode 94 - binary tree inorder traversal.py ├── leetcode 944 - delete columns to make sorted.py ├── leetcode 96 - unique binary search trees.py ├── leetcode 98 - validate binary search tree.py ├── leetcode 989 - add to array form of integer.py └── leetcode 997 - find the town judge.py ├── DSA_Cheatsheet.pdf ├── Python_Cheatsheet.pdf ├── Python_Cheatsheet_2.pdf ├── loggerMiddleware.js ├── readme.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __pycache__/ 3 | .DS_Store 4 | 14. Questions/code.py 5 | 14. Questions/temp.py 6 | 14. Questions/tempCodeRunnerFile.py -------------------------------------------------------------------------------- /01. Resources/BigO-cheat-sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/BigO-cheat-sheet.pdf -------------------------------------------------------------------------------- /01. Resources/Books/Data Structures - Reema Thareja.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/Books/Data Structures - Reema Thareja.pdf -------------------------------------------------------------------------------- /01. Resources/Books/competitiveCoding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/Books/competitiveCoding.pdf -------------------------------------------------------------------------------- /01. Resources/DAA Syllabus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/DAA Syllabus.pdf -------------------------------------------------------------------------------- /01. Resources/Interview cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/Interview cheatsheet.pdf -------------------------------------------------------------------------------- /01. Resources/Master Plan.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/Master Plan.pdf -------------------------------------------------------------------------------- /01. Resources/Master_the_Interview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/01. Resources/Master_the_Interview.pdf -------------------------------------------------------------------------------- /02. Big-O/O(1).py: -------------------------------------------------------------------------------- 1 | #O(1) - Constant Time 2 | #The no. of operations do not depend on the size of the input and are always constant. 3 | import time 4 | 5 | array_small = ['nemo' for i in range(10)] 6 | array_medium = ['nemo' for i in range(100)] 7 | array_large = ['nemo' for i in range(10000)] 8 | 9 | def finding_nemo(array): 10 | t0 = time.time() 11 | for i in array: 12 | pass 13 | t1 = time.time() 14 | print(f'Time taken = {t1-t0}') 15 | 16 | finding_nemo(array_small) 17 | finding_nemo(array_medium) 18 | finding_nemo(array_large) 19 | 20 | #Time taken in all 3 cases would be 0.0 seconds because we are only extracting the first and second elements of the arays. 21 | #We are not looping over the entire array. 22 | #We are performing two O(1) operations, which equal to O(2) 23 | #Any constant number can be considered as 1. There we can say this function is of O(1) - Constant Time Complexity. 24 | 25 | -------------------------------------------------------------------------------- /02. Big-O/O(m + n).py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | large1 = ['nemo' for i in range(100000)] 4 | large2 = ['nemo' for i in range(100000)] 5 | 6 | def find_nemo(array1, array2): 7 | 8 | #Here there are two different variables array1 and array2. 9 | #They have to be represented by 2 different variables in the Big-O representation as well. 10 | #Let array1 correspond to m and array2 correspond to n 11 | 12 | t0 = time.time() #O(1) 13 | for i in range(0,len(array1)): #O(m) 14 | if array1[i] == 'nemo': #m*O(1) 15 | print("Found Nemo!!") #k1*O(1) where k1 <= m because this statement will be executed only if the if statement returns True, which can be k1(<=m) times 16 | t1 = time.time() #O(1) 17 | print(f'The search took {t1-t0} seconds.') #O(1) 18 | 19 | t0 = time.time() #O(1) 20 | for i in range(0, len(array2)): #O(n) 21 | if array2[i] == 'nemo': #n*O(1) 22 | print("Found Nemo!!") #k2*O(1) where k2 <= m because this statement will be executed only if the if statement returns True, which can be k2(<=m) times 23 | t1 = time.time() #O(1) 24 | print(f'The search took {t1 - t0} seconds.') #O(1) 25 | 26 | find_nemo(large1, large2) 27 | 28 | #Total time complexity of the find_nemo function = 29 | #O(1 + m + m*1 + k1*1 + 1 + 1 + 1 + n + n*1 + k2*1 + 1 + 1) = O(6 + 2m + 2n + k1 + k2) 30 | #Now k1<=m and k2<=n. In the worst case, k1 can be m and k2 can be n. We'll consider the worst case and calculate the Big-O 31 | #O(6 + 2m + 2n + m + n) = O(3m + 3n + 6) = O(3(m + n + 2)) 32 | #The constants can be safely ignored. 33 | #Therefore, O(m + n + 2) = O(m + n) 34 | -------------------------------------------------------------------------------- /02. Big-O/O(m x n).py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | array1 = ['a','b','c','d','e'] 4 | array2 = [1,2,3,4,5] 5 | 6 | def pairs(array1, array2): 7 | 8 | # Here there are two different variables array1 and array2. 9 | # They have to be represented by 2 different variables in the Big-O representation as well. 10 | # Let array1 correspond to m and array2 correspond to n 11 | 12 | for i in range(len(array1)): #n*O(m) 13 | for j in range(len(array2)): #m*O(n) 14 | print(array1[i],array2[j]) #m*n*O(1) 15 | 16 | pairs(array1,array2) 17 | 18 | #Total time complexity of the pairs function = 19 | #O(n*m + m*n + m*n*1) = O(3*m*n) 20 | #The constants can be safely ignored. 21 | #Therefore, O(m * n * 3) = O(m * n) 22 | 23 | -------------------------------------------------------------------------------- /02. Big-O/O(n).py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | nemo = ['nemo'] 4 | everyone = ['dory', 'bruce', 'marlin', 'nemo', 'gill', 'bloat', 'nigel', 'squirt', 'darla'] 5 | large = ['nemo' for i in range(100000)] 6 | def find_nemo(array): 7 | t0 = time.time() 8 | for i in range(0,len(array)): 9 | if array[i] == 'nemo': 10 | print("Found Nemo!!") 11 | t1 = time.time() 12 | print(f'The search took {t1-t0} seconds.') 13 | find_nemo(nemo) 14 | find_nemo(everyone) 15 | find_nemo(large) 16 | 17 | 18 | def funchallenge(input): 19 | temp = 10 #O(1) 20 | temp = temp +50 #O(1) 21 | for i in range(len(input)): #O(n) 22 | var = True #n*O(1) 23 | temp += 1 #n*O(1) 24 | return temp #O(1) 25 | 26 | funchallenge(nemo) 27 | funchallenge(everyone) 28 | funchallenge(large) 29 | 30 | #Total running time of the funchallenge function = 31 | #O(1 + 1 + n + n*1 + n*1 + n*1 + 1) = O(3n +3) = O(3(n+1)) 32 | #Any constant in the Big-O representation can be replaced by 1, as it doesn't really matter what constant it is. 33 | #Therefore, O(3(n+1)) becomes O(n+1) 34 | #Similarly, any constant number added or subtracted to n or multiplied or divided by n can also be safely written as just n 35 | #This is because the constant that operates upon n, doesn't depend on n, i.e., the input size 36 | #Therefore, the funchallenge function can be said to be of O(n) or Linear Time Complexity. -------------------------------------------------------------------------------- /02. Big-O/O(n^2).py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | array = ['a','b','c','d','e'] 4 | 5 | def log_all_pairs(array): 6 | 7 | #There are nested for loops in this function but there is only one variable array. So we don't need two variables for the Big-O 8 | 9 | for i in range(len(array)): #n*O(n) 10 | for j in range(len(array)): #n*O(n) 11 | print(array[i], array[j]) #n*n*O(1) 12 | 13 | log_all_pairs(array) 14 | 15 | #Total time complexity of the log_all_pairs function = 16 | #O(n*n + n*n + n*n*1) = O(3n^2) 17 | #The constants can be safely ignored. 18 | #Therefore, O(3n^2) = O(n^2) 19 | 20 | new_array = [1,2,3,4,5] 21 | def print_numbers_then_pairs(array): 22 | 23 | #There are a total of three loops here but only one variable. So we need only variable for our Big-O notation 24 | 25 | print("The numbers are : ") #O(1) 26 | for i in range(len(array)): #O(n) 27 | print(array[i]) #n*O(1) 28 | 29 | print("The pairs are :") #O(1) 30 | for i in range(len(array)): #n*O(n) 31 | for j in range(len(array)): #n*O(n) 32 | print(array[i],array[j]) #n*n*O(1) 33 | 34 | print_numbers_then_pairs(new_array) 35 | 36 | #Total time complexity of the print_numbers_then_pairs function = 37 | #O(1 + n + n*1 + 1 + n*n + n*n + n*n*1) = O(3n^2 + 2n + 2) 38 | #Now, Big-O presents scalability of the cod, i.e., how the code will behave as the inputs grow larger and larger 39 | #Therefore if the expression contains terms of different degrees and the size of inputs is huge, the terms of the smaller degrees become negligible in comparison to those of the higher degrees 40 | #Therefore, we can ignore the terms of the smaller degrees and only keep the highest degree term 41 | #O(3n^2 + 2n + 2) = O(3n2) 42 | #The constants can be safely ignored. 43 | #Therefore, O(3n^2) = O(n^2) -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Contains_Duplicate.py: -------------------------------------------------------------------------------- 1 | #Given an array of integers, find if the array contains any duplicates. 2 | #Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct. 3 | #Example 1: 4 | #Input: [1,2,3,1] 5 | #Output: true 6 | #Example 2: 7 | #Input: [1,2,3,4] 8 | #Output: false 9 | 10 | #As usual we'll get the naive approach out of the way first. 11 | 12 | def brute_force_duplicate_search(array): 13 | for i in range(len(array)-1): 14 | for j in range(i+1,len(array)): 15 | if array[i] == array[j]: 16 | return True 17 | return False 18 | 19 | array = [1,2,46,32,98,61,34,46] 20 | print(brute_force_duplicate_search(array)) 21 | 22 | #This is pretty simple, as we go through every possible pair of elements to check if they are the same. 23 | #If we find a pair having the same elements we return True, else we return False 24 | #Time Complexity - O(n^2) 25 | 26 | #A slightly better solution can be : 27 | #First we sort the array using O(nlog n) built-in sort of Python. 28 | #Then we loop through the array once to check if any consecutive elements are same, which will be O(n). 29 | #So overall complexity will be O(nlog n) 30 | 31 | def better_duplicate_search(array): 32 | array.sort() 33 | for i in range(len(array)-1): 34 | if array[i] == array[i+1]: 35 | return True 36 | return False 37 | 38 | print(better_duplicate_search(array)) 39 | 40 | #An even better solution can be using a dictionary. 41 | #As we loop through the array, we'll check first if the current element is present in the dictionary 42 | #If yes, we return True 43 | #If no, we add the element to the dictionary. 44 | #Since looking up in a dictionary is O(1) time, overall complexity would be O(n) 45 | 46 | def smart_duplicate_search(array): 47 | dictionary = dict() 48 | if len(array)<2: 49 | return False 50 | else: 51 | for i in range(len(array)): 52 | if array[i] in dictionary: 53 | return True 54 | else: 55 | dictionary[array[i]] = True 56 | return False 57 | 58 | print(smart_duplicate_search(array)) -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Maximum_SubArray.py: -------------------------------------------------------------------------------- 1 | #Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum 2 | #and return its sum. 3 | #Example: 4 | #Input: [-2,1,-3,4,-1,2,1,-5,4], 5 | #Output: 6 6 | #Explanation: [4,-1,2,1] has the largest sum = 6. 7 | 8 | 9 | #The first solution that comes to mind, as always, is the brute force solution. 10 | #Here we will extract the sum of every possible subarray of the given array and return the maximum value. 11 | #We can predict right off the bat that it is not going to be an optimal solution, but it is going to be a solution nonetheless. 12 | #So let's try it. 13 | 14 | def brute_force_max_subarray(array): 15 | maximum = 0 16 | if len(array)==0: 17 | return None 18 | for i in range(len(array)): 19 | cum_sum = 0 20 | for j in range(i,len(array)): 21 | cum_sum += array[j] 22 | maximum = max(maximum, cum_sum) 23 | return maximum 24 | 25 | array = [-2,1,-3,4,-1,2,1,-5,4] 26 | print(brute_force_max_subarray(array)) 27 | 28 | #What's happening here is each iteration of the outer loop is giving us all the possible subarrays starting from the ith index. 29 | #For example, in the first iteration we get all the subarrays starting with the first element and so on. 30 | #Now with the second for loop we are building a subarray starting from the ith index to the last index 31 | #In the process we are cumulating the sum at every iteration which is giving us the sum of the subarray from the ith index to the jth index 32 | #Then we are checking if the cumulative sum is greater than all other cumulative sums we have found so far and storing the greatest sum in "maximum" 33 | #Finally we return maximum which contains the greatest sum of any subarray in the array. 34 | #Since we are looping through two nested for loops the time complexity is O(n^2) 35 | 36 | 37 | #There's a much faster way to solve this though, and that is by using the Kadane's algorithm. 38 | #In this, we loop over the array and for every iteration we check for the maximum subarray ending at that index. 39 | #Now the maximum subarray ending at a particular index can be either of two things: 40 | #1. Sum of the maximum subarray ending at the previous index + the element at the current index, or 41 | #2. The current element only. 42 | #So lets implement this algorithm. 43 | 44 | def kadane(array): 45 | maximum = maxarray = array[0] 46 | for i in range(1,len(array)): 47 | maxarray = max(array[i], maxarray+array[i]) 48 | maximum = max(maxarray, maximum) 49 | return maximum 50 | 51 | print(kadane(array)) 52 | 53 | #We set the variables maximum and maxarray to the value of the first element of the array. 54 | #Then we loop over the entire array from the first index. 55 | #At every iteration, we update the value of maxarray to be the maximum among the current element 56 | #and the sum of the maxarray ending at the previous index and the current element 57 | #This way, maxarray stores the maximum subarray ending at index i 58 | #And the variable maximum stores the maximum among all the maxarrays ending at every index, effectively storing the global maximum 59 | # Since this requires only one for loop, the time complexity is an efficiently O(n)! -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Merging_sorted_arrays.py: -------------------------------------------------------------------------------- 1 | #Given two sorted arrays, we need to merge them and create one big sorted array. 2 | #For example, array1 = [1,3,5,7], array2 = [2,4,6,8] 3 | #The result should be array = [1,2,3,4,5,6,7,8] 4 | 5 | #One solution can be : we compare the corresponding elements of both arrays 6 | #We add the smaller element to a new array and increment the index of the array from which the element was added. 7 | #Again we compare the elements of both arrays and repeat the procedure until all the elements have been added. 8 | 9 | def merge(array1, array2): 10 | new_array = [] 11 | flag = 0 12 | first_array_index = second_array_index = 0 13 | while not (first_array_index>=len(array1) or second_array_index>=len(array2)): #The loop runs until we reach the end of either of the arrays 14 | if array1[first_array_index] <= array2[second_array_index]: 15 | new_array.append(array1[first_array_index]) 16 | first_array_index += 1 17 | else: 18 | new_array.append(array2[second_array_index]) 19 | second_array_index += 1 20 | 21 | if first_array_index==len(array1): #When the loop finishes, we need to know which array's end was reached, so that the remaining elements of the other array can be appended to the new array 22 | flag = 1 #This flag will tell us if we reached the end of the first array or the second array 23 | 24 | if flag == 1: #If the end of the first array was reached, the remaining elements of the second array are added to the new array 25 | for item in array2[second_array_index:]: 26 | new_array.append(item) 27 | else: #And if the end of the second array was reached, the remaining elements of the first array are added to the new array 28 | for item in array1[first_array_index:]: 29 | new_array.append(item) 30 | 31 | return new_array 32 | 33 | array1 = [1,3,5,7] 34 | array2 = [2,4,6,8,10,12] 35 | print(merge(array1,array2)) 36 | #[1, 2, 3, 4, 5, 6, 7, 8, 10, 12] 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Move_Zeroes.py: -------------------------------------------------------------------------------- 1 | #Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements. 2 | #Example: 3 | #Input: [0,1,0,3,12] 4 | #Output: [1,3,12,0,0] 5 | 6 | #The first solution that comes to mind is we can traverse the original length of the array and for every zero we find, 7 | #We append a 0 at the end of the array. 8 | #Then we again traverse the array, only for the original length of the array, 9 | #And we pop every 0 that we find, thus removing every 0 from the original part of the array and moving them all to the end. 10 | #This seems inefficient as pop operation anywhere apart fromthe end of the array costs O(n) time 11 | #And we have loop over the entire array so it becomes O(n^2), but lets see. 12 | 13 | def naive_zero_mover(array): 14 | l = len(array) 15 | for i in range(l): 16 | if array[i] == 0: 17 | array.append(0) #We have appended 0's at the end for every 0 in the original array 18 | j = 0 19 | c = 0 20 | while c < l: #We run the while loop only for l times, i.e., the original length of the array 21 | if array[j]!=0: 22 | j += 1 #If the element is non-zero we increment j by 1, which keeps track of every index of the array upto l 23 | else: 24 | array.pop(j) #If we find a 0 , we pop it and we do not increase j as all the elements have been shifted left. So the next element is in jth index only 25 | c += 1 26 | return array 27 | 28 | array = [0,0,0,0,1,0,3,0,0,0,12,9,7] 29 | print(naive_zero_mover(array)) 30 | 31 | 32 | #A far better solution can be swapping every non-zero element we find with the first un-swapped zero 33 | 34 | def swap_move(array): 35 | z = 0 36 | for i in range(len(array)): 37 | if array[i] != 0: 38 | array[i], array[z] = array[z], array[i] 39 | z += 1 40 | return array 41 | print(swap_move(array)) 42 | #In this solution, we traverse the array and swap every non-zero element with itself until we find a 0 43 | #Then we swap the next non-zero element with the 0 and we keep doing this until we have looped over the entire array. 44 | #This seems like a cleaner solution to the first one but still there are lots of unnecessary swaps going on here. 45 | #Still, we are looping over the array once and the swapping is done in constant time, so overall time complexity is O(n) 46 | 47 | 48 | #A very elegant solution to this problem can be the following one-liner : 49 | 50 | def one_liner_move(array): 51 | array.sort(key=bool, reverse=True) 52 | return array 53 | 54 | print(one_liner_move(array)) 55 | 56 | #What this does is it sorts the array in place using the key as boolean. 57 | #Now the integer 0 is considered as boolean 0 and all other integers are considered as boolean 1 58 | #So providing the key as bool, we are telling the sort method to sort the array on the basis of boolean values 59 | #The 0's , which are considered as 0, come first, and the remaining numbers, considered as 1, come next, in their original order. 60 | #The reverse=True reverses this arrangement so that the 0's are all the end and the non-zero numbers at the front. 61 | #The complexity for this is O(nlog n) as the complexity of Python's built-in sort method is O(nlog n) -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Reversing_String.py: -------------------------------------------------------------------------------- 1 | # A string is given. We have to print the reversed string. 2 | # For example, the string is "Hi how are you?" 3 | # The output should be "?ouy era woh iH" 4 | 5 | #The first solution that comes to mind is we can create a new array and append the characters of the original array, 6 | #one by one from the end to the beginning. 7 | 8 | def simple_reverse(string): 9 | new_string = [] 10 | for i in range(len(string)-1, -1, -1): #The for loop runs from the last element to the first element of the original string 11 | new_string.append(string[i]) #The characters of the original string are added to the new string 12 | return ''.join(new_string) #The characters of the reversed array are joined to form a string 13 | 14 | string = "Hello" 15 | print(simple_reverse(string)) 16 | #Since we only have to traverse the string once, the time complexity is O(n) 17 | #But since we are also creating a new array of the same size , the space complexity is also O(n) 18 | 19 | 20 | #A smarter way to do this , can be taking a pair of elements from either end of the string and swapping them 21 | #We have start at both the ends and continue swapping pairs till the middle of the string 22 | #This way we can avoid having to create a new array and save on space complexity while keeping time complexity at O(n) 23 | 24 | def swap(string, a, b): #Function which swaps two characters of a string 25 | string = list(string) 26 | temp = string[a] 27 | string[a] = string[b] 28 | string[b] = temp 29 | return ''.join(string) 30 | 31 | def smarter_reverse(string): 32 | for i in range(len(string)//2): 33 | string = swap(string, i, len(string)-i-1) 34 | return string 35 | 36 | print(smarter_reverse(string)) 37 | 38 | 39 | #Apart from these, some built-in functions that can be used to reverse a string are as follows: 40 | 41 | string1 = 'abcde' 42 | string2 = reversed(string1) 43 | print(''.join(string2)) 44 | 45 | list1 = list(string1) 46 | list1.reverse() 47 | print(''.join(list1)) 48 | 49 | #Both these methods are of O(n) time complexity -------------------------------------------------------------------------------- /03. Data Structures/Arrays/Rotate_Array.py: -------------------------------------------------------------------------------- 1 | #Given an array, rotate the array to the right by k steps, where k is non-negative. 2 | #Example 1: 3 | #nput: nums = [1,2,3,4,5,6,7], k = 3 4 | #Output: [5,6,7,1,2,3,4] 5 | 6 | #The instant solution for this that comes to mind is : 7 | #We create a new array and initialize the first k elemenst of the new array with the last k elements of the original array. 8 | #Then we fill in the remaining elements 9 | #The time complexity is O(n) and the space complexity is also O(n) 10 | 11 | def naive_rotation(array, k): 12 | new_array = [] 13 | for i in range(k%len(array)): 14 | new_array.append(array[len(array)-k+i]) 15 | for i in range(len(array)-k%len(array)): 16 | new_array.append(array[i]) 17 | return new_array 18 | 19 | array = [1,2,3,4,5,6,7,8,9] 20 | k = 11 21 | print(naive_rotation(array,k)) 22 | 23 | 24 | #Another inefficient but correct approach can be the brute force appoach where we rotate the array by 1 element in each traversal of the array 25 | #This way we won't have to use another array, so we'll save on space complexity 26 | #But the time complexity would be O(n*k) as we we'll have to rotate the array k times and for each rotation, we need to traverse the entire array 27 | 28 | def brute_force_rotation(array, k): 29 | for i in range(k): 30 | temp = array[-1] 31 | for i in range(len(array)-1,0,-1): 32 | array[i] = array[i-1] 33 | array[0] = temp 34 | return array 35 | 36 | print(brute_force_rotation(array, k)) 37 | 38 | 39 | #A better solution can be using the Reversal Algorithm 40 | #In this, we first reverse the entire array, then we reverse the first k elements, followed by reversing the last n-k elements 41 | #Since, the time complexity of reversing is O(n) 42 | #Therefore, overall time complexity for this algorithm would be O(3n) which is equal to O(n), with no extra space required 43 | 44 | def reverse(array, start, end): #Function to reverse the elements of array from index start to index end 45 | while start> {" ".join(map(str, self.adjacency_list[node]))}') 43 | 44 | 45 | my_graph = Graph() 46 | my_graph.insert_node(1) 47 | my_graph.insert_node(2) 48 | my_graph.insert_node(3) 49 | my_graph.insert_edge(1,2) 50 | my_graph.insert_edge(1,3) 51 | my_graph.insert_edge(2,3) 52 | my_graph.show_connections() 53 | 54 | """ 55 | 1 -->> 2 3 56 | 2 -->> 1 3 57 | 3 -->> 1 2 58 | """ 59 | 60 | print(my_graph.adjacency_list) 61 | #{1: [2, 3], 2: [1, 3], 3: [1, 2]} 62 | 63 | print(my_graph.number_of_nodes) 64 | #3 -------------------------------------------------------------------------------- /03. Data Structures/Hash Tables/First_Recurring_Character.py: -------------------------------------------------------------------------------- 1 | #Google Question 2 | #Given an array, return the first recurring character 3 | #Example1 : array = [2,1,4,2,6,5,1,4] 4 | #It should return 2 5 | #Example 2 : array = [2,6,4,6,1,3,8,1,2] 6 | #It should return 6 7 | 8 | #First thing that comes to mind is we can create a dictionary and keep storing each element of the array in the dictionary 9 | #as we go along the array. But before adding the element to the dictionary, we'll check if the element is already present in the dictionary 10 | #If yes, then we simply return the element and break out 11 | #If not, then we add the element to the dictionary and move forward 12 | 13 | def simple_frc(array): 14 | dictionary = dict() 15 | for item in array: 16 | if item in dictionary: 17 | return item 18 | else: 19 | dictionary[item] = True 20 | return None 21 | 22 | array = [2,1,4,1,5,2,6] 23 | #print(simple_frc(array)) 24 | 25 | #The time complexity is O(n) as we are looping through the array only once 26 | #And the search which we are doing in the dictionary, is of O(1) time, since it is basically an implementation of hash table. 27 | 28 | 29 | #Another aproach can be the naive approach using two nested loops 30 | 31 | def naive_frc(array): 32 | l = len(array) 33 | i= 0 34 | frc = None 35 | while(i" 22 | curr = curr.next 23 | if to_print: 24 | return '[' + to_print[:-2] + ']' 25 | 26 | return '[]' 27 | 28 | def append_val(self, x): 29 | 30 | if not isinstance(x, Node): 31 | x = Node(x) 32 | 33 | if self.head == None: 34 | self.head = x 35 | else: 36 | self.tail.next = x 37 | 38 | self.tail = x 39 | 40 | 41 | def add_to_start(self, x): 42 | if not isinstance(x, Node): 43 | x = Node(x) 44 | 45 | temp = self.head 46 | x.next = temp 47 | self.head = x 48 | 49 | 50 | 51 | 52 | def search_val(self, x): 53 | 54 | current = self.head 55 | i = 0 56 | 57 | while current != None: 58 | if current.data == x: 59 | print (f"{x} value found at index {i}") 60 | 61 | current = current.next 62 | i += 1 63 | print (f"{x} value not found") 64 | 65 | 66 | 67 | 68 | def remove_val_by_index(self, key): 69 | 70 | temp = self.head 71 | 72 | if (temp is not None): 73 | if (temp.data == key): 74 | self.head = temp.next 75 | temp = None 76 | return 77 | 78 | while(temp is not None): 79 | if temp.data == key: 80 | break 81 | prev = temp 82 | temp = temp.next 83 | 84 | 85 | if(temp == None): 86 | return 87 | 88 | 89 | prev.next = temp.next 90 | 91 | temp = None 92 | 93 | def length(self): 94 | temp = self.head 95 | count = 0 96 | 97 | while (temp): 98 | count += 1 99 | temp = temp.next 100 | return count 101 | 102 | def reverse_list_recur(self, current, previous): 103 | if self.head == None: 104 | return 105 | elif current.next == None: 106 | self.tail = self.head 107 | current.next = previous 108 | self.head = current 109 | else: 110 | next = current.next 111 | current.next = previous 112 | self.reverse_list_recur(next, current) 113 | 114 | 115 | node1 = Node(1) 116 | node2 = Node(2) 117 | node3 = Node(3) 118 | node4 = Node(4) 119 | node5 = Node(5) 120 | 121 | my_list = LinkedList() 122 | 123 | my_list.append_val(node1) 124 | my_list.append_val(node2) 125 | my_list.append_val(node3) 126 | my_list.append_val(node4) 127 | my_list.append_val(node5) 128 | my_list.append_val(6) 129 | my_list.add_to_start(9) 130 | my_list.search_val(3) 131 | 132 | print(my_list) -------------------------------------------------------------------------------- /03. Data Structures/Linked Lists/Reverse.py: -------------------------------------------------------------------------------- 1 | #Given a linked list we have to reverse it. 2 | #For this we would have to implement a linked list from scratch first, so we will import our Implementation.py file 3 | #And use the LinkedList and Node classes defined there so that we don't have to create a Linked List from scratch 4 | 5 | from Implementation import LinkedList, Node 6 | 7 | #Now we create a Linked List by appending some values 8 | my_linked_list = LinkedList() 9 | my_linked_list.append(2) 10 | my_linked_list.append(3) 11 | my_linked_list.append(4) 12 | my_linked_list.append(5) 13 | my_linked_list.append(6) 14 | my_linked_list.print_list() 15 | #2 3 4 5 6 16 | 17 | 18 | #Linked list has been created. Now we need to create a reverse function, which will reverse the list. 19 | #It will take the linked list as an argument and return the reversed list. 20 | #If the list is empty or consists of 1 item only we return the list as it is. 21 | #Otherwise, we create two nodes first and second which point to the first and second nodes of the list respectively 22 | #Then we update the tail of the list to point to the head as after reversing the present head will become the last node 23 | #Then we run a loop until second becmes None 24 | #Inside the loop we create a temporary node which points to the 'next' of the second node 25 | #Then we update the 'next' of the second node to point to the first node so that the link is now reversed (2nd node points to 1st node instead of 3rd). 26 | #And then we will update the first and second nodes to be equal to the second and temporary nodes respectively. 27 | #What this does is, in the next iteration, 'second' will point to the 3rd node and 'first' to the 2nd 28 | #And the 'second.next = first' statement will make the 3rd node point to the 2nd node instead of the 4th. 29 | #And this will go on till 'second' becomes None and by then all the links will be reversed. 30 | #Finally, we will update the 'next' of the head(which is still the original head) point to None as it is effectively the last node 31 | #And then we will update the head to be equal to 'first', which by now points to the last node of the original list, and return the now reversed linked list 32 | #Time complexity pretty clearly will be O(n) 33 | def reverse(linked_list): 34 | if linked_list.length <=1: 35 | return linked_list 36 | else: 37 | first = linked_list.head 38 | second = first.next 39 | linked_list.tail = linked_list.head 40 | while second: 41 | temp = second.next 42 | second.next = first 43 | first = second 44 | second = temp 45 | linked_list.head.next = None 46 | linked_list.head = first 47 | return linked_list 48 | 49 | reversed_linked_list = reverse(my_linked_list) 50 | reversed_linked_list.print_list() 51 | #6 5 4 3 2 52 | -------------------------------------------------------------------------------- /03. Data Structures/Queues/LEARN: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /03. Data Structures/Queues/Legacy/queue_demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Node: 4 | 5 | def __init__(self, data=None): 6 | '''initialize node with data and next pointer''' 7 | self.data = data 8 | self.next = None 9 | 10 | class Queue: 11 | 12 | def __init__(self): 13 | '''initialize queue with head and tail''' 14 | print("Queue created") 15 | self.head = None 16 | self.tail = None 17 | 18 | def add(self, x): 19 | '''append x to the tail of the queue''' 20 | if not isinstance(x, Node): 21 | x = Node(x) 22 | print(f"Appending {x.data} to the tail of the Queue") 23 | if self.is_empty(): 24 | self.head = x 25 | else: 26 | self.tail.next = x 27 | self.tail = x 28 | 29 | def remove(self): 30 | '''remove and return the node at head of the queue''' 31 | if not self.is_empty(): 32 | print(f"Removing node at head of the Queue") 33 | curr = self.head 34 | self.head = self.head.next 35 | curr.next = None 36 | return curr.data 37 | else: 38 | return "Queue is empty" 39 | 40 | def is_empty(self): 41 | '''return True if queue is empty, else return false''' 42 | return self.head == None 43 | 44 | def peek(self): 45 | '''look at the node at head of the queue''' 46 | if not self.is_empty(): 47 | return self.head.data 48 | 49 | def __str__(self): 50 | print("Printing Queue state...") 51 | to_print = "" 52 | curr = self.head 53 | while curr is not None: 54 | to_print += str(curr.data) + "->" 55 | curr = curr.next 56 | if to_print: 57 | if len(to_print) > 4: 58 | print("Head", " "*(len(to_print)-9),"Tail") 59 | print(" |", " "*(len(to_print)-6), "|") 60 | print(" V", " "*(len(to_print)-6), "V") 61 | return "[" + to_print[:-2] + "]" 62 | else: 63 | print("Head & Tail") 64 | print(" |") 65 | print(" V") 66 | return "[" + to_print[:-2] + "]" 67 | return "[]" 68 | 69 | my_queue = Queue() 70 | print("Checking if Queue is empty:", my_queue.is_empty()) 71 | time.sleep(2) 72 | my_queue.add(1) 73 | print(my_queue) 74 | time.sleep(2) 75 | my_queue.add(2) 76 | my_queue.add(3) 77 | print(my_queue) 78 | time.sleep(2) 79 | my_queue.add(4) 80 | my_queue.add(5) 81 | time.sleep(2) 82 | print("Checking node at head of Queue:", my_queue.peek()) 83 | time.sleep(2) 84 | my_queue.add(6) 85 | print(my_queue) 86 | time.sleep(2) 87 | print(my_queue.remove()) 88 | time.sleep(2) 89 | print(my_queue.remove()) 90 | time.sleep(2) 91 | print(my_queue) 92 | time.sleep(2) 93 | my_queue.add(4) 94 | time.sleep(2) 95 | print(my_queue) 96 | -------------------------------------------------------------------------------- /03. Data Structures/Queues/Queue_Using_Stacks.py: -------------------------------------------------------------------------------- 1 | #This ia popular interview question. Implementation of a queue using stacks. 2 | #We have access to stacks push and pop operations. Using those we need to execute a qeueue's enqueue and dequeue operation 3 | #It can be done in two ways, by either making the enqueue operation costly(O(n)) or the dequeue operation costly(O(n)) 4 | #In the first method, we need two stacks say s1 and s2 and we have maintain them such that the element entered first 5 | #Is always at the top of the stack s1. This way, for dequeue, we just need to pop from s1. 6 | #But for enqueueing, we have to make the enqueued item reach the bottom of the stack. 7 | #For that, we will have to pop the elements of s1 one by one and push them onto stack 2, then add the new item to stack1 , 8 | #And then again pop everything from stack2 and push it back to stack 1., so the new item is now at the last. 9 | 10 | #Lets implement a queue using stacks(array implementation) using this method first 11 | 12 | class Queue(): 13 | def __init__(self): 14 | self.s1 = [] 15 | self.s2 = [] 16 | 17 | 18 | def peek(self): 19 | if len(self.s1) == 0: 20 | print("Queue empty") 21 | else: 22 | return self.s1[len(self.s1)-1] 23 | 24 | 25 | def enqueue(self, data): 26 | for i in range(len(self.s1)): 27 | item = self.s1.pop() 28 | self.s2.append(item) 29 | self.s1.append(data) 30 | for i in range(len(self.s2)): 31 | item = self.s2.pop() 32 | self.s1.append(item) 33 | return 34 | 35 | def dequeue(self): 36 | if len(self.s1)==0: 37 | print("Queue Empty") 38 | return 39 | else: 40 | return self.s1.pop() 41 | 42 | def print_queue(self): 43 | if len(self.s1) == 0: 44 | print("Queue Empty") 45 | return 46 | for i in range(len(self.s1) - 1,0,-1): 47 | print(f'{self.s1[i]} <<-- ',end='') 48 | print(self.s1[0]) 49 | return 50 | 51 | 52 | my_queue = Queue() 53 | my_queue.enqueue(2) 54 | my_queue.enqueue(5) 55 | my_queue.enqueue(0) 56 | my_queue.print_queue() 57 | #2 <<-- 5 <<-- 0 58 | 59 | my_queue.dequeue() 60 | my_queue.print_queue() 61 | #5 <<-- 0 62 | 63 | print(my_queue.peek()) 64 | #5 65 | my_queue.enqueue(9) 66 | my_queue.print_queue() 67 | #5 <<-- 0 <<-- 9 68 | 69 | my_queue.dequeue() 70 | my_queue.dequeue() 71 | my_queue.dequeue() 72 | my_queue.print_queue() 73 | #Queue Empty 74 | 75 | 76 | ''' 77 | For the second method, we can make the dequeue operation costly just like we did with the enqueue operation above. 78 | For enqueueing, we will simply push in s1. 79 | For dequeueing, we will pop all but last element of s1 and push it onto s2. Then we will pop the last element of s1, 80 | Which is the element we want to dequeue. After that we pop out all items of s2 and push it back onto s1. 81 | This makes the dequeue uperation O(n) while enqueue and peek remain O(1) 82 | ''' -------------------------------------------------------------------------------- /03. Data Structures/Stacks/Array_Implementation.py: -------------------------------------------------------------------------------- 1 | #Stacks can be implemented with th ehelp of arrays as well. 2 | #We can insert and delete elements only at the end of the array(the top of the stack) 3 | #Python comes built-in with lists which are basically arrays. 4 | #They contain functionalities like append and pop which correspond to the push and pop methods of stacks respectively 5 | #So implementing stacks using arrays is pretty simple in Python 6 | #The time complexities of different operations are same as that for the inked list implementation of stacks 7 | 8 | 9 | #We define a class Stack with the array which will store the elements and the methods we require for a stack 10 | class Stack(): 11 | 12 | #The constructor consists of only an empty array as length comes built-in with arrays(lists) 13 | def __init__(self): 14 | self.array = [] 15 | 16 | #In the peek method we access the last element of the array(top element of the stack) by using the built-in length functionality of arrays 17 | def peek(self): 18 | return self.array[len(self.array)-1] 19 | 20 | #For push operation, we use the built-in append method of lists, which appends/pushes/inserts an element at the end of the list(top of the stack) 21 | def push(self, data): 22 | self.array.append(data) 23 | return 24 | 25 | #For pop operation, we use thebuilt-in pop method of lists, which removes the last element of the list(top element of the stack) 26 | #Time complexity of pop operation for the last element of the list is O(1). 27 | def pop(self): 28 | if len(self.array)!= 0: 29 | self.array.pop() 30 | return 31 | else: 32 | print("Stack Empty") 33 | return 34 | 35 | #Stack follows LIFO, so for the print operation, we have to print the last element of the list first. 36 | #This will require a loop traversing the entire array, so the complexity is O(n) 37 | def print_stack(self): 38 | for i in range(len(self.array)-1, -1, -1): 39 | print(self.array[i]) 40 | return 41 | 42 | 43 | 44 | my_stack = Stack() 45 | my_stack.push("Andrei's") 46 | my_stack.push("Courses") 47 | my_stack.push("Are") 48 | my_stack.push("Awesome") 49 | my_stack.print_stack() 50 | #Awesome 51 | #Are 52 | #Courses 53 | #Andrei's 54 | 55 | my_stack.pop() 56 | my_stack.pop() 57 | my_stack.print_stack() 58 | #Courses 59 | #Andrei's 60 | 61 | print(my_stack.peek()) 62 | #Courses 63 | 64 | print(my_stack.__dict__) 65 | #{'array': ["Andrei's", 'Courses']} 66 | 67 | 68 | '''Stacks can be implemented in Python in two more ways. 69 | 1. Using the 'deque' class from 'collections' module. Same methods used in lists, append and pop are used in deques 70 | 2. Using 'LifoQueue' from the 'queue' module . 'put()' and 'get()' methods are used for pushing and popping. It comes with some other useful metjods async well. 71 | ''' -------------------------------------------------------------------------------- /03. Data Structures/Stacks/Legacy/stack_demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Node: 4 | 5 | def __init__(self, data=None): 6 | '''initialize node with data and next pointer''' 7 | self.data = data 8 | self.next = None 9 | 10 | class Stack: 11 | 12 | def __init__(self): 13 | '''initialize stack with stack pointer''' 14 | print("Stack created") 15 | self.stack_pointer = None 16 | 17 | def push(self, x): 18 | '''add x to the top of the stack''' 19 | if not isinstance(x, Node): 20 | x = Node(x) 21 | print(f"Adding {x.data} to the top of stack") 22 | if self.is_empty(): 23 | self.stack_pointer = x 24 | else: 25 | x.next = self.stack_pointer 26 | self.stack_pointer = x 27 | 28 | def pop(self): 29 | '''remove and return the node on top of the stack''' 30 | if not self.is_empty(): 31 | print(f"Removing node on top of stack") 32 | curr = self.stack_pointer 33 | self.stack_pointer = self.stack_pointer.next 34 | curr.next = None 35 | return curr.data 36 | else: 37 | return "Stack is empty" 38 | 39 | def is_empty(self): 40 | '''return True if stack is empty, else return false''' 41 | return self.stack_pointer == None 42 | 43 | def peek(self): 44 | '''look at the node on top of the stack''' 45 | if not self.is_empty(): 46 | return self.stack_pointer.data 47 | 48 | def __str__(self): 49 | print("Printing Stack state...") 50 | to_print = "" 51 | curr = self.stack_pointer 52 | while curr is not None: 53 | to_print += str(curr.data) + "->" 54 | curr = curr.next 55 | if to_print: 56 | print("Stack Pointer") 57 | print(" |") 58 | print(" V") 59 | return "[" + to_print[:-2] + "]" 60 | return "[]" 61 | 62 | my_stack = Stack() 63 | print("Checking if stack is empty:", my_stack.is_empty()) 64 | my_stack.push(1) 65 | time.sleep(2) 66 | my_stack.push(2) 67 | print(my_stack) 68 | time.sleep(2) 69 | my_stack.push(3) 70 | time.sleep(2) 71 | my_stack.push(4) 72 | time.sleep(2) 73 | print("Checking item on top of stack:", my_stack.peek()) 74 | time.sleep(2) 75 | my_stack.push(5) 76 | print(my_stack) 77 | time.sleep(2) 78 | print(my_stack.pop()) 79 | time.sleep(2) 80 | print(my_stack.pop()) 81 | print(my_stack) 82 | time.sleep(2) 83 | my_stack.push(4) 84 | print(my_stack) 85 | time.sleep(2) 86 | -------------------------------------------------------------------------------- /03. Data Structures/Trees/Priority_Queues_Using_Heap.py: -------------------------------------------------------------------------------- 1 | # Priority Queues, as the name suggests, are queues where the elements have different priorities 2 | # And it does not always follow the FIFO rule. 3 | # They can be implemented in a number of ways, out of which heap is the most commonly used one. 4 | #In Python, it is available using “heapq” module. The property of this data structure in Python is that each time the smallest of heap element is popped(min heap). 5 | #Whenever elements are pushed or popped, heap structure in maintained. The heap[0] element returns the smallest element each time. 6 | #Operations we can perform on heap using heapq module are: 7 | #heapify(iterable) : This function converts the iterable into a heap. i.e. in heap order. 8 | #heappush(heap, ele) : This function inserts an element into heap. The order is adjusted, so as heap structure is maintained. 9 | #heappop(heap) : This function removes and returns the smallest element from heap. Again, he order is adjusted, so as heap structure is maintained. 10 | #heappushpop(heap, ele) : This function combines the functioning of both push and pop operations in one statement, increasing efficiency. Heap order is maintained after this operation. 11 | #eapreplace(heap, ele) : This function also inserts and pops element in one statement, but in this, minimum element is first popped, then the new element is pushed. 12 | #heapreplace() returns the smallest value originally in heap regardless of the pushed element as opposed to heappushpop(). 13 | 14 | 15 | import heapq 16 | 17 | # initializing list 18 | li = [5, 7, 9, 1, 3] 19 | 20 | #using heapify to convert list into heap 21 | heapq.heapify(li) 22 | 23 | #printing created heap 24 | print("The created heap is : ", end="") 25 | print(list(li)) 26 | #The created heap is : [1, 3, 9, 7, 5] 27 | 28 | 29 | #using heappush() to push elements into heap 30 | heapq.heappush(li, 4) 31 | 32 | #printing modified heap 33 | print("The modified heap after push is : ", end="") 34 | print(list(li)) 35 | #The modified heap after push is : [1, 3, 4, 7, 5, 9] 36 | 37 | 38 | #using heappop() to pop smallest element 39 | print("The popped and smallest element is : ", end="") 40 | print(heapq.heappop(li)) 41 | #The popped and smallest element is : 1 42 | 43 | 44 | #Creating two identical heaps to demonstrate the difference between heappushpop and heapreplace 45 | li1 = [5, 7, 9, 4, 3] 46 | li2 = [5, 7, 9, 4, 3] 47 | heapq.heapify(li1) 48 | heapq.heapify(li2) 49 | 50 | # using heappushpop() to push and pop items simultaneously 51 | print("The popped item using heappushpop() is : ", end="") 52 | print(heapq.heappushpop(li1, 2)) 53 | #The popped item using heappushpop() is : 2 54 | 55 | 56 | # using heapreplace() to push and pop items simultaneously 57 | print("The popped item using heapreplace() is : ", end="") 58 | print(heapq.heapreplace(li2, 2)) 59 | #The popped item using heapreplace() is : 3 60 | -------------------------------------------------------------------------------- /04. Algorithms/Divide and Conquer/karatsuba_multiplication.py: -------------------------------------------------------------------------------- 1 | ''' 2 | karatsuba multiplication (integer-wise) 3 | to multiply two n-digit integers, the following formula holds: 4 | => (a * c * 10^n) + (b * d) + {[(b * c) + (a * d)] * 10^(n/2)} 5 | where: a = first half of first integer 6 | b = second half of first integer 7 | c = first half of second integer 8 | d = second half of second integer 9 | this can further be used with recursion to implement divide-and-conquer 10 | wherein each product (ac, bd, bc, ad) can be calculated using the same. 11 | 12 | note: assuming n = even & n1 = n2 13 | ''' 14 | def k_multiply(x, y): 15 | if len(x) > 1 and len(y) > 1: # base condition: length of integers > 1 16 | a, b = x[:len(x) // 2], x[len(x) // 2:] # divide first int for a, b, 17 | c, d = y[:len(y) // 2], y[len(y) // 2:] # divide second int for c, d 18 | 19 | print(a, b, c, d) 20 | ac = k_multiply(a, c) 21 | bd = k_multiply(b, d) 22 | bc = k_multiply(b, c) 23 | ad = k_multiply(a, d) 24 | n = len(x) 25 | n2 = n // 2 26 | prod = (ac * pow(10, n)) + (bd) + ((bc + ad) * pow(10, n2)) # prod by karatsuba 27 | 28 | return prod 29 | else: 30 | return (x[0] * y[0]) # return product if length <= 1 31 | 32 | if __name__ == '__main__': 33 | try: 34 | first_integer = [int(x) for x in input('first integer: ')] 35 | second_integer = [int(x) for x in input('second integer: ')] 36 | 37 | if (len(second_integer) != len(first_integer)): 38 | raise ValueError 39 | 40 | prod = k_multiply(first_integer, second_integer) 41 | print(prod) 42 | except ValueError: 43 | print('Invalid Inputs') 44 | 45 | -------------------------------------------------------------------------------- /04. Algorithms/Dynamic Programming/Fibonacci.py: -------------------------------------------------------------------------------- 1 | #Now we will implement our old Fibonacci program using Dynamic Programming 2 | #Fibonacci Sequence : 0 1 1 2 3 5 8 13 21 35 55 89 144 233 . . . 3 | 4 | import time 5 | 6 | def fibonacci(n): 7 | if n<2: 8 | return n 9 | else: 10 | return fibonacci(n-1) + fibonacci(n-2) 11 | 12 | 13 | cache = {} 14 | def dynamic_fibonacci(n): 15 | if n in cache: 16 | return cache[n] 17 | else: 18 | if n < 2: 19 | return n 20 | else: 21 | cache[n] = dynamic_fibonacci(n-1) + dynamic_fibonacci(n-2) 22 | return cache[n] 23 | 24 | 25 | t1 = time.time() 26 | print(fibonacci(30)) 27 | t2 = time.time() 28 | print(t2-t1) 29 | #832040 30 | #0.39888763427734375 31 | 32 | t1 = time.time() 33 | print(dynamic_fibonacci(30)) 34 | t2 = time.time() 35 | print(t2-t1) 36 | #832040 37 | #0.0 38 | 39 | 40 | t1 = time.time() 41 | print(dynamic_fibonacci(60)) 42 | t2 = time.time() 43 | print(t2-t1) 44 | #1548008755920 45 | #0.0 46 | 47 | 48 | t1 = time.time() 49 | print(dynamic_fibonacci(100)) 50 | t2 = time.time() 51 | print(t2-t1) 52 | #354224848179261915075 53 | #0.0 54 | 55 | t1 = time.time() 56 | print(dynamic_fibonacci(1000)) 57 | t2 = time.time() 58 | print(t2-t1) 59 | #43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875 60 | #0.0009982585906982422 61 | 62 | #I won't even dare to try calculating fibonacci(1000) using the normal recursive function! -------------------------------------------------------------------------------- /04. Algorithms/Dynamic Programming/Memoization.py: -------------------------------------------------------------------------------- 1 | #Memoization is an optimization technique used to speed up programs by storing the results of expensive function calls 2 | #and returning the cached result when the same inputs occur again. 3 | #In Python there's a module named functools with a method lru_cache() which allows us to use this optimization technique 4 | #First, we'll implement memoization on our own with an example function, then with the help of lru_cache 5 | 6 | import time, random 7 | 8 | times =[] 9 | 10 | def squaring_without_memoization(number): #Function to calculate the square of a number 11 | return number**2 12 | 13 | array = [random.randint(1,10) for _ in range(10000000)] #Generates an array of size 1000000 with random integers between 1-10(both included) 14 | t1 = time.time() 15 | for i in range(len(array)): 16 | print(squaring_without_memoization(array[i])) 17 | t2 = time.time() 18 | times.append(t2-t1) 19 | 20 | 21 | cache = {} 22 | def squaring_with_memoization(number): 23 | if number in cache: 24 | return cache[number] 25 | else: 26 | cache[number] = number**2 27 | return cache[number] 28 | 29 | t1 = time.time() 30 | for i in range(len(array)): 31 | print(squaring_with_memoization(array[i])) 32 | t2 = time.time() 33 | times.append(t2-t1) 34 | 35 | 36 | from functools import lru_cache 37 | 38 | @lru_cache(maxsize=10000) 39 | def squaring(number): 40 | return number**2 41 | 42 | print(array) 43 | t1 = time.time() 44 | for i in range(len(array)): 45 | print(squaring(array[i])) 46 | t2 = time.time() 47 | times.append(t2-t1) 48 | 49 | print(times) 50 | #[203.95188665390015, 148.48580384254456, 148.26833629608154] --- When array size was 10000000 51 | #[7.06306266784668, 6.145563125610352, 5.758295774459839] --- When array size was 1000000 52 | 53 | print(cache) 54 | #{8: 64, 7: 49, 6: 36, 1: 1, 4: 16, 9: 81, 2: 4, 5: 25, 3: 9, 10: 100} 55 | 56 | print(squaring.cache_info()) 57 | #CacheInfo(hits=999990, misses=10, maxsize=10000, currsize=10) --- When array size was 1000000 58 | #CacheInfo(hits=9999990, misses=10, maxsize=10000, currsize=10) --- When array size was 10000000 59 | 60 | -------------------------------------------------------------------------------- /04. Algorithms/Recursion/Factorial.py: -------------------------------------------------------------------------------- 1 | #Given a number, we have to return its factorial. 2 | #For example, factorial(5) should return 5! = 5*4*3*2*1 = 120 3 | #We can solve this recursively, or iteratively. 4 | #First we are going to solve it iteratively. 5 | 6 | def iterative_factorial(number): 7 | f = 1 8 | for i in range(1, number+1): 9 | f = f * i 10 | return f 11 | 12 | print(iterative_factorial(0)) 13 | #1 14 | print(iterative_factorial(5)) 15 | #120 16 | print(iterative_factorial(50)) 17 | #30414093201713378043612608166064768844377641568960512000000000000 18 | 19 | def recursive_factorial(number): 20 | if number <= 1: 21 | return 1 22 | else: 23 | return number * recursive_factorial(number-1) 24 | 25 | print(recursive_factorial(0)) 26 | #1 27 | print(recursive_factorial(5)) 28 | #120 29 | print(recursive_factorial(50)) 30 | #30414093201713378043612608166064768844377641568960512000000000000 31 | print(recursive_factorial(1000)) 32 | #RecursionError: maximum recursion depth exceeded in comparison 33 | -------------------------------------------------------------------------------- /04. Algorithms/Recursion/Fibonacci.py: -------------------------------------------------------------------------------- 1 | #Given a number, we have to return the number at that index of the fibonacci sequence. 2 | #Fibonacci Sequence - 0 1 1 2 3 5 8 13 21 34 55 89 144 . . . . 3 | #For example, fibonacci(5) should return 5 as the 5th index (staring from 0) of the fibonacci sequence is the number 5 4 | #Again , we will do both the iterative and recursive solutions 5 | 6 | def iterative_fibonacci(index): 7 | first_number = 0 8 | second_number = 1 9 | if index == 0: 10 | return first_number 11 | if index == 1: 12 | return second_number 13 | for i in range(2,index +1): 14 | third_number = first_number + second_number 15 | first_number = second_number 16 | second_number = third_number 17 | return third_number 18 | 19 | print(iterative_fibonacci(0)) #0 20 | print(iterative_fibonacci(1)) #1 21 | print(iterative_fibonacci(5)) #5 22 | print(iterative_fibonacci(7)) #13 23 | print(iterative_fibonacci(10)) #55 24 | print(iterative_fibonacci(12)) #144 25 | 26 | 27 | def recursive_fibonacci(index): 28 | if index == 0: #Base case 1 29 | return 0 30 | if index == 1: #Base case 2 31 | return 1 32 | return recursive_fibonacci(index-1) + recursive_fibonacci(index-2) #Every term in fib sequence = sum of previous two terms 33 | 34 | print(recursive_fibonacci(0)) #0 35 | print(recursive_fibonacci(1)) #1 36 | print(recursive_fibonacci(5)) #5 37 | print(recursive_fibonacci(7)) #13 38 | print(recursive_fibonacci(10)) #55 39 | print(recursive_fibonacci(12)) #144 40 | -------------------------------------------------------------------------------- /04. Algorithms/Recursion/Reverse_String.py: -------------------------------------------------------------------------------- 1 | #Given a string , we need to reverse it using recursion (and iteration) 2 | #For example, input = "Zero To Mastery", output = "yretsaM oT oreZ" 3 | 4 | #First we will implement the iterative solution 5 | def iterative_reverse(string): #Here we use a second string to store the reversed version. Time and Space complexity = O(n) 6 | reversed_string = '' 7 | for i in range(len(string)): 8 | reversed_string = reversed_string + string[len(string)-i-1] 9 | return reversed_string 10 | 11 | print(iterative_reverse("Zero To Mastery")) 12 | #yretsaM oT oreZ 13 | 14 | #Here we append the string backwards into the original string itself and then slice it to contain only the 2nd half,i.e.,the reversed part. 15 | #Time complexity = O(n). Space complexity = O(n) 16 | def second_iterative_reverse(string): 17 | original_length = len(string) 18 | for i in range(original_length): 19 | string = string + string[original_length - i - 1] 20 | string = string[original_length:] 21 | return string 22 | 23 | print(second_iterative_reverse("Zero To Mastery")) 24 | #yretsaM oT oreZ 25 | 26 | 27 | def recursive_reverse(string): 28 | print(string) 29 | if len(string) == 0: 30 | return string 31 | else: 32 | return recursive_reverse(string[1:]) + string[0] 33 | 34 | print(recursive_reverse("Zero To Mastery")) 35 | ''' 36 | Zero To Mastery 37 | ero To Mastery 38 | ro To Mastery 39 | o To Mastery 40 | To Mastery 41 | To Mastery 42 | o Mastery 43 | Mastery 44 | Mastery 45 | astery 46 | stery 47 | tery 48 | ery 49 | ry 50 | y 51 | 52 | yretsaM oT oreZ 53 | ''' -------------------------------------------------------------------------------- /04. Algorithms/Searching/Alt_Binary_Search.py: -------------------------------------------------------------------------------- 1 | # alternative binary search 2 | # - based on "efficient iteration" 3 | # - make jumps and slow down as we get close to target 4 | # - time complexity => O(logn) 5 | 6 | # iterative 7 | def bsearch_alt(target, arr): 8 | n = len(arr) 9 | k = 0 10 | i = n // 2 11 | while (i >= 1): 12 | while (k + i < n) and (arr[k + i] <= target): 13 | k = k + 1 14 | i = i // 2 15 | 16 | return k if arr[k] == target else -1 17 | 18 | 19 | 20 | print(bsearch_alt(4, [1, 2, 3, 4, 5])) 21 | -------------------------------------------------------------------------------- /04. Algorithms/Sorting/Bubble_Sort.py: -------------------------------------------------------------------------------- 1 | #In Bubble Sort, the largest value is bubbled up in every pass. 2 | #Every two adjacent items are compared and they are swapped if they are in the wrong order. 3 | #This way, after every pass, the largest element reaches to the end of the array. 4 | #Time complexity of Bubble Sort in Worst and Average Case is O(n^2) and in best case, its O(n) 5 | 6 | def bubble_sort(array): 7 | count = 0 8 | for i in range(len(array)-1): #-1 because when only 1 item will be left, we don't need to sort that 9 | print(array) 10 | for j in range(len(array)-i-1): #In every iteration of the outer loop, one number gets sorted. So the inner loop will run only for the unsorted part 11 | count += 1 12 | if array[j] > array[j+1]: #If two adjacent elements in the wrong order are found, they are swapped 13 | array[j], array[j+1] = array[j+1], array[j] 14 | #print(f'Number of comparisons = {count}') 15 | return (f'{array} \nNumber of comparisons = {count}') 16 | 17 | array = [5,9,3,10,45,2,0] 18 | print(bubble_sort(array)) 19 | 20 | ''' 21 | [5, 9, 3, 10, 45, 2, 0] 22 | [5, 3, 9, 10, 2, 0, 45] 23 | [3, 5, 9, 2, 0, 10, 45] 24 | [3, 5, 2, 0, 9, 10, 45] 25 | [3, 2, 0, 5, 9, 10, 45] 26 | [2, 0, 3, 5, 9, 10, 45] 27 | [0, 2, 3, 5, 9, 10, 45] 28 | [0, 2, 3, 5, 9, 10, 45] 29 | Number of comparisons = 21 30 | ''' 31 | 32 | 33 | sorted_array = [5,6,7,8,9] 34 | print(bubble_sort(sorted_array)) 35 | 36 | ''' 37 | [5, 6, 7, 8, 9] 38 | [5, 6, 7, 8, 9] 39 | [5, 6, 7, 8, 9] 40 | [5, 6, 7, 8, 9] 41 | [5, 6, 7, 8, 9] 42 | Number of comparisons = 10 43 | ''' 44 | 45 | 46 | 47 | #We can optimize the bubble sort slightly by adding a new boolean variable 48 | #which keeps track of wehether any swaps were done in the last iteration or not 49 | #This way, if say halfway through the loops, the array becomes completely sorted, then we won't do unnecessary comparisons 50 | def optimized_bubble_sort(array): 51 | count = 0 52 | for i in range(len(array) - 1): 53 | swap = False 54 | print(array) 55 | for j in range(len(array) - i - 1): 56 | count += 1 57 | if array[j] > array[j+1]: 58 | array[j], array[j+1] = array[j+1], array[j] 59 | swap = True 60 | if swap==False: 61 | return (f'{array} \nNumber of comparisons = {count}') 62 | return (f'{array} \nNumber of comparisons = {count}') 63 | 64 | 65 | array1 = [5,9,3,10,45,2,0] 66 | print(optimized_bubble_sort(array1)) 67 | 68 | ''' 69 | [5, 9, 3, 10, 45, 2, 0] 70 | [5, 3, 9, 10, 2, 0, 45] 71 | [3, 5, 9, 2, 0, 10, 45] 72 | [3, 5, 2, 0, 9, 10, 45] 73 | [3, 2, 0, 5, 9, 10, 45] 74 | [2, 0, 3, 5, 9, 10, 45] 75 | [0, 2, 3, 5, 9, 10, 45] 76 | Number of comparisons = 21 77 | ''' 78 | 79 | 80 | sorted_array1 = [5,6,7,8,9] 81 | print(optimized_bubble_sort(sorted_array1)) 82 | 83 | ''' 84 | [5, 6, 7, 8, 9] 85 | [5, 6, 7, 8, 9] 86 | Number of comparisons = 4 87 | ''' -------------------------------------------------------------------------------- /04. Algorithms/Sorting/Heap_Sort.py: -------------------------------------------------------------------------------- 1 | #Heap Sort as the name suggests, uses the heap data structure. 2 | #First the array is converted into a binary heap. Then the first element which is the maximum elemet in case of a max-heap, 3 | #is swapped with the last element so that the maximum element goes to the end of the array as it should be in a sorted array. 4 | #Then the heap size is reduced by 1 and max-heapify function is called on the root. 5 | #Time complexity is O(nlog N) in all cases and space complexity = O(1) 6 | 7 | count = 0 8 | def max_heapify(array, heap_size, i): 9 | left = 2 * i + 1 10 | right = 2 * i + 2 11 | largest = i 12 | global count 13 | if left < heap_size: 14 | count += 1 15 | if array[left] > array[largest]: 16 | largest = left 17 | if right < heap_size: 18 | count += 1 19 | if array[right] > array[largest]: 20 | largest = right 21 | if largest != i: 22 | array[i], array[largest] = array[largest], array[i] 23 | max_heapify(array, heap_size, largest) 24 | 25 | def build_heap(array): 26 | heap_size = len(array) 27 | for i in range ((heap_size//2),-1,-1): 28 | max_heapify(array,heap_size, i) 29 | 30 | def heap_sort(array): 31 | heap_size = len(array) 32 | build_heap(array) 33 | print (f'Heap : {array}') 34 | for i in range(heap_size-1,0,-1): 35 | array[0], array[i] = array[i], array[0] 36 | heap_size -= 1 37 | max_heapify(array, heap_size, 0) 38 | 39 | array = [5,9,3,10,45,2,0] 40 | heap_sort(array) 41 | print (array) 42 | print(f'Number of comparisons = {count}') 43 | ''' 44 | Heap : [45, 10, 3, 5, 9, 2, 0] 45 | [0, 2, 3, 5, 9, 10, 45] 46 | Number of comparisons = 22 47 | ''' 48 | 49 | sorted_array = [5,6,7,8,9] 50 | heap_sort(sorted_array) 51 | print(sorted_array) 52 | print(f'Number of comparisons = {count}') 53 | ''' 54 | Heap : [9, 8, 7, 5, 6] 55 | [5, 6, 7, 8, 9] 56 | Number of comparisons = 12 57 | ''' 58 | 59 | reverse_sorted_array = [9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10] 60 | heap_sort(reverse_sorted_array) 61 | print(reverse_sorted_array) 62 | print(f'Number of comparisons = {count}') 63 | ''' 64 | Heap : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10] 65 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 66 | Number of comparisons = 105 67 | ''' -------------------------------------------------------------------------------- /04. Algorithms/Sorting/Selection_Sort.py: -------------------------------------------------------------------------------- 1 | #Selection sort involves finding the minimum element in one pass through the array 2 | #and then swapping it with the first position of the unsorted part of the array. 3 | #Time complexity of selection sort is O(n^2) in all cases 4 | 5 | def selection_sort(array): 6 | count = 0 7 | for i in range(len(array)-1): #-1 because when only 1 elment remians, it will be already be sorted 8 | print(array) 9 | minimum = array[i] #We set the minu=imum element to be the ith element 10 | minimum_index = i #And the minimum index to be the ith index 11 | for j in range(i+1,len(array)): #Then we check the array from the i+1th element to the end 12 | count += 1 13 | if array[j] < minimum: #If a smaller element than the minimum element is found, we re-assign the minimum element and the minimu index 14 | minimum = array[j] 15 | minimum_index = j 16 | if minimum_index != i: #If minimum index has changed, i.e, a smaller element has been found, then we swap that element with the ith element 17 | array[minimum_index], array[i] = array[i], array[minimum_index] 18 | return (f'{array} \nNumber of comparisons = {count}') 19 | 20 | array = [5,9,3,10,45,2,0] 21 | print(selection_sort(array)) 22 | ''' 23 | [5, 9, 3, 10, 45, 2, 0] 24 | [0, 9, 3, 10, 45, 2, 5] 25 | [0, 2, 3, 10, 45, 9, 5] 26 | [0, 2, 3, 10, 45, 9, 5] 27 | [0, 2, 3, 5, 45, 9, 10] 28 | [0, 2, 3, 5, 9, 45, 10] 29 | [0, 2, 3, 5, 9, 10, 45] 30 | Number of comparisons = 21 31 | ''' 32 | sorted_array = [5,6,7,8,9] 33 | print(selection_sort(sorted_array)) 34 | """ 35 | [5, 6, 7, 8, 9] 36 | [5, 6, 7, 8, 9] 37 | [5, 6, 7, 8, 9] 38 | [5, 6, 7, 8, 9] 39 | [5, 6, 7, 8, 9] 40 | Number of comparisons = 10 41 | """ 42 | 43 | reverse_sorted_array = [9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10] 44 | print(selection_sort(reverse_sorted_array)) 45 | ''' 46 | [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10] 47 | 48 | [-10, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, 9] 49 | 50 | [-10, -9, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, 8, 9] 51 | 52 | [-10, -9, -8, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, 7, 8, 9] 53 | 54 | [-10, -9, -8, -7, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, 6, 7, 8, 9] 55 | 56 | [-10, -9, -8, -7, -6, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, 5, 6, 7, 8, 9] 57 | 58 | [-10, -9, -8, -7, -6, -5, 3, 2, 1, 0, -1, -2, -3, -4, 4, 5, 6, 7, 8, 9] 59 | 60 | [-10, -9, -8, -7, -6, -5, -4, 2, 1, 0, -1, -2, -3, 3, 4, 5, 6, 7, 8, 9] 61 | 62 | [-10, -9, -8, -7, -6, -5, -4, -3, 1, 0, -1, -2, 2, 3, 4, 5, 6, 7, 8, 9] 63 | 64 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9] 65 | 66 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 67 | 68 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 69 | 70 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 71 | 72 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 73 | 74 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 75 | 76 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 77 | 78 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 79 | 80 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 81 | 82 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 83 | 84 | [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 85 | 86 | Number of comparisons = 190 87 | ''' 88 | -------------------------------------------------------------------------------- /04. Algorithms/Sorting/legacy/bubble_sort.py: -------------------------------------------------------------------------------- 1 | def bubble_sort(arr): 2 | """ 3 | Fill in this docstring to practice writing docstrings 4 | along with summarizing what the function does 5 | """ 6 | swap_happened = True 7 | while swap_happened: 8 | print('bubble sort status: ' + str(arr)) 9 | swap_happened = False 10 | for num in range(len(arr)-1): 11 | if arr[num] > arr[num+1]: 12 | swap_happened = True 13 | arr[num], arr[num+1] = arr[num+1], arr[num] 14 | 15 | l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] # original case 16 | # l = [10, 9, 8, 8, 7, 6, 5, 4, 3, 2, 1] # worst case 17 | # l = [1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10] # best case 18 | bubble_sort(l) 19 | -------------------------------------------------------------------------------- /04. Algorithms/Sorting/legacy/insertion_sort.py: -------------------------------------------------------------------------------- 1 | def insertion_sort(arr): 2 | for key in range(1, len(arr)): 3 | if arr[key] < arr[key-1]: 4 | j = key 5 | while j > 0 and arr[j] < arr[j-1]: 6 | arr[j], arr[j-1] = arr[j-1], arr[j] 7 | j -= 1 8 | 9 | l = [6,1,8,4,10] 10 | insertion_sort(l) 11 | print(l) 12 | -------------------------------------------------------------------------------- /04. Algorithms/Sorting/legacy/merge_sort.py: -------------------------------------------------------------------------------- 1 | def merge_sorted(arr1,arr2): 2 | print("Merge function called with lists below:") 3 | print(f"left: {arr1} and right: {arr2}") 4 | sorted_arr = [] 5 | i, j = 0, 0 6 | 7 | while i < len(arr1) and j < len(arr2): 8 | 9 | if arr1[i] < arr2[j]: 10 | sorted_arr.append(arr1[i]) 11 | i += 1 12 | 13 | else: 14 | sorted_arr.append(arr2[j]) 15 | j += 1 16 | 17 | while j < len(arr2): 18 | sorted_arr.append(arr2[j]) 19 | j += 1 20 | 21 | 22 | while i < len(arr1): 23 | sorted_arr.append(arr1[i]) 24 | i += 1 25 | 26 | return sorted_arr 27 | 28 | def mergersort(arr): 29 | if len(arr) < 2: 30 | return arr[:] 31 | else: 32 | middle = len(arr)//2 33 | l1 = divide_arr(arr[:middle]) 34 | l2 = divide_arr(arr[middle:]) 35 | return merge_sorted(l1, l2) 36 | 37 | 38 | # xxxxxxxxxxxxxxxx Program Execution xxxxxxxxxxxxxxxx 39 | l = [8, 6, 2, 5, 10, 13, 4, 55, 61, 23, 100] 40 | print(divide_arr(l)) 41 | -------------------------------------------------------------------------------- /04. Algorithms/Sorting/legacy/quick_sort.py: -------------------------------------------------------------------------------- 1 | def quicksort(arr): 2 | if len(arr)<2: 3 | return arr 4 | else: 5 | pivot = arr[-1] 6 | smaller, equal, larger = [], [], [] 7 | for num in arr: 8 | if num < pivot: 9 | smaller.append(num) 10 | 11 | elif num == pivot: 12 | equal.append(num) 13 | 14 | else: 15 | larger.append(num) 16 | 17 | return quicksort(smaller)+ equal+ quicksort(larger) 18 | 19 | l = [6,8,1,4,10,7,8.9,3,2,5] 20 | print(quicksort(l)) -------------------------------------------------------------------------------- /04. Algorithms/Sorting/legacy/selection_sort.py: -------------------------------------------------------------------------------- 1 | def selection_sort(arr): 2 | """ 3 | Use selection sort algorithm to sort a list of numbers 4 | """ 5 | spot_marker = 0 6 | while spot_marker < len(arr): 7 | for num in range(spot_marker, len(arr)): 8 | if arr[num] < arr[spot_marker]: 9 | arr[spot_marker], arr[num] = arr[num], arr[spot_marker] 10 | spot_marker += 1 11 | 12 | l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] # original case 13 | # l = [10, 9, 8, 8, 7, 6, 5, 4, 3, 2, 1] # worst case 14 | # l = [1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10] # best case 15 | selection_sort(l) 16 | print(l) 17 | -------------------------------------------------------------------------------- /04. Algorithms/Traversals/bisection_iter.py: -------------------------------------------------------------------------------- 1 | def bisection_iter(n, arr): 2 | start = 0 3 | stop = len(arr)-1 4 | 5 | while start <= stop: 6 | mid = (start + stop)//2 7 | if n == arr[mid]: 8 | return (f"{n} found at index {mid}") 9 | 10 | elif n > arr[mid]: 11 | start = mid+1 12 | 13 | else: 14 | stop = mid-1 15 | 16 | return (f"{n} not found in list") 17 | 18 | def create_list(max_val): 19 | arr = [] 20 | for num in range(1, max_val+1): 21 | arr.append(num) 22 | 23 | return arr 24 | 25 | 26 | max = int(input("Enter the maximum length of the list : ")) 27 | num_to_search = int(input("Enter the number you want to search for : ")) 28 | l = create_list(max) 29 | print(bisection_iter(num_to_search, l)) -------------------------------------------------------------------------------- /04. Algorithms/Traversals/bisection_recur.py: -------------------------------------------------------------------------------- 1 | def bisection_recur(n, arr, start, stop): 2 | 3 | if start > stop: 4 | return f"{n} not found in list" 5 | 6 | else: 7 | mid = (start+stop)//2 8 | if n == arr[mid]: 9 | return f"{n} is found at index {mid}" 10 | 11 | elif n > arr[mid]: 12 | start = mid+1 13 | return bisection_recur(n, arr, start, stop) 14 | 15 | else: 16 | stop = mid-1 17 | return bisection_recur(n, arr, start, stop) 18 | 19 | def create_list(max_val): 20 | arr = [] 21 | for num in range(1, max_val+1): 22 | arr.append(num) 23 | 24 | return arr 25 | 26 | 27 | max = int(input("Enter the maximum length of the list : ")) 28 | num_to_search = int(input("Enter the number you want to search for : ")) 29 | l = create_list(max) 30 | print(l) 31 | print(bisection_recur(num_to_search, l, 1, len(l)-1)) -------------------------------------------------------------------------------- /05. File Handling and OOPS/data.txt: -------------------------------------------------------------------------------- 1 | mashrur,hossain:python,ruby,javascript 2 | joe,schmo:python,ruby,javascript 3 | john,schmoe:python,ruby,javascript 4 | jane,doe:c,ruby,javascript 5 | -------------------------------------------------------------------------------- /05. File Handling and OOPS/file+classes.py: -------------------------------------------------------------------------------- 1 | class Student: 2 | 3 | def __init__(self, first, last, courses=None): 4 | self.first_name = first 5 | self.last_name = last 6 | if courses == None: 7 | self.courses = [] 8 | else: 9 | self.courses = courses 10 | 11 | def add_course(self, course): 12 | if course not in self.courses: 13 | self.courses.append(course) 14 | else: 15 | print(f"{self.first_name} is already enrolled in the {course} course") 16 | 17 | def remove_course(self, course): 18 | if course in self.courses: 19 | self.courses.remove(course) 20 | else: 21 | print(f"{course} not found") 22 | 23 | def find_in_file(self, filename): 24 | with open(filename) as f: 25 | for line in f: 26 | first_name, last_name, course_details = Student.prep_record(line.strip()) 27 | student_read_in = Student(first_name, last_name, course_details) 28 | if self == student_read_in: 29 | return True 30 | return False 31 | 32 | def add_to_file(self, filename): 33 | if self.find_in_file(filename): 34 | return "Record already exists" 35 | else: 36 | record_to_add = Student.prep_to_write(self.first_name, self.last_name, self.courses) 37 | with open(filename, "a+") as to_write: 38 | to_write.write(record_to_add+"\n") 39 | return "Record added" 40 | 41 | 42 | @staticmethod 43 | def prep_record(line): 44 | line = line.split(":") 45 | first_name, last_name = line[0].split(",") 46 | course_details = line[1].rstrip().split(",") 47 | return first_name, last_name, course_details 48 | 49 | @staticmethod 50 | def prep_to_write(first_name, last_name, courses): 51 | full_name = first_name+','+last_name 52 | courses = ",".join(courses) 53 | return full_name+':'+courses 54 | 55 | def __eq__(self, other): 56 | return self.first_name == other.first_name and self.last_name == other.last_name 57 | 58 | def __len__(self): 59 | return len(self.courses) 60 | 61 | def __repr__(self): 62 | return f"Student('{self.first_name}','{self.last_name}',{self.courses})" 63 | 64 | def __str__(self): 65 | return f"First name: {self.first_name.capitalize()}\nLast name: {self.last_name.capitalize()}\ 66 | \nCourses: {', '.join(map(str.capitalize, self.courses))}" 67 | 68 | class StudentAthlete(Student): 69 | 70 | def __init__(self, first, last, courses=None, sport=None): 71 | super().__init__(first, last, courses) 72 | self.sport = sport 73 | 74 | courses = ["python","ruby","javascript"] 75 | jane = StudentAthlete("jane","doe",courses,"hockey") 76 | print(jane.sport) 77 | print(isinstance(jane, Student)) 78 | -------------------------------------------------------------------------------- /06. Error Handling/error_handling.py: -------------------------------------------------------------------------------- 1 | 2 | while True: 3 | try: 4 | age = int(input("Enter your age: ")) 5 | age_in_dogs_year = 10/age 6 | 7 | except ZeroDivisionError: 8 | print("enter age greater than 0") 9 | continue 10 | 11 | except ValueError: 12 | print("Please enter a no.") 13 | break 14 | 15 | except ValueError: 16 | print("!!!!") 17 | 18 | else: 19 | print(f"thank you, and your age is {age}") 20 | break 21 | 22 | finally: 23 | print("I will always get printed no matter what :)") 24 | 25 | print("can you hear me??????") 26 | -------------------------------------------------------------------------------- /06. Error Handling/error_handling2.py: -------------------------------------------------------------------------------- 1 | 2 | def division_fn(num1, num2): 3 | try: 4 | return num1/num2 5 | except (ZeroDivisionError, TypeError) as err: 6 | print(f'error: {err}') 7 | 8 | print(division_fn(1,'0')) 9 | print(division_fn(1,0)) 10 | print(division_fn(1,4)) 11 | -------------------------------------------------------------------------------- /06. Error Handling/raise_error.py: -------------------------------------------------------------------------------- 1 | 2 | # we can stop the program by raising our own errors. 3 | 4 | print("Hello!!!!") 5 | # raise TypeError("yo") 6 | raise Exception("Any message ") 7 | print("bye") 8 | -------------------------------------------------------------------------------- /06. Error Handling/raise_error2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | age = int(input("age: ")) 5 | age = 10/age 6 | raise ValueError("Ending the program") 7 | # raise Exception("quit") 8 | 9 | except ValueError: 10 | print("Please enter a no.") -------------------------------------------------------------------------------- /07. Functional Programming/dict_comprehension.py: -------------------------------------------------------------------------------- 1 | 2 | my_dict = {num:num**2 for num in range(1,11)} 3 | print(my_dict) 4 | 5 | random_dict = { 6 | 'a': 1, 7 | 'b': 2, 8 | 'c': 3, 9 | 'd': 4 10 | } 11 | 12 | my_new_dict = {k:v**2 for k,v in random_dict.items()} 13 | print(my_new_dict) 14 | 15 | my_new_dict2 = {k:v**2 for k,v in random_dict.items() if v % 2 == 0} 16 | print(my_new_dict2) 17 | -------------------------------------------------------------------------------- /07. Functional Programming/enumerate.py: -------------------------------------------------------------------------------- 1 | fruits = ['apple', 'banana', 'cherry', 'grape'] 2 | 3 | for index, fruit in enumerate(fruits, 1): 4 | print(index, fruit) 5 | -------------------------------------------------------------------------------- /07. Functional Programming/filter().py: -------------------------------------------------------------------------------- 1 | 2 | def only_even(item): 3 | return item % 2 == 0 4 | 5 | my_list = [5,8,9,2,5,6,98,56,62] 6 | 7 | print(filter(only_even, my_list)) 8 | print(list(filter(only_even, my_list))) 9 | print(list(map(only_even, my_list))) 10 | print(my_list) 11 | -------------------------------------------------------------------------------- /07. Functional Programming/lambda_expressions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import functools 4 | 5 | my_list = [1,2,3,4,5] 6 | 7 | print(list(map(lambda item: item*2, my_list))) 8 | 9 | print(list(filter(lambda item: item % 2 != 0, my_list))) 10 | 11 | print(functools.reduce(lambda acc,item: item+acc, my_list)) 12 | 13 | ''' 14 | syntax: 15 | lambda param: action(param) 16 | it automatically returns the action taken, 17 | it do not have any name, doesn't get stored in the memory. 18 | and so used only once. 19 | and behaves exactly like a function. 20 | ''' -------------------------------------------------------------------------------- /07. Functional Programming/lambda_expressions2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | a = [(0,2),(4,4),(10,-1),(5,3)] 4 | 5 | a.sort(key=lambda x:x[1], reverse=False) 6 | print(a) 7 | -------------------------------------------------------------------------------- /07. Functional Programming/list_set_comprehension.py: -------------------------------------------------------------------------------- 1 | 2 | my_list = [] 3 | 4 | for item in 'hello': 5 | my_list.append(item) 6 | 7 | print(my_list) 8 | 9 | my_list1 = [item for item in 'Saurabh'] 10 | print(my_list1) 11 | 12 | my_list2 = [num**2 for num in range(1,11)] 13 | print(my_list2) 14 | 15 | # only even squares 16 | my_set = {num**2 for num in range(1,11) if num**2 % 2 == 0} 17 | print(my_set) 18 | # remember that set don't contain duplicate values 19 | -------------------------------------------------------------------------------- /07. Functional Programming/map().py: -------------------------------------------------------------------------------- 1 | 2 | def multiply_by2(item): 3 | return item*2 4 | 5 | my_list = [5,8,9] 6 | 7 | print(map(multiply_by2, my_list)) # it returns a map object, which then we can convert to a list/tuple/set 8 | print(list(map(multiply_by2, my_list))) # notice that we just write the function name without the curly braces 9 | print(my_list) 10 | 11 | ''' 12 | notice that map is not modifying anything, and creating a new list. 13 | it is also using separate data and function to work upon them. 14 | it's a nice concept of Functional programming and pure function. 15 | ''' 16 | -------------------------------------------------------------------------------- /07. Functional Programming/pure_functions.py: -------------------------------------------------------------------------------- 1 | 2 | def multiply_by2(li): 3 | new_li = [] 4 | for item in li: 5 | new_li.append(item) 6 | return new_li 7 | 8 | print(multiply_by2([5,6,8])) 9 | 10 | ''' 11 | If we define 'new_li' outside the function, or print something inside the function, then it is no longer 12 | a pure function. 13 | ''' 14 | -------------------------------------------------------------------------------- /07. Functional Programming/reduce().py: -------------------------------------------------------------------------------- 1 | 2 | from functools import reduce 3 | 4 | def accumulator(acc, item): 5 | print(f'acc: {acc}, item: {item}') 6 | return acc+item 7 | 8 | my_list = [1,2,3,4,5] 9 | print(reduce(accumulator, my_list)) # by default takes '0' as the 3rd argument 10 | print(reduce(accumulator, my_list, 10)) 11 | print(my_list) 12 | 13 | ''' 14 | acc is nothing but the return of the last iteration. 15 | ''' -------------------------------------------------------------------------------- /07. Functional Programming/zip().py: -------------------------------------------------------------------------------- 1 | 2 | li1 = [1,2,3] 3 | set1 = {4,5,6} 4 | tuple1 = (7,8,9) 5 | 6 | print(zip(li1, set1, tuple1)) 7 | print(list(zip(li1, set1, tuple1))) # combines the items sequence wise into a sequence of tuples 8 | print(li1, set1, tuple1) 9 | 10 | ''' 11 | use the same no.s of items in all the iterables, otherwise it can ruin the sequence. 12 | ''' -------------------------------------------------------------------------------- /08. Decorators/HOC.py: -------------------------------------------------------------------------------- 1 | 2 | # Higher Order Function (HOC) is a function which returns another function, or accepts another function 3 | 4 | def greet(func): 5 | func() 6 | 7 | def greet2(): 8 | def hello(): 9 | print("hello!") 10 | return hello() 11 | 12 | print(greet(greet2)) 13 | -------------------------------------------------------------------------------- /08. Decorators/authentication.py: -------------------------------------------------------------------------------- 1 | # Create an @authenticated decorator that only allows the function to run is user1 has 'valid' set to True: 2 | user1 = { 3 | 'name': 'Sorna', 4 | 'valid': True 5 | } 6 | 7 | def authenticated(fn): 8 | def wrapper(*args, **kwargs): 9 | if args[0]['valid']: 10 | return fn(*args, **kwargs) 11 | return wrapper 12 | 13 | @authenticated 14 | def message_friends(user): 15 | print('message has been sent') 16 | 17 | message_friends(user1) -------------------------------------------------------------------------------- /08. Decorators/decorators.py: -------------------------------------------------------------------------------- 1 | 2 | def my_decorator(func): 3 | def wrap_func(): 4 | print("***********") 5 | func() 6 | print("***********") 7 | return wrap_func 8 | 9 | @my_decorator 10 | def hello(): 11 | print("Hello!") 12 | 13 | hello() 14 | 15 | # using decorator is same as doing the below: 16 | # my_decorator(hello)() 17 | -------------------------------------------------------------------------------- /08. Decorators/decorators0.py: -------------------------------------------------------------------------------- 1 | 2 | def my_decorator(func): 3 | def wrap_func(*args, **kwargs): 4 | func(*args, **kwargs) 5 | return wrap_func 6 | 7 | @my_decorator 8 | def hello(name, age): 9 | print(f"Hello {name}, your age is {age}.") 10 | 11 | @my_decorator 12 | def logged_in(username): 13 | print(f"{username} is logged in.") 14 | 15 | hello("saurabh", 21) 16 | logged_in("saurabh") 17 | 18 | # using decorator is same as doing the below: 19 | # my_decorator(hello)() 20 | -------------------------------------------------------------------------------- /08. Decorators/decorators1.py: -------------------------------------------------------------------------------- 1 | 2 | def hello(): 3 | print("hello!") 4 | 5 | greet = hello 6 | 7 | del hello 8 | # here the function is not deleted, just the keyword, because greet is still pointing to the function memory 9 | # and python has not deleted it 10 | #(hello() # this will give error, because it has been deleted 11 | print(greet) 12 | greet() 13 | -------------------------------------------------------------------------------- /08. Decorators/decorators2.py: -------------------------------------------------------------------------------- 1 | 2 | def hello(func): 3 | func() 4 | 5 | def greet(): 6 | print("Hello!") 7 | 8 | hello(greet) 9 | 10 | # this example shows us that functions can also be used as variables. 11 | -------------------------------------------------------------------------------- /08. Decorators/performance_decorator.py: -------------------------------------------------------------------------------- 1 | 2 | from time import time 3 | 4 | def performance(fn): 5 | def wrap_fn(*args, **kwargs): 6 | t1 = time() 7 | fn(*args, **kwargs) 8 | t2 = time() 9 | print(f'It took {t2-t1} sec') 10 | return wrap_fn 11 | 12 | @performance 13 | def long_fn(): 14 | for i in range(10000000): 15 | i*5 16 | 17 | long_fn() 18 | -------------------------------------------------------------------------------- /09. Debugging/dubugging_pdb.py: -------------------------------------------------------------------------------- 1 | import pdb 2 | 3 | def add(n1, n2): 4 | return n1+n2 5 | 6 | pdb.set_trace() 7 | add(4, 'five') 8 | 9 | ''' 10 | Some useful commands for pdb: 11 | a : Print the argument list of the current function. 12 | step: to run the current line of code, and stop at the first possible occasion 13 | help: to list all the commands available 14 | help : to see what a command does 15 | continue: to continue the program till the error comes 16 | w: previous line, current line and next line content 17 | next: Continue execution until the next line in the current function is reached or it returns. 18 | 19 | We can change the variables value in the console window as well. 20 | we can type in the variable name to get its value 21 | ''' 22 | -------------------------------------------------------------------------------- /10. Generators/fibonacci_list.py: -------------------------------------------------------------------------------- 1 | 2 | def fib(num): 3 | a = 0 4 | b= 1 5 | li=[] 6 | for i in range(num): 7 | li.append(a) 8 | temp = a 9 | a = b 10 | b = temp + b 11 | print(li) 12 | 13 | num = int(input("Enter a number: ")) 14 | fib(num) 15 | -------------------------------------------------------------------------------- /10. Generators/fibonacci_range.py: -------------------------------------------------------------------------------- 1 | 2 | def fib(num): 3 | a = 0 4 | b= 1 5 | for i in range(num): 6 | yield a 7 | temp = a 8 | a = b 9 | b = temp + b 10 | 11 | num = int(input("Enter a number: ")) 12 | 13 | for i in fib(num): 14 | print(i) 15 | -------------------------------------------------------------------------------- /10. Generators/generator.py: -------------------------------------------------------------------------------- 1 | 2 | # here we are generating our own generator function, just like a range(). 3 | 4 | def generator_fn(num): 5 | print("check") 6 | #yield 7 | for i in range(num): 8 | print("****") 9 | yield i*2 10 | print("####") 11 | 12 | g = generator_fn(3) 13 | print(g) 14 | print(next(g)) 15 | print(next(g)) 16 | print(next(g)) 17 | #print(next(g)) # StopIteration error 18 | print(g) 19 | 20 | for item in generator_fn(5): 21 | print(item) 22 | 23 | # here it goes to the generator_fn(), gets the 'i' value, pauses the function, until called for the 2nd time, 24 | # and so on, it doesn't store all the no.s in the memory (just the most recent one). 25 | 26 | ''' 27 | 'yield' pauses the function and comes back to it when we do something to it, which is called 'next'. 28 | 29 | if there is the keyword 'yield' written inside the function, then python recognises that its a 30 | generator function, and won't run the function until the function is being iterated. 31 | ''' 32 | -------------------------------------------------------------------------------- /10. Generators/our_own_forloop.py: -------------------------------------------------------------------------------- 1 | 2 | def my_own_forloop(iterable): 3 | iterator = iter(iterable) 4 | while True: 5 | try: 6 | print(iterator) 7 | print(next(iterator)) 8 | except StopIteration: 9 | break 10 | 11 | my_own_forloop([1,2,3,4,5]) 12 | -------------------------------------------------------------------------------- /10. Generators/our_own_range.py: -------------------------------------------------------------------------------- 1 | 2 | class OurOwnRange(): 3 | current = 0 4 | def __init__(self,first,last): 5 | self.first = first 6 | self.last = last 7 | 8 | def __iter__(self): 9 | return self 10 | 11 | def __next__(self): 12 | print("hehehheh") 13 | # if self.current < self.last: 14 | # num = OurOwnRange.current 15 | # OurOwnRange.current += 1 16 | # return num 17 | # raise StopIteration 18 | 19 | gen = OurOwnRange(0,10) 20 | print(gen) 21 | 22 | for i in gen: 23 | print(i) 24 | 25 | ''' 26 | loops by default deal with StopIteration error. they have build in functionality to handle them. 27 | ''' 28 | -------------------------------------------------------------------------------- /11. Regular Expressions/email_password_regex.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | 4 | email_pattern = re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)") 5 | check_email = email_pattern.fullmatch('saurabh_1089@gmail.com') 6 | 7 | password_patter = re.compile(r"([a-zA-Z0-9@#$%]{8,}$)") 8 | check_password = password_patter.fullmatch('12345678') 9 | 10 | if check_email and check_password: 11 | print("Both email and password are correct.") 12 | else: 13 | print("Try again.") 14 | 15 | ''' 16 | password is also checking for minimum 8 chars 17 | ''' 18 | -------------------------------------------------------------------------------- /11. Regular Expressions/regular_exp.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | string = "this is a really cool string really!" 4 | 5 | a = re.search('really',string) 6 | print(a) 7 | 8 | # the below 4 commands will give error if the searching string does not exist. 9 | print(a.span()) 10 | print(a.start()) 11 | print(a.end()) 12 | print(a.group()) 13 | 14 | pattern = re.compile('really') 15 | 16 | b = pattern.search(string) 17 | c = pattern.findall(string) 18 | 19 | pattern = re.compile('this is a really cool string really!') 20 | d = pattern.fullmatch('this is a really cool string really!') 21 | e = pattern.fullmatch('hello this is a really cool string really!') # this should be exact match, otherwise returns none 22 | 23 | pattern = re.compile('really') 24 | f = pattern.match('really cool feature') # it starts matching from the first character otherwise returns none 25 | g = pattern.match('yo really') 26 | 27 | print(f"b: {b}") 28 | print(f"c: {c}") 29 | print(f"d: {d}") 30 | print(f"e: {e}") 31 | print(f"f: {f}") 32 | print(f"g: {g}") 33 | -------------------------------------------------------------------------------- /12. Unit Testing/guess_game.py: -------------------------------------------------------------------------------- 1 | 2 | import random 3 | 4 | def run_guess(guess, answer): 5 | if 0 < guess < 11: 6 | if guess == answer: 7 | print('you are a genius!') 8 | return True 9 | else: 10 | print('hey bozo, I said 1~10') 11 | return False 12 | 13 | if __name__ == '__main__': # so that the game don't start while testing. 14 | answer = random.randint(1, 10) 15 | while True: 16 | try: 17 | guess = int(input('guess a number 1~10: ')) 18 | if (run_guess(guess, answer)): 19 | break 20 | except ValueError: 21 | print('please enter a number') 22 | continue 23 | 24 | ''' 25 | we test a function at a time. 26 | that is why it is called unit testing. 27 | hence we try to make pure functions and test them. 28 | ''' -------------------------------------------------------------------------------- /12. Unit Testing/script.py: -------------------------------------------------------------------------------- 1 | 2 | def add(num): 3 | try: 4 | return int(num) + 5 5 | except ValueError as err: 6 | return err 7 | -------------------------------------------------------------------------------- /12. Unit Testing/test.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import script 4 | 5 | class TestMain(unittest.TestCase): # inheriting TestCase class 6 | 7 | def setUp(self): # this method will run before starting all the other test methods 8 | print("Starting a method/test: ") 9 | 10 | def test_add(self): 11 | '''This is the info for this particular test''' 12 | test_param = 10 13 | result = script.add(test_param) 14 | self.assertEqual(result,15) 15 | 16 | def test_add2(self): 17 | test_param = 'random string' 18 | result = script.add(test_param) 19 | self.assertTrue(isinstance(result,ValueError)) 20 | 21 | def tearDown(self): # this method will run after every test method. Generally used to reset/cleaning up data variables. 22 | print("Cleaning up....") 23 | 24 | 25 | class A: 26 | print("\nClass A") 27 | 28 | if __name__ == '__main__': 29 | unittest.main() # this will run the entire classes present in the file 30 | 31 | 32 | class B: 33 | print("Class B") 34 | -------------------------------------------------------------------------------- /12. Unit Testing/test2.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import script 4 | 5 | class TestMain(unittest.TestCase): # inheriting TestCase class 6 | def test_add(self): 7 | test_param = 10 8 | result = script.add(test_param) 9 | self.assertEqual(result,15) 10 | 11 | def test_add2(self): 12 | test_param = 'random string' 13 | result = script.add(test_param) 14 | self.assertTrue(isinstance(result,ValueError)) 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /12. Unit Testing/test_guess_game.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import guess_game 4 | 5 | class testGame(unittest.TestCase): 6 | def test_game(self): 7 | result = guess_game.run_guess(5,5) 8 | self.assertTrue(result) 9 | 10 | def test_game2(self): 11 | result = guess_game.run_guess(0,5) 12 | self.assertFalse(result) 13 | 14 | def test_game3(self): 15 | result = guess_game.run_guess(15,4) 16 | self.assertFalse(result) 17 | 18 | if __name__ == '__main__': 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /13. Mini-Projects/Job Scheduler/data.txt: -------------------------------------------------------------------------------- 1 | 4:30,30,Price Loader 2 | 3:00,15,Load Transactions 3 | 8:30,90,Balance Sheet 4 | 1:30,60,Web Refresh 5 | 15:00,20,Transaction Validator 6 | 18:00,75,Batch Process 7 | 19:15,15,Batch Report 8 | 2:30,30,Transaction Preprocess 9 | 21:00,30,Nightly Refresh 10 | 22:30,15,Batch Cleanup 11 | 10:30,15,This will reject 12 | 19:25,10,This will reject 13 | 5:15,30,Schedule Distribution 14 | 23:00,15,Test Insert 15 | -------------------------------------------------------------------------------- /13. Mini-Projects/email_project/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | from random import randint, random, seed, choice 3 | from string import ascii_letters as letters 4 | 5 | def bisection_iter(n, arr): 6 | start = 0 7 | stop = len(arr)-1 8 | 9 | while start <= stop: 10 | mid = (start + stop)//2 11 | if n == arr[mid]: 12 | return mid, f"{n} found at index {mid}" 13 | 14 | elif n > arr[mid]: 15 | start = mid+1 16 | 17 | else: 18 | stop = mid-1 19 | 20 | return None, f"{n} not found in list" 21 | 22 | def analyze_func(func_name, *arr): 23 | tic = time.time() 24 | func_name(*arr) 25 | toc = time.time() 26 | seconds = toc-tic 27 | print(f"Time Elapsed while {func_name.__name__.capitalize()} --> {seconds:.5f}") 28 | 29 | 30 | seed(1) 31 | length_of_name = randint(1,15) 32 | 33 | def generate_name (): 34 | seed(1) 35 | length_of_name = randint(1,15) 36 | return ''.join(choice(letters) for i in range(length_of_name)) 37 | 38 | def get_domain(list_of_domain): 39 | return choice(list_of_domain) 40 | 41 | def generate_emails(list_of_domain, total_email): 42 | emails = [] 43 | for num in range(total_email): 44 | emails.append(generate_name()+"@"+get_domain(list_of_domain)) 45 | return emails -------------------------------------------------------------------------------- /13. Mini-Projects/email_project/run.py: -------------------------------------------------------------------------------- 1 | from demo import bisection_iter, analyze_func, generate_emails 2 | 3 | list_of_domains = ['gmail.com', 'yahoo.com', 'hotmail.com'] 4 | 5 | total_email = int(input("Number of emails in the list : ")) 6 | 7 | emails = generate_emails(list_of_domains, total_email) 8 | 9 | email = 'shushrut@gmail.com' 10 | email2 = 'sk6554@gmail.com' 11 | emails.append(email) 12 | 13 | sorted_emails = sorted(emails) 14 | 15 | index, found = bisection_iter(email, sorted_emails) 16 | 17 | print(found) 18 | 19 | if index == None: 20 | print("Email not found") 21 | 22 | else: 23 | print(f"Element at index : {index} is {sorted_emails[index]}") 24 | 25 | analyze_func(bisection_iter, email, sorted_emails) 26 | analyze_func(generate_emails, list_of_domains, total_email) 27 | 28 | -------------------------------------------------------------------------------- /13. Mini-Projects/hash_project/hash_algo.py: -------------------------------------------------------------------------------- 1 | class AlgoHashTable: 2 | 3 | def __init__ (self, size): 4 | self.size = size 5 | self.hash_table = self.create_buckets() 6 | 7 | def create_buckets(self): 8 | return [[] for _ in range(self.size)] 9 | 10 | def __str__(self): 11 | return "".join(str(item) for item in self.hash_table) 12 | 13 | 14 | 15 | def set_val(self, key, value): 16 | 17 | hashed_key = hash(key)%self.size 18 | bucket = self.hash_table[hashed_key] 19 | 20 | found_key = False 21 | 22 | for index, record in enumerate(bucket): 23 | record_key, record_value = record 24 | if record_key == key: 25 | found_key = True 26 | break 27 | 28 | if found_key: 29 | bucket[index] = (key, value) 30 | 31 | else: 32 | bucket.append((key, value)) 33 | 34 | def get_val(self, key): 35 | 36 | hashed_key = hash(key)%self.size 37 | bucket = self.hash_table[hashed_key] 38 | found_key = False 39 | 40 | for index, record in enumerate(bucket): 41 | record_key, record_value = record 42 | if record_key == key: 43 | found_key = True 44 | break 45 | 46 | if found_key: 47 | return record_value 48 | 49 | else: 50 | return f"{key} not found" 51 | 52 | 53 | 54 | 55 | hash_table = AlgoHashTable(200) 56 | 57 | file = open("data.txt") 58 | 59 | lines = file.readline().strip().split(':') 60 | 61 | for line in file: 62 | key, value = line.split(":") 63 | hash_table.set_val(key, value) 64 | 65 | 66 | print(hash_table) 67 | 68 | print("-"*100) 69 | 70 | print(hash_table.get_val("ahlrdmukjn@yaexample.com")) 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /13. Mini-Projects/hash_project/project_script.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from string import ascii_lowercase as letters 3 | 4 | list_of_domains = ['yaexample.com','goexample.com','example.com'] 5 | 6 | quotes = [ 'Luck is what happens when preparation meets opportunity', 7 | 'All cruelty springs from weakness', 8 | 'Begin at once to live, and count each separate day as a separate life', 9 | 'Throw me to the wolves and I will return leading the pack'] 10 | 11 | def generate_name(length_of_name): 12 | return ''.join(choice(letters) for i in range(length_of_name)) 13 | 14 | def get_domain(list_of_domains): 15 | return choice(list_of_domains) 16 | 17 | def get_quotes(list_of_quotes): 18 | return choice(list_of_quotes) 19 | 20 | def generate_records(length_of_name, list_of_domains, total_records, list_of_quotes): 21 | with open("./data.txt", "w") as to_write: 22 | for num in range(total_records): 23 | key = generate_name(length_of_name)+"@"+get_domain(list_of_domains) 24 | value = get_quotes(quotes) 25 | to_write.write(key + ":" + value + "\n") 26 | to_write.write("mashrur@example.com:Don't let me leave Murph\n") 27 | to_write.write("evgeny@example.com:All I do is win win win no matter what!\n") 28 | 29 | generate_records(10, list_of_domains, 199, quotes) 30 | -------------------------------------------------------------------------------- /13. Mini-Projects/hash_project/tempCodeRunnerFile.py: -------------------------------------------------------------------------------- 1 | print(key) -------------------------------------------------------------------------------- /13. Mini-Projects/recursion_miniprojects/countdown_timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | def recur_countdown_time(n): 4 | if n == 0: 5 | return n 6 | else: 7 | print(n) 8 | time.sleep(2) 9 | return recur_countdown_time(n-1) 10 | 11 | def iter_countdown_timer(n): 12 | while n>0: 13 | print(n) 14 | time.sleep(1) 15 | n -=1 16 | print(n) 17 | 18 | z = int(input("Enter the starting number : ")) 19 | print(f"Counting down from {z}") 20 | # iter_countdown_timer(z) 21 | 22 | print(recur_countdown_time(z)) -------------------------------------------------------------------------------- /13. Mini-Projects/recursion_miniprojects/factorial.py: -------------------------------------------------------------------------------- 1 | def factorial_recur(n): 2 | if n == 0: 3 | return 1 4 | else : 5 | return n*factorial_recur(n-1) 6 | 7 | z = int(input("Enter the value to get factorial : ")) 8 | print(f"The value of {z}! is {factorial_recur(z)}") -------------------------------------------------------------------------------- /13. Mini-Projects/recursion_miniprojects/fibonacci.py: -------------------------------------------------------------------------------- 1 | def fib_recur(z): 2 | if z == 0: 3 | return 0 4 | elif z == 1: 5 | return 1 6 | else: 7 | return fib_recur(z-1) + fib_recur(z-2) 8 | 9 | def fib_runner(z): 10 | print(f"The {z}th number in the fibonacci sequence is {fib_recur(z)}") 11 | 12 | z = int(input("Enter the number you want fibonacci value as : ")) 13 | fib_runner(z) -------------------------------------------------------------------------------- /13. Mini-Projects/recursion_miniprojects/multi_tasker.py: -------------------------------------------------------------------------------- 1 | #COUNTDOWN TIMER : 2 | import time 3 | 4 | def recur_countdown_time(n): 5 | if n == 0: 6 | return n 7 | else: 8 | print(n) 9 | time.sleep(1) 10 | return recur_countdown_time(n-1) 11 | 12 | def iter_countdown_timer(n): 13 | while n>0: 14 | print(n) 15 | time.sleep(1) 16 | n -=1 17 | print(n) 18 | 19 | #FACTORIAL : 20 | 21 | def factorial_recur(n): 22 | if n == 0: 23 | return 1 24 | else : 25 | return n*factorial_recur(n-1) 26 | 27 | #FIBONACCI SEQUENCE : 28 | 29 | def fib_recur(z): 30 | if z == 0: 31 | return 0 32 | elif z == 1: 33 | return 1 34 | else: 35 | return fib_recur(z-1) + fib_recur(z-2) 36 | 37 | def fib_runner(z): 38 | print(f"The {z}th number in the fibonacci sequence is {fib_recur(z)}") 39 | 40 | 41 | 42 | x = int(input("Type 1 for Countdown \nType 2 for Factorial \nType 3 for Fibonacci Sequence \n: ")) 43 | 44 | if x == 1: 45 | z = int(input("Enter the starting number : ")) 46 | print(f"Counting down from {z}") 47 | print(recur_countdown_time(z)) 48 | 49 | elif x == 2: 50 | z = int(input("Enter the value to get factorial : ")) 51 | print(f"The value of {z}! is {factorial_recur(z)}") 52 | 53 | elif x == 3: 54 | z = int(input("Enter the number you want fibonacci value as : ")) 55 | fib_runner(z) -------------------------------------------------------------------------------- /13. Mini-Projects/recursion_miniprojects/tempCodeRunnerFile.py: -------------------------------------------------------------------------------- 1 | x = input("Type 1 for Countdown \nType 2 for Factorial \nType 3 for Fibonacci Sequence \n: ") 2 | 3 | if (x == 1): 4 | z = int(input("Enter the starting number : ")) 5 | print(f"Counting down from {z}") 6 | print(recur_countdown_time(z)) 7 | 8 | elif (x == 2): 9 | z = int(input("Enter the value to get factorial : ")) 10 | print(f"The value of {z}! is {factorial_recur(z)}") 11 | 12 | elif (x == 3): 13 | z = int(input("Enter the number you want fibonacci value as : ")) 14 | fib_runner(z) -------------------------------------------------------------------------------- /13. Mini-Projects/runtime_analyser/analyser.py: -------------------------------------------------------------------------------- 1 | from sorts import quicksort, mergersort, bubblesort, insertionsort, selectionsort 2 | import random 3 | import time 4 | 5 | 6 | def create_random_list(size, max_val): 7 | ran_list = [] 8 | for num in range(size): 9 | ran_list.append(random.randint(1,max_val)) 10 | return(ran_list) 11 | 12 | 13 | def analyze_func(func_name, arr): 14 | tic = time.time() 15 | func_name(arr) 16 | toc = time.time() 17 | seconds = toc-tic 18 | print(f"Time Elapsed while {func_name.__name__.capitalize()} --> {seconds:.5f}") 19 | 20 | size = int(input("What size of list you want to create? ")) 21 | max = int(input("What is the max value of the range? ")) 22 | run_time = int(input("How many times you want to run : ")) 23 | 24 | 25 | for num in range(run_time): 26 | print(f"Run : {num+1}") 27 | l = create_random_list(size,max) 28 | analyze_func(quicksort, l) 29 | analyze_func(mergersort, l) 30 | analyze_func(bubblesort, l.copy()) 31 | analyze_func(sorted, l) 32 | analyze_func(insertionsort, l.copy()) 33 | analyze_func(selectionsort, l.copy()) 34 | print("-"*70) 35 | -------------------------------------------------------------------------------- /13. Mini-Projects/runtime_analyser/sorts.py: -------------------------------------------------------------------------------- 1 | def quicksort(arr): 2 | if len(arr)<2: 3 | return arr 4 | else: 5 | pivot = arr[-1] 6 | smaller, equal, larger = [], [], [] 7 | for num in arr: 8 | if num < pivot: 9 | smaller.append(num) 10 | 11 | elif num == pivot: 12 | equal.append(num) 13 | 14 | else: 15 | larger.append(num) 16 | 17 | return quicksort(smaller)+ equal+ quicksort(larger) 18 | 19 | 20 | def merge_sorted(arr1,arr2): 21 | sorted_arr = [] 22 | i, j = 0, 0 23 | 24 | while i < len(arr1) and j < len(arr2): 25 | 26 | if arr1[i] < arr2[j]: 27 | sorted_arr.append(arr1[i]) 28 | i += 1 29 | 30 | else: 31 | sorted_arr.append(arr2[j]) 32 | j += 1 33 | 34 | while j < len(arr2): 35 | sorted_arr.append(arr2[j]) 36 | j += 1 37 | 38 | 39 | while i < len(arr1): 40 | sorted_arr.append(arr1[i]) 41 | i += 1 42 | 43 | return sorted_arr 44 | 45 | def mergersort(arr): 46 | if len(arr) < 2: 47 | return arr[:] 48 | else: 49 | middle = len(arr)//2 50 | l1 = mergersort(arr[:middle]) 51 | l2 = mergersort(arr[middle:]) 52 | return merge_sorted(l1, l2) 53 | 54 | 55 | def bubblesort(arr): 56 | swap_happened = True 57 | while swap_happened: 58 | swap_happened = False 59 | for num in range(len(arr)-1): 60 | if arr[num] > arr[num+1]: 61 | swap_happened = True 62 | arr[num], arr[num+1] = arr[num+1], arr[num] 63 | 64 | def insertionsort(arr): 65 | for key in range(1, len(arr)): 66 | if arr[key] < arr[key-1]: 67 | j = key 68 | while j > 0 and arr[j] < arr[j-1]: 69 | arr[j], arr[j-1] = arr[j-1], arr[j] 70 | j -= 1 71 | 72 | def selectionsort(arr): 73 | spot_marker = 0 74 | while spot_marker < len(arr): 75 | for num in range(spot_marker, len(arr)): 76 | if arr[num] < arr[spot_marker]: 77 | arr[spot_marker], arr[num] = arr[num], arr[spot_marker] 78 | spot_marker += 1 -------------------------------------------------------------------------------- /14. Questions/leetcode 01 - two sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. 3 | 4 | You may assume that each input would have exactly one solution, and you may not use the same element twice. 5 | 6 | You can return the answer in any order. 7 | 8 | Question: https://leetcode.com/problems/two-sum/ 9 | 10 | """ 11 | #O(n)^2 solution - brute force 12 | class Solution: 13 | def twoSum(self, nums: List[int], target: int) -> List[int]: 14 | for i in range(len(nums)): 15 | for j in range(i+1, len(nums)): 16 | if nums[i] + nums[j] == target: 17 | return [i,j] 18 | return [] 19 | 20 | #O(n) solution - hash maps 21 | 22 | class Solution: 23 | def twoSum(self, nums: List[int], target: int) -> List[int]: 24 | prevMap = {} 25 | 26 | for i, n in enumerate(nums): 27 | diff = target-n 28 | if diff in prevMap: 29 | return[prevMap[diff],i] 30 | prevMap[n] = i 31 | return -------------------------------------------------------------------------------- /14. Questions/leetcode 02 - add two numbers.py: -------------------------------------------------------------------------------- 1 | # add two numbers | leetcode 02 | https://leetcode.com/problems/add-two-numbers/ 2 | 3 | # Definition for singly-linked list. 4 | class ListNode: 5 | def __init__(self, val=0, next=None): 6 | self.val = val 7 | self.next = next 8 | 9 | class Solution: 10 | def addTwoNumbers(self, l1: list[ListNode], l2: list[ListNode]) -> list[ListNode]: 11 | res = ListNode() 12 | head = res 13 | 14 | while l1 != None or l2 != None: 15 | if l1 == None: 16 | this_val = res.val + l2.val 17 | l2 = l2.next 18 | elif l2 == None: 19 | this_val = res.val + l1.val 20 | l1 = l1.next 21 | else: 22 | this_val = res.val + l1.val + l2.val 23 | l1, l2 = l1.next, l2.next 24 | 25 | this_digit = this_val % 10 26 | next_digit = this_val // 10 27 | 28 | res.val = this_digit 29 | if l1 != None or l2 != None: 30 | res.next = ListNode(next_digit) 31 | res = res.next 32 | 33 | if next_digit > 0: 34 | res.next = ListNode(next_digit) 35 | res = res.next 36 | 37 | return head 38 | -------------------------------------------------------------------------------- /14. Questions/leetcode 03 - longest substring without repeating characters.py: -------------------------------------------------------------------------------- 1 | # longest substring without repeating characters | leetcode 03 | https://leetcode.com/problems/longest-substring-without-repeating-characters 2 | # sliding window; remove elements until last occurence of current duplicate 3 | 4 | class Solution: 5 | def lengthOfLongestSubstring(self, s: str) -> int: 6 | ptrL = 0 7 | seen = dict() 8 | longest = 0 9 | 10 | for ptrR in range(len(s)): 11 | while seen.get(s[ptrR]) is not None: 12 | seen.pop(s[ptrL]) 13 | ptrL += 1 14 | seen[s[ptrR]] = True 15 | longest = max(ptrR - ptrL + 1, longest) 16 | 17 | return longest 18 | -------------------------------------------------------------------------------- /14. Questions/leetcode 09 - palindrome number.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an integer x, return true if x is palindrome integer. 3 | 4 | An integer is a palindrome when it reads the same backward as forward. 5 | 6 | For example, 121 is a palindrome while 123 is not. 7 | 8 | Question: https://leetcode.com/problems/palindrome-number/ 9 | 10 | """ 11 | 12 | 13 | class Solution: 14 | def isPalindrome(self, x: int) -> bool: 15 | if x < 0: 16 | return False 17 | c = x 18 | b = 0 19 | 20 | while c: 21 | b = b * 10 + c % 10 22 | c //= 10 23 | 24 | return b == x 25 | -------------------------------------------------------------------------------- /14. Questions/leetcode 100 - same tree.py: -------------------------------------------------------------------------------- 1 | # same tree | leetcode 100 | https://leetcode.com/problems/same-tree/ 2 | # given a root of each of the two trees, check if the trees are the exact same or not 3 | # method: (DFS) inorder traversal to compare left subtree, current node and right subtree 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def isSameTree(self, p, q): 14 | if p is None and q is None: 15 | return True 16 | 17 | if p is None or q is None: 18 | return False 19 | 20 | lResult = self.isSameTree(p.left, q.left) 21 | nResult = p.val == q.val 22 | rResult = self.isSameTree(p.right, q.right) 23 | 24 | return lResult and nResult and rResult -------------------------------------------------------------------------------- /14. Questions/leetcode 101 - symmetric tree.py: -------------------------------------------------------------------------------- 1 | # symmetric tree | leetcode 101 | https://leetcode.com/problems/symmetric-tree/ 2 | # given the root of a binary tree, check whether it is a mirror of itself 3 | # method: recursively compare two copies of the same tree 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def isSymmetric(self, root): 14 | def checkSymm(copy1, copy2): 15 | if copy1 is None and copy2 is None: 16 | return True 17 | if copy1 is None or copy2 is None: 18 | return False 19 | 20 | return (copy1.val == copy2.val) and checkSymm(copy1.left, copy2.right) and checkSymm(copy1.right, copy2.left) 21 | 22 | return checkSymm(root, root) 23 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1011 - capacity to ship packages.py: -------------------------------------------------------------------------------- 1 | # capacity to ship packages within D days | leetcode 1011 | https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/ 2 | # binary search on a range of min and max capacity required 3 | # min capacity = max(weights) and max capacity = sum(weights) 4 | 5 | class Solution: 6 | def shipWithinDays(self, weights: list[int], days: int) -> int: 7 | low, high = max(weights), sum(weights) 8 | res = high 9 | 10 | # check if days required for a capacity is less than D 11 | def isPossible (capacity): 12 | daysReq = 1 13 | window = capacity 14 | for weight in weights: 15 | if window - weight < 0: 16 | window = capacity 17 | daysReq += 1 18 | window -= weight 19 | 20 | return daysReq <= days 21 | 22 | # binary search on [min...max] 23 | while low <= high: 24 | mid = (high + low) // 2 25 | 26 | if isPossible(mid): 27 | res = min(res, mid) 28 | high = mid - 1 29 | else: 30 | low = mid + 1 31 | 32 | return res 33 | 34 | -------------------------------------------------------------------------------- /14. Questions/leetcode 102 - level order traversal of tree.py: -------------------------------------------------------------------------------- 1 | # binary tree level order traversal | leetcode 102 | https://leetcode.com/problems/binary-tree-level-order-traversal/ 2 | # order: from left to right, level by level 3 | # method: breadth first search 4 | 5 | #Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | 14 | # l to r, level by level 15 | def levelOrder(self, root): 16 | res = [] 17 | tempQ = [] 18 | 19 | # queue to track visits 20 | tempQ.append(root) 21 | LtempQ = len(tempQ) 22 | 23 | # keep iterating till: 24 | # the track queue is empty 25 | while LtempQ is not 0: 26 | LtempQ = len(tempQ) 27 | level = [] 28 | for i in range(LtempQ): 29 | node = tempQ.pop(0) # pop this node from queue (visited) 30 | if node is not None: 31 | level.append(node.val) # add this node to the level 32 | tempQ.append(node.left) # add left child to queue (to visit) 33 | tempQ.append(node.right) # add right child to queue (to visit) 34 | if len(level) is not 0: 35 | res.append(level) 36 | 37 | return res 38 | 39 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1022 - sum of root-leaf binary num.py: -------------------------------------------------------------------------------- 1 | # sum of root to leaf binary numbers | leetcode 1022 | https://leetcode.com/problems/sum-of-root-to-leaf-binary-numbers/ 2 | # method: (dfs) for each node, left-shift 1 bit and add val. 3 | # return sum of both left and right subtree 4 | # return sum till now at each leaf 5 | 6 | # Definition for a binary tree node. 7 | class TreeNode: 8 | def __init__(self, val=0, left=None, right=None): 9 | self.val = val 10 | self.left = left 11 | self.right = right 12 | 13 | class Solution: 14 | def sumRootToLeaf(self, root) -> int: 15 | def dfsSum(root, total): 16 | if root is None: 17 | return 0 18 | 19 | total = (total << 1) | root.val 20 | 21 | if root.left is None and root.right is None: 22 | return total 23 | 24 | return dfsSum(root.left, total) + dfsSum(root.right, total) 25 | 26 | return dfsSum(root, 0) -------------------------------------------------------------------------------- /14. Questions/leetcode 103 - zigzag level order traversal.py: -------------------------------------------------------------------------------- 1 | # binary tree zigzag level order traversal | leetcode 103 | https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal 2 | # use flag to keep track of reversed levels; O(n) because worst case is full level - n/2 elements 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def zigzagLevelOrder(self, root: TreeNode) -> list[list[int]]: 13 | res = [] 14 | tempQ = [] 15 | zig = False 16 | 17 | # queue to track visits 18 | tempQ.append(root) 19 | LtempQ = len(tempQ) 20 | 21 | # keep iterating till: 22 | # the track queue is empty 23 | while LtempQ is not 0: 24 | LtempQ = len(tempQ) 25 | level = [] 26 | for i in range(LtempQ): 27 | node = tempQ.pop(0) # pop this node from queue (visited) 28 | if node is not None: 29 | level.append(node.val) # add this node to the level 30 | tempQ.append(node.left) # add left child to queue (to visit) 31 | tempQ.append(node.right) # add right child to queue (to visit) 32 | 33 | if len(level) is not 0: # add level and reverse if zig 34 | res.append(reversed(level) if zig else level) 35 | zig = not zig 36 | 37 | return res -------------------------------------------------------------------------------- /14. Questions/leetcode 104 - max depth of binary tree.py: -------------------------------------------------------------------------------- 1 | # max depth of binary tree | leetcode 104 | https://leetcode.com/problems/maximum-depth-of-binary-tree/ 2 | # given the root of a binary tree, return its maximum depth. 3 | # method: recursively increment left and right count for each new node and return max 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def maxDepth(self, root): 14 | def findDepth(node): 15 | if node is None: 16 | return -1 17 | 18 | ldepth = findDepth(node.left) 19 | rdepth = findDepth(node.right) 20 | 21 | if ldepth > rdepth: 22 | return ldepth + 1 23 | else: 24 | return rdepth + 1 25 | 26 | return findDepth(root) + 1 27 | -------------------------------------------------------------------------------- /14. Questions/leetcode 108 - sorted array to bst.py: -------------------------------------------------------------------------------- 1 | # sorted array to bst | leetcode 108 | https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/ 2 | # given a sorted array of int, convert it to a balanced binary search tree 3 | # method: take middle element as root, use recursion for depth first, add each subtree as a balanced bst 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def sortedArrayToBST(self, nums): 14 | if not nums: 15 | return None 16 | 17 | mid = len(nums) // 2 18 | 19 | root = TreeNode(val = nums[mid]) 20 | root.left = self.sortedArrayToBST(nums[:mid]) 21 | root.right = self.sortedArrayToBST(nums[mid+1:]) 22 | 23 | return root -------------------------------------------------------------------------------- /14. Questions/leetcode 110 - balanced bst.py: -------------------------------------------------------------------------------- 1 | # balanced bst | leetcode 110 | https://leetcode.com/problems/balance-a-binary-search-tree/ 2 | # given a bst, check if it is balanced or not 3 | # method: for each subtree, check if its left and right subtrees and balanced, and return the maxDepth + 1 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def isBalanced(self, root) -> bool: 14 | def dfs(root): 15 | if root is None: return [True, 0] 16 | 17 | left, right = dfs(root.left), dfs(root.right) 18 | balanced = left[0] and right[0] and abs(left[1] - right[1]) <= 1 19 | 20 | return [balanced, max(left[1], right[1]) + 1] 21 | 22 | return dfs(root)[0] -------------------------------------------------------------------------------- /14. Questions/leetcode 112 - path sum.py: -------------------------------------------------------------------------------- 1 | # path sum | leetcode 112 | https://leetcode.com/problems/path-sum/ 2 | # given the root of a tree, check if there exists a path whose sum equals target 3 | # method: (dfs) update curSum for each node, and return true or false for each subtree 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | def hasPathSum(self, root, targetSum): 14 | 15 | def dfs(root, curSum): 16 | if root is None: 17 | return False 18 | 19 | curSum += root.val 20 | if root.left is None and root.right is None: 21 | return curSum == targetSum 22 | 23 | return dfs(root.left, curSum) or dfs(root.right, curSum) 24 | 25 | return dfs(root, 0) 26 | 27 | 28 | -------------------------------------------------------------------------------- /14. Questions/leetcode 114 - binary tree preorder traversal.py: -------------------------------------------------------------------------------- 1 | # binary tree preorder traversal | leetcode 94 | https://leetcode.com/problems/binary-tree-preorder-traversal/ 2 | # method: node, left subtree, right subtree recursively 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def inorderTraversal(self, root): 13 | travList = [] 14 | 15 | def traverse(root, travList): 16 | if root is None: 17 | return None 18 | 19 | travList.append(root.val) # add this node 20 | traverse(root.left, travList) # traverse left subtree and add nodes 21 | traverse(root.right, travList) # traverse right subtree and add nodes 22 | 23 | traverse(root, travList) 24 | return travList 25 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1147 - largest number at least twice of others.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are given an integer array nums where the largest integer is unique. 3 | 4 | Determine whether the largest element in the array is at least twice as much as every other number in the array. If it is, return the index of the largest element, or return -1 otherwise. 5 | 6 | Question: https://leetcode.com/explore/learn/card/array-and-string/201/introduction-to-array/1147/ 7 | 8 | """ 9 | 10 | class Solution: 11 | def dominantIndex(self, nums: List[int]) -> int: 12 | if len(nums) == 1: 13 | return 0 14 | a = max(nums) 15 | for i in range(len(nums)): 16 | if nums[i] == a: 17 | b = i 18 | nums.remove(a) 19 | count = 0 20 | for i in nums: 21 | if (a >= 2*i): 22 | count = count +1 23 | if count == len(nums): 24 | return b 25 | else: 26 | return -1 -------------------------------------------------------------------------------- /14. Questions/leetcode 121 - best time to buy and sell stock.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are given an array prices where prices[i] is the price of a given stock on the ith day. 3 | 4 | You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. 5 | 6 | Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0. 7 | 8 | Question: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ 9 | 10 | """ 11 | class Solution: 12 | def maxProfit(self, prices: List[int]) -> int: 13 | l = 0 14 | r = 1 15 | maxP = 0 16 | 17 | while r int: 6 | if nums == []: 7 | return 0 8 | 9 | all = set(nums) 10 | longest = 0 11 | 12 | for each in all: 13 | if each - 1 not in all: 14 | curr = each 15 | seq = 1 16 | while curr + 1 in all: 17 | seq += 1 18 | curr = curr + 1 19 | if seq > longest: 20 | longest = seq 21 | 22 | return longest -------------------------------------------------------------------------------- /14. Questions/leetcode 13 - roman to integer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M. 3 | 4 | Symbol Value 5 | I 1 6 | V 5 7 | X 10 8 | L 50 9 | C 100 10 | D 500 11 | M 1000 12 | For example, 2 is written as II in Roman numeral, just two one's added together. 12 is written as XII, which is simply X + II. The number 27 is written as XXVII, which is XX + V + II. 13 | 14 | Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used: 15 | 16 | I can be placed before V (5) and X (10) to make 4 and 9. 17 | X can be placed before L (50) and C (100) to make 40 and 90. 18 | C can be placed before D (500) and M (1000) to make 400 and 900. 19 | Given a roman numeral, convert it to an integer. 20 | 21 | Questions: https://leetcode.com/problems/roman-to-integer/ 22 | 23 | """ 24 | 25 | 26 | class Solution: 27 | def romanToInt(self, s: str) -> int: 28 | roman = {'I':1, 'V':5, 'X': 10, 'L':50, 'C':100, 'D':500, 'M':1000} 29 | 30 | res = 0 31 | 32 | for i in range(len(s)): 33 | if i+1 int: 13 | result = 0 14 | maxHeight = 0 15 | 16 | # dfs 17 | def dfs(node, currHeight): 18 | nonlocal result, maxHeight 19 | if node is None: 20 | return 21 | 22 | # reset if current height is not max 23 | if currHeight > maxHeight: 24 | result = 0 25 | maxHeight = currHeight 26 | 27 | # add to sum if current height is max 28 | if currHeight == maxHeight: 29 | result += node.val 30 | 31 | # recursively traverse left and right subtrees 32 | dfs(node.left, currHeight + 1) 33 | dfs(node.right, currHeight + 1) 34 | 35 | dfs(root, 0) 36 | return result 37 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1305 - all elements in two binary search trees.py: -------------------------------------------------------------------------------- 1 | # all elements in two bst | leetcode 1305 | https://leetcode.com/problems/all-elements-in-two-binary-search-trees/ 2 | # method: dfs, sort 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def getAllElements(self, root1: TreeNode, root2: TreeNode) -> list[int]: 13 | elements = [] 14 | 15 | def dfs(node): 16 | if node is None: 17 | return 18 | 19 | dfs(node.left) 20 | elements.append(node.val) 21 | dfs(node.right) 22 | 23 | dfs(root1) 24 | dfs(root2) 25 | elements.sort() 26 | return elements 27 | -------------------------------------------------------------------------------- /14. Questions/leetcode 133 - clone graph.py: -------------------------------------------------------------------------------- 1 | # clone graph | leetcode 133 | https://leetcode.com/problems/clone-graph/ 2 | # method: depth first search, recursively add neighbours 3 | 4 | # Definition for a Node. 5 | class Node: 6 | def __init__(self, val = 0, neighbors = None): 7 | self.val = val 8 | self.neighbors = neighbors if neighbors is not None else [] 9 | 10 | class Solution: 11 | def cloneGraph(self, node: Node): 12 | oldToNew = {} 13 | 14 | def dfs(node): 15 | if node in oldToNew: 16 | return oldToNew[node] 17 | 18 | duplicate = Node(node.val) 19 | oldToNew[node] = duplicate 20 | for neighbour in node.neighbors: 21 | duplicate.neighbors.append(dfs(neighbour)) 22 | 23 | return duplicate 24 | 25 | return dfs(node) if node else None 26 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1379 - corresponding target in cloned tree.py: -------------------------------------------------------------------------------- 1 | # corresponding node in a clone of the binary tree | leetcode 1379 | https://leetcode.com/problems/find-a-corresponding-node-of-a-binary-tree-in-a-clone-of-that-tree/ 2 | # return a reference to the same node in a cloned tree 3 | # method: traverse through the original and the cloned tree parallely until the original matches the target 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, x): 8 | self.val = x 9 | self.left = None 10 | self.right = None 11 | 12 | class Solution: 13 | def getTargetCopy(self, original: TreeNode, cloned: TreeNode, target: TreeNode) -> TreeNode: 14 | self.clonedTarget = None 15 | def inorderTraversal(original, cloned): 16 | if original: 17 | inorderTraversal(original.left, cloned.left) 18 | if original is target: 19 | self.clonedTarget = cloned 20 | inorderTraversal(original.right, cloned.right) 21 | 22 | inorderTraversal(original, cloned) 23 | return self.clonedTarget -------------------------------------------------------------------------------- /14. Questions/leetcode 1382 - balance a bst.py: -------------------------------------------------------------------------------- 1 | # balance a bst | leetcode 1382 | https://leetcode.com/problems/balance-a-binary-search-tree/ 2 | # given a bst, return a balanced bst 3 | # method: use inorder traversal to make a sorted array, convert sorted array to balanced bst 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | class Solution: 13 | # convert sorted array to bst 14 | def sortedArrayToBST(self, nums): 15 | if not nums: 16 | return None 17 | 18 | mid = len(nums) // 2 19 | 20 | root = TreeNode(val = nums[mid]) 21 | root.left = self.sortedArrayToBST(nums[:mid]) 22 | root.right = self.sortedArrayToBST(nums[mid+1:]) 23 | 24 | return root 25 | 26 | # in-order traveral gives sorted array 27 | def inorderTraversal(self, root): 28 | travList = [] 29 | 30 | def traverse(root, travList): 31 | if root is None: 32 | return None 33 | 34 | traverse(root.left, travList) 35 | travList.append(root.val) 36 | traverse(root.right, travList) 37 | 38 | traverse(root, travList) 39 | return travList 40 | 41 | # balance a binary search tree 42 | def balanceBST(self, root): 43 | return self.sortedArrayToBST(self.inorderTraversal(root)) -------------------------------------------------------------------------------- /14. Questions/leetcode 14 - longest common prefix.py: -------------------------------------------------------------------------------- 1 | """ 2 | Write a function to find the longest common prefix string amongst an array of strings. 3 | 4 | If there is no common prefix, return an empty string "". 5 | 6 | Questions: https://leetcode.com/problems/longest-common-prefix/ 7 | 8 | """ 9 | 10 | class Solution: 11 | def longestCommonPrefix(self, strs: List[str]) -> str: 12 | res = "" 13 | n = len(strs) 14 | strs.sort() 15 | first = strs[0] 16 | last = strs[n-1] 17 | for i in range(len(first)): 18 | if first[i] != last[i]: 19 | return res 20 | else: 21 | res = res + first[i] 22 | return res 23 | 24 | 25 | 26 | # for i in range(len(strs[0])): 27 | # for s in strs: 28 | # if i == len(s) or s[i] != strs[0][i]: 29 | # return res 30 | 31 | # res += strs[0][i] 32 | 33 | # return res -------------------------------------------------------------------------------- /14. Questions/leetcode 15 - three sum.py: -------------------------------------------------------------------------------- 1 | # three sum | leetcode 15 | https://leetcode.com/problems/3sum/ 2 | # - sorted; nested loop; outer loop for first element 3 | # - inner loop for two sum on rest of list 4 | # - avoid duplicates by shifting window till last occurrence 5 | 6 | class Solution: 7 | def threeSum(self, nums: list[int]) -> list[list[int]]: 8 | nums.sort() 9 | N = len(nums) 10 | triplets = [] 11 | for i in range(N): 12 | if i > 0 and nums[i] == nums[i - 1]: 13 | continue 14 | 15 | ptrL = i + 1 16 | ptrR = N - 1 17 | while ptrL < ptrR: 18 | s = nums[i] + nums[ptrL] + nums[ptrR] 19 | if s > 0: 20 | ptrR -= 1 21 | elif s < 0: 22 | ptrL += 1 23 | else: 24 | triplets.append([nums[i], nums[ptrL], nums[ptrR]]) 25 | ptrL += 1 26 | while nums[ptrL] == nums[ptrL - 1] and ptrL < ptrR: 27 | ptrL += 1 28 | 29 | return triplets -------------------------------------------------------------------------------- /14. Questions/leetcode 162 - peak element.py: -------------------------------------------------------------------------------- 1 | # find peak element | leetcode 162 | https://leetcode.com/problems/find-peak-element/ 2 | # using binary search to determine 3 | # if the "middle" element is on a +ve / -ve slope 4 | 5 | class Solution: 6 | def findPeakElement(self, nums: list[int]) -> int: 7 | a = 0 8 | b = len(nums) - 1 9 | while a < b: 10 | k = (a + b) // 2 11 | if nums[k] > nums[k + 1]: 12 | b = k 13 | else: 14 | a = k + 1 15 | 16 | return a -------------------------------------------------------------------------------- /14. Questions/leetcode 167 - two sum II.py: -------------------------------------------------------------------------------- 1 | # two sum II - input array is sorted | leetcode 167 | https://leetcode.com/problems/two-sum-ii-input-array-is-sorted 2 | # use two pointers on sorted array; if sum > target slide window left, else slide window right 3 | 4 | class Solution: 5 | def twoSum(self, numbers: list[int], target: int) -> list[int]: 6 | ptrL = 0 7 | ptrR = 1 8 | N = len(numbers) 9 | 10 | while ptrR < N: 11 | s = numbers[ptrR] + numbers[ptrL] 12 | if s == target: 13 | return [ptrL + 1, ptrR + 1] 14 | elif s < target: 15 | ptrL += 1 16 | ptrR += 1 17 | else: 18 | ptrL -= 1 19 | 20 | # unreachable for testcases with exactly one solution 21 | return [-1, -1] -------------------------------------------------------------------------------- /14. Questions/leetcode 1971 - find if path exists in a graph.py: -------------------------------------------------------------------------------- 1 | # find if path exists in a graph | leetcode 1971 | https://leetcode.com/problems/find-if-path-exists-in-graph/ 2 | # method: adjacency list, visited and toVisit lists 3 | 4 | from collections import defaultdict 5 | 6 | class Solution: 7 | def validPath(self, n: int, edges: list[list[int]], source: int, destination: int) -> bool: 8 | # edge case 9 | if n == 1 and source == destination: 10 | return True 11 | 12 | edgeMap = defaultdict(list) # adjacency list 13 | for edge in edges: 14 | edgeMap[edge[0]].append(edge[1]) 15 | edgeMap[edge[1]].append(edge[0]) 16 | 17 | visited = set() # set of visited nodes 18 | toVisit = [edgeMap[source]] # set of nodes to visit 19 | 20 | # while there are nodes to visit 21 | while toVisit: 22 | 23 | # this node is now visited 24 | nodes = toVisit.pop() 25 | 26 | # for each node in the adjacent nodes 27 | for node in nodes: 28 | if node == destination: 29 | return True 30 | 31 | # if node wasn't visited 32 | # visit its adjacent nodes 33 | elif node not in visited: 34 | visited.add(node) 35 | toVisit.append(edgeMap[node]) 36 | 37 | # if node was visited 38 | # do nothing 39 | 40 | # if no more nodes to visit 41 | # and still no path 42 | return False 43 | -------------------------------------------------------------------------------- /14. Questions/leetcode 1991 - find the middle index in array.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Given a 0-indexed integer array nums, find the leftmost middleIndex (i.e., the smallest amongst all the possible ones). 4 | 5 | A middleIndex is an index where nums[0] + nums[1] + ... + nums[middleIndex-1] == nums[middleIndex+1] + nums[middleIndex+2] + ... + nums[nums.length-1]. 6 | 7 | If middleIndex == 0, the left side sum is considered to be 0. Similarly, if middleIndex == nums.length - 1, the right side sum is considered to be 0. 8 | 9 | Return the leftmost middleIndex that satisfies the condition, or -1 if there is no such index. 10 | 11 | Question: https://leetcode.com/problems/find-the-middle-index-in-array/ 12 | 13 | """ 14 | 15 | 16 | class Solution: 17 | def findMiddleIndex(self, nums: List[int]) -> int: 18 | total = sum(nums) 19 | 20 | leftSum = 0 21 | for i in range(len(nums)): 22 | rightSum = total - nums[i] - leftSum 23 | if leftSum == rightSum: 24 | return i 25 | leftSum += nums[i] 26 | return -1 -------------------------------------------------------------------------------- /14. Questions/leetcode 20 - valid parentheses.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. 3 | 4 | An input string is valid if: 5 | 6 | Open brackets must be closed by the same type of brackets. 7 | Open brackets must be closed in the correct order. 8 | 9 | Question: https://leetcode.com/problems/valid-parentheses/ 10 | 11 | 12 | """ 13 | 14 | 15 | class Solution: 16 | def isValid(self, s: str) -> bool: 17 | para = {')':'(', ']':'[', '}':'{'} 18 | op = ['(','[', '{'] 19 | stack = [] 20 | 21 | for c in s: 22 | if c in op: 23 | stack.append(c) 24 | 25 | elif c in para: 26 | if len(stack) != 0 and stack[-1] == para[c]: 27 | stack.pop() 28 | else: 29 | return False 30 | 31 | if len(stack) == 0: 32 | return True 33 | else: 34 | return False 35 | 36 | 37 | 38 | 39 | 40 | 41 | # if c in para: 42 | # if len(stack) != 0 and stack[-1] == para[c]: 43 | # stack.pop() 44 | # else: 45 | # return False 46 | 47 | # else: 48 | # stack.append(c) 49 | 50 | # if len(stack) == 0: 51 | # return True 52 | # else: 53 | # False -------------------------------------------------------------------------------- /14. Questions/leetcode 207 - course schedule.py: -------------------------------------------------------------------------------- 1 | # course schedule | leetcode 207 | https://leetcode.com/problems/course-schedule/ 2 | # method: depth first search 3 | 4 | class Solution: 5 | def canFinish(self, numCourses: int, prerequisites) -> bool: 6 | 7 | # init prequisite map 8 | preqMap = {} 9 | for i in range(numCourses): 10 | preqMap[i] = [] 11 | 12 | # add mentioned prerequisites 13 | for crs, pre in prerequisites: 14 | preqMap[crs].append(pre) 15 | 16 | # init visit set 17 | visitSet = set() 18 | 19 | # dfs 20 | def checkPreq(crs): 21 | 22 | # if course is already visited 23 | if crs in visitSet: 24 | return False 25 | 26 | # if no prequisites left 27 | if preqMap[crs] == []: 28 | return True 29 | 30 | # visiting this course 31 | visitSet.add(crs) 32 | 33 | # checking each prerequisite 34 | for pre in preqMap[crs]: 35 | if not checkPreq(pre): return False 36 | 37 | # all prerequisites are doable 38 | visitSet.remove(crs) 39 | preqMap[crs] = [] 40 | return True 41 | 42 | # check prerequisites for each course 43 | for crs in range(numCourses): 44 | if not checkPreq(crs): return False 45 | 46 | return True 47 | -------------------------------------------------------------------------------- /14. Questions/leetcode 21 - merge two sorted lists.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are given the heads of two sorted linked lists list1 and list2. 3 | 4 | Merge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists. 5 | 6 | Return the head of the merged linked list. 7 | 8 | Question: https://leetcode.com/problems/merge-two-sorted-lists/ 9 | 10 | """ 11 | 12 | 13 | class ListNode: 14 | def __init__(self, val=0, next=None): 15 | self.val = val 16 | self.next = next 17 | class Solution: 18 | def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: 19 | answer = ListNode() 20 | tail = answer 21 | while list1 and list2: 22 | if list1.val < list2.val: 23 | tail.next = list1 24 | list1 = list1.next 25 | else: 26 | tail.next = list2 27 | list2=list2.next 28 | tail = tail.next 29 | 30 | if list1: 31 | tail.next = list1 32 | elif list2: 33 | tail.next = list2 34 | 35 | return answer.next -------------------------------------------------------------------------------- /14. Questions/leetcode 217 - contains duplicates.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct. 3 | 4 | Question: https://leetcode.com/problems/contains-duplicate/ 5 | 6 | """ 7 | class Solution: 8 | def containsDuplicate(self, nums: List[int]) -> bool: 9 | duplicates = {} 10 | for i in nums: 11 | if i in duplicates: 12 | return True 13 | else: 14 | duplicates[i] = 1 15 | return False 16 | -------------------------------------------------------------------------------- /14. Questions/leetcode 226 - invert binary tree.py: -------------------------------------------------------------------------------- 1 | # invert a binary tree | leetcode 226 | https://leetcode.com/problems/invert-binary-tree/ 2 | # method: (dfs) keep recursively swapping left and right subtrees 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def invertTree(self, root): 13 | 14 | def dfs(root): 15 | if root is None: 16 | return 17 | 18 | if root.left is None and root.right is None: 19 | return 20 | 21 | root.left, root.right = root.right, root.left 22 | dfs(root.left) 23 | dfs(root.right) 24 | 25 | dfs(root) 26 | return root -------------------------------------------------------------------------------- /14. Questions/leetcode 2265 - count nodes equal to average of subtree.py: -------------------------------------------------------------------------------- 1 | # count nodes equal to average of subtree | leetcode 2265 | https://leetcode.com/problems/count-nodes-equal-to-average-of-subtree/ 2 | # method: dfs, update size and sum of subtree at each node and check for average 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def averageOfSubtree(self, root: list[TreeNode]) -> int: 13 | self.counter = 0 14 | def dfs(node): 15 | if node is None: 16 | return 0, 0 17 | 18 | lSize, lSum = dfs(node.left) 19 | rSize, rSum = dfs(node.right) 20 | 21 | nSize, nSum = lSize + rSize + 1, lSum + rSum + node.val 22 | if (nSum // nSize) == node.val: 23 | self.counter += 1 24 | 25 | return nSize, nSum 26 | 27 | dfs(root) 28 | return self.counter 29 | 30 | -------------------------------------------------------------------------------- /14. Questions/leetcode 2306 - naming a company.py: -------------------------------------------------------------------------------- 1 | # naming a company | leetcode 2306 | https://leetcode.com/problems/naming-a-company 2 | # bucket by starting character to make it n(26^2.n) and compare each set with each other 3 | 4 | class Solution: 5 | def distinctNames(self, ideas: list[str]) -> int: 6 | buckets = dict() 7 | num_distinct = 0 8 | 9 | for idea in ideas: 10 | if buckets.get(idea[0]) is None: 11 | buckets[idea[0]] = {idea[1:]} 12 | else: 13 | buckets[idea[0]].add(idea[1:]) 14 | 15 | for prefix_i, suffix_i in buckets.items(): 16 | for prefix_j, suffix_j in buckets.items(): 17 | if prefix_i == prefix_j: 18 | continue 19 | common = len(suffix_i & suffix_j) 20 | common_i = len(suffix_i) - common 21 | common_j = len(suffix_j) - common 22 | num_distinct += common_i * common_j 23 | 24 | return num_distinct -------------------------------------------------------------------------------- /14. Questions/leetcode 2331 - evaluate boolean binary tree.py: -------------------------------------------------------------------------------- 1 | # evaluate boolean binary tree | leetcode 2331 | https://leetcode.com/problems/evaluate-boolean-binary-tree/ 2 | # method: dfs, evaluate left and/or right, return node's value 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def evaluateTree(self, node): 13 | if node.left is None and node.right is None: 14 | return node.val 15 | 16 | if node.val == 2: 17 | node.val = bool(self.evaluateTree(node.left)) or bool(self.evaluateTree(node.right)) 18 | 19 | if node.val == 3: 20 | node.val = bool(self.evaluateTree(node.left)) and bool(self.evaluateTree(node.right)) 21 | 22 | return node.val 23 | -------------------------------------------------------------------------------- /14. Questions/leetcode 234 - palindrome linked list.py: -------------------------------------------------------------------------------- 1 | # palindrome linked list | leetcode 234 | https://leetcode.com/problems/palindrome-linked-list/ 2 | 3 | # Definition for singly-linked list. 4 | # class ListNode: 5 | # def __init__(self, val=0, next=None): 6 | # self.val = val 7 | # self.next = next 8 | class Solution: 9 | 10 | # to check if its palindrome 11 | def isPalindrome(self, head) -> bool: 12 | 13 | # if underflow, is palindrome 14 | if not head or not head.next: return True 15 | 16 | # get one before mid element 17 | # and check if number of elements are even 18 | mid, even = self.get_mid(head) 19 | second = mid.next 20 | if not even: second = second.next 21 | 22 | # reverse the first half of the linked list 23 | first = self.rev_ll(head, mid) 24 | 25 | # match the reversed 1st and normal 2nd halves 26 | while first and second: 27 | if first.val != second.val: return False 28 | first = first.next 29 | second = second.next 30 | return True 31 | 32 | 33 | # to reverse the linked list half 34 | def rev_ll(self, head, upto): 35 | prev, curr = None, head 36 | 37 | while curr and prev != upto: 38 | temp = curr.next 39 | curr.next = prev 40 | prev = curr 41 | curr = temp 42 | 43 | return prev 44 | 45 | # to get the mid element 46 | # and check for even 47 | def get_mid(self, head): 48 | prev = head 49 | slow = head 50 | fast = head.next 51 | 52 | while fast and fast.next: 53 | prev = slow 54 | slow = slow.next 55 | fast = fast.next.next 56 | 57 | if not fast: return prev, False 58 | return slow, True 59 | -------------------------------------------------------------------------------- /14. Questions/leetcode 235 - lowest common ancestor in bst.py: -------------------------------------------------------------------------------- 1 | # lowest common ancestor in binary search tree | leetcode 235 | https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ 2 | # method: iteration through each node, when p and q are in different subtrees, current node is LCA 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, x): 7 | self.val = x 8 | self.left = None 9 | self.right = None 10 | 11 | class Solution: 12 | def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': 13 | cur = root 14 | 15 | while cur: 16 | if p.val > cur.val and q.val > cur.val: 17 | cur = cur.right 18 | elif p.val < cur.val and q.val < cur.val: 19 | cur = cur.left 20 | else: 21 | return cur 22 | 23 | return root 24 | -------------------------------------------------------------------------------- /14. Questions/leetcode 238 - product of array except self.py: -------------------------------------------------------------------------------- 1 | # product of array except self | leetcode 238 | https://leetcode.com/problems/product-of-array-except-self/ 2 | # save prefixes to result array and apply postfix in reverse 3 | # (since output array doesnt increase space complexity) 4 | 5 | class Solution: 6 | def productExceptSelf(self, nums: list[int]) -> list[int]: 7 | result = [] 8 | N = len(nums) 9 | 10 | # save prefix to result array 11 | product = 1 12 | for i in range(N): 13 | product = nums[i] * product 14 | result.append(product) 15 | 16 | # update result array as per postfix 17 | postfix = 1 18 | for i in range(N - 1, 0, -1): 19 | result[i] = result[i - 1] * postfix 20 | postfix = postfix * nums[i] 21 | result[0] = postfix 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /14. Questions/leetcode 257 - binary tree paths.py: -------------------------------------------------------------------------------- 1 | # binary tree paths | leetcode 257 | https://leetcode.com/problems/binary-tree-paths/ 2 | # method: (dfs) in-order traversal and at each node, update path. if leaf, append to list of paths. 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]: 13 | def dfs(root, path): 14 | if root is None: 15 | return 16 | 17 | if root.left is None and root.right is None: 18 | path += str(root.val) 19 | self.paths.append(path) 20 | return 21 | 22 | path += str(root.val) + '->' 23 | dfs(root.left, path) 24 | dfs(root.right, path) 25 | 26 | self.paths = [] 27 | dfs(root, "") 28 | 29 | return self.paths 30 | 31 | -------------------------------------------------------------------------------- /14. Questions/leetcode 26 - remove duplicates from sorted array.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an integer array nums sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same. 3 | 4 | Since it is impossible to change the length of the array in some languages, you must instead have the result be placed in the first part of the array nums. More formally, if there are k elements after removing the duplicates, then the first k elements of nums should hold the final result. It does not matter what you leave beyond the first k elements. 5 | 6 | Return k after placing the final result in the first k slots of nums. 7 | 8 | Do not allocate extra space for another array. You must do this by modifying the input array in-place with O(1) extra memory. 9 | 10 | Question: https://leetcode.com/problems/remove-duplicates-from-sorted-array/ 11 | 12 | """ 13 | 14 | class Solution: 15 | def removeDuplicates(self, nums: List[int]) -> int: 16 | list = 1 17 | 18 | for r in range(1, len(nums)): 19 | if nums[r] != nums[r-1]: 20 | nums[list] = nums[r] 21 | list += 1 22 | 23 | return list -------------------------------------------------------------------------------- /14. Questions/leetcode 27 - remove element.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an integer array nums and an integer val, remove all occurrences of val in nums in-place. The relative order of the elements may be changed. 3 | 4 | Since it is impossible to change the length of the array in some languages, you must instead have the result be placed in the first part of the array nums. More formally, if there are k elements after removing the duplicates, then the first k elements of nums should hold the final result. It does not matter what you leave beyond the first k elements. 5 | 6 | Return k after placing the final result in the first k slots of nums. 7 | 8 | Do not allocate extra space for another array. You must do this by modifying the input array in-place with O(1) extra memory. 9 | 10 | Question: https://leetcode.com/problems/remove-element/ 11 | 12 | """ 13 | 14 | 15 | class Solution: 16 | def removeDuplicates(self, nums: List[int]) -> int: 17 | list = 1 18 | 19 | for r in range(1, len(nums)): 20 | if nums[r] != nums[r-1]: 21 | nums[list] = nums[r] 22 | list += 1 23 | 24 | return list -------------------------------------------------------------------------------- /14. Questions/leetcode 28 - implement strStr().py: -------------------------------------------------------------------------------- 1 | """ 2 | Implement strStr(). 3 | 4 | Given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. 5 | 6 | Clarification: 7 | 8 | What should we return when needle is an empty string? This is a great question to ask during an interview. 9 | 10 | For the purpose of this problem, we will return 0 when needle is an empty string. This is consistent to C's strstr() and Java's indexOf(). 11 | 12 | Question: https://leetcode.com/problems/implement-strstr/ 13 | 14 | """ 15 | 16 | class Solution: 17 | def strStr(self, haystack: str, needle: str) -> int: 18 | if not needle: 19 | return 0 20 | 21 | for i in range(len(haystack)): 22 | if haystack[i:i+len(needle)] == needle: 23 | return i 24 | return -1 -------------------------------------------------------------------------------- /14. Questions/leetcode 28 - index of first occurrence.py: -------------------------------------------------------------------------------- 1 | # find the index of the first occurrence of a string | leetcode 28 | https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/ 2 | # sliding window to match each character of the haystack with the needle; no slices. 3 | 4 | class Solution: 5 | def strStr(self, haystack: str, needle: str) -> int: 6 | # ----- using regex ----- 7 | # if needle == '': 8 | # return 0 9 | 10 | # import re 11 | # match = re.search(needle, haystack) 12 | # return match.start() if match else -1 13 | 14 | # ----- using sliding windows ----- 15 | ptrL, ptrR = 0, 0 16 | N_needle, N_haystack = len(needle), len(haystack) 17 | while ptrR < N_haystack: 18 | if haystack[ptrR] == needle[ptrR - ptrL]: 19 | ptrR += 1 20 | if ptrR - ptrL > N_needle - 1: 21 | return ptrL 22 | else: 23 | ptrR = ptrL + 1 24 | ptrL += 1 25 | 26 | return -1 27 | -------------------------------------------------------------------------------- /14. Questions/leetcode 290 - word pattern.py: -------------------------------------------------------------------------------- 1 | # word pattern | leetcode 290 | https://leetcode.com/problems/word-pattern/ 2 | # create a vocabulary to match pattern and a seen hashset to record seen words 3 | 4 | class Solution: 5 | def wordPattern(self, pattern: str, s: str) -> bool: 6 | vocab = dict() 7 | seens = dict() 8 | sent = s.split(" ") 9 | 10 | if len(sent) != len(pattern): 11 | return False 12 | 13 | for i in range(len(pattern)): 14 | i_patt = pattern[i] 15 | i_sent = sent[i] 16 | 17 | if vocab.get(i_patt): 18 | if vocab[i_patt] != i_sent: 19 | return False 20 | else: 21 | if seens.get(i_sent): 22 | return False 23 | vocab[i_patt] = i_sent 24 | seens[i_sent] = True 25 | 26 | return True -------------------------------------------------------------------------------- /14. Questions/leetcode 347 - top k frequent elements.py: -------------------------------------------------------------------------------- 1 | # top k frequency elements | leetcode 347 | https://leetcode.com/problems/top-k-frequent-elements/ 2 | # use buckets with each bucket being the frequency of an element 3 | 4 | from collections import Counter 5 | 6 | class Solution: 7 | def topKFrequent(self, nums: list[int], k: int) -> list[int]: 8 | freq = Counter(nums) 9 | N = len(nums) 10 | 11 | # create buckets where index = frequency of element 12 | buckets = [[] for x in range(N + 1)] 13 | for f in freq: 14 | buckets[freq[f]].append(f) 15 | 16 | # get k elements starting from the end of the bucket 17 | k_mf = [] 18 | for x in buckets[::-1]: 19 | if k > 0: 20 | if x != []: 21 | k_mf += x 22 | k -= len(x) 23 | else: 24 | return k_mf 25 | -------------------------------------------------------------------------------- /14. Questions/leetcode 35 - search insert position.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. 4 | 5 | You must write an algorithm with O(log n) runtime complexity. 6 | 7 | Question: https://leetcode.com/problems/search-insert-position/ 8 | 9 | """ 10 | 11 | class Solution: 12 | def searchInsert(self, nums: List[int], target: int) -> int: 13 | l = 0 14 | r = len(nums)-1 15 | 16 | while l <= r: 17 | mid = (l+r)//2 18 | 19 | if target == nums[mid]: 20 | return mid 21 | 22 | if target > nums[mid]: 23 | l = mid +1 24 | 25 | else: 26 | r = mid - 1 27 | 28 | return l -------------------------------------------------------------------------------- /14. Questions/leetcode 350 - intersection of two arrays II.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given two integer arrays nums1 and nums2, return an array of their intersection. Each element in the result must appear as many times as it shows in both arrays and you may return the result in any order. 3 | 4 | 5 | Question: https://leetcode.com/problems/intersection-of-two-arrays-ii/ 6 | 7 | 8 | """ 9 | 10 | #solution 1 11 | class Solution: 12 | def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: 13 | c = Counter(nums1) 14 | output=[] 15 | for n in (nums2): 16 | if c[n] > 0: 17 | output.append(n) 18 | c[n]-=1 19 | 20 | return (output) 21 | 22 | #solution 2 23 | 24 | class Solution: 25 | def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: 26 | i=0 27 | j=0 28 | output = [] 29 | nums1.sort() 30 | nums2.sort() 31 | 32 | while i nums2[j]: 36 | j = j+1 37 | else: 38 | output.append(nums1[i]) 39 | i=i+1 40 | j=j+1 41 | 42 | return output 43 | -------------------------------------------------------------------------------- /14. Questions/leetcode 36 - valid sudoku.py: -------------------------------------------------------------------------------- 1 | # valid sudoku | leetcode 36 | https://leetcode.com/problems/valid-sudoku/ 2 | # Determine if a n^2 x n^2 Psuedo-Sudoku board is valid. 3 | # Only the filled cells need to be validated. 4 | 5 | import collections 6 | 7 | class Solution: 8 | def isValidSudoku(self, board: list[list[str]]) -> bool: 9 | rows = collections.defaultdict(set) 10 | columns = collections.defaultdict(set) 11 | squares = collections.defaultdict(set) 12 | 13 | for i in range(len(board)): 14 | for j in range(len(board[i])): 15 | 16 | if board[i][j] == '.': 17 | continue 18 | 19 | isInRow = board[i][j] in rows[i] 20 | isInColumn = board[i][j] in columns[j] 21 | isInSquare = board[i][j] in squares[(i//3, j//3)] 22 | if (isInRow or isInColumn or isInSquare): 23 | return False 24 | 25 | rows[i].add(board[i][j]) 26 | columns[j].add(board[i][j]) 27 | squares[(i//3, j//3)].add(board[i][j]) 28 | 29 | return True 30 | -------------------------------------------------------------------------------- /14. Questions/leetcode 424 - longest repeating character replacement.py: -------------------------------------------------------------------------------- 1 | # longest repeating character replacement | leetcode 424 | https://leetcode.com/problems/longest-repeating-character-replacement/ 2 | # keep track of max freq in sliding window and check if size of window - max freq > k 3 | 4 | class Solution: 5 | def characterReplacement(self, s: str, k: int) -> int: 6 | ptrL = 0 7 | ptrR = 0 8 | longest = 0 9 | freq = dict() 10 | max_freq = 0 11 | w_size = 0 12 | 13 | for ptrR in range(len(s)): 14 | freq[s[ptrR]] = 1 + freq.get(s[ptrR], 0) 15 | max_freq = max(max_freq, freq[s[ptrR]]) 16 | 17 | if (ptrR - ptrL + 1) - max_freq > k: 18 | freq[s[ptrL]] -= 1 19 | ptrL += 1 20 | 21 | longest = max(longest, (ptrR - ptrL + 1)) 22 | 23 | return longest -------------------------------------------------------------------------------- /14. Questions/leetcode 427 - construct quad tree.py: -------------------------------------------------------------------------------- 1 | # construct quad tree | leetcode 427 | https://leetcode.com/problems/construct-quad-tree/ 2 | # recursively call each quad of the grid and check if each quad is uniform or not 3 | 4 | # Definition for a QuadTree node. 5 | class Node: 6 | def __init__(self, val, isLeaf, topLeft, topRight, bottomLeft, bottomRight): 7 | self.val = val 8 | self.isLeaf = isLeaf 9 | self.topLeft = topLeft 10 | self.topRight = topRight 11 | self.bottomLeft = bottomLeft 12 | self.bottomRight = bottomRight 13 | 14 | 15 | class Solution: 16 | def construct(self, grid: list[list[int]]) -> Node: 17 | def checkThisQuad(row, col, n) -> bool: 18 | for i in range(row, row + n): 19 | for j in range(col, col + n): 20 | if grid[i][j] != grid[row][col]: 21 | return False 22 | return True 23 | 24 | def quadTree(row, col, n): 25 | if checkThisQuad(row, col, n): 26 | return Node(grid[row][col], 1, None, None, None, None) 27 | 28 | 29 | return Node(grid[row][col], 0, 30 | quadTree(row, col, n//2), 31 | quadTree(row, col + n//2, n//2), 32 | quadTree(row + n//2, col, n//2), 33 | quadTree(row + n//2, col + n//2, n//2) 34 | ) 35 | 36 | return quadTree(0, 0, len(grid)) 37 | 38 | -------------------------------------------------------------------------------- /14. Questions/leetcode 435 - non-overlapping intervals.py: -------------------------------------------------------------------------------- 1 | # non-overlapping intervals | leetcode 435 | https://leetcode.com/problems/non-overlapping-intervals 2 | # sort by starting times; keep track of latest ending time; always keep interval with min end time 3 | 4 | class Solution: 5 | def eraseOverlapIntervals(self, intervals: list[list[int]]) -> int: 6 | min_intervals_to_remove = 0 7 | intervals.sort(key = lambda x: x[0]) 8 | latest_end = intervals[0][1] 9 | 10 | for i in range(1, len(intervals)): 11 | if intervals[i][0] < latest_end: 12 | min_intervals_to_remove += 1 13 | latest_end = min(intervals[i][1], latest_end) 14 | else: 15 | latest_end = intervals[i][1] 16 | 17 | return min_intervals_to_remove 18 | -------------------------------------------------------------------------------- /14. Questions/leetcode 438 - find all anagrams in string.py: -------------------------------------------------------------------------------- 1 | # find all anagrams in string | leetcode 438 | https://leetcode.com/problems/find-all-anagrams-in-a-string/ 2 | # sliding window to track "which" substring; add ptr2 to counter, remove ptr1 from counter 3 | 4 | 5 | from collections import Counter 6 | 7 | class Solution: 8 | def findAnagrams(self, s: str, p: str) -> list[int]: 9 | Ns, Np = len(s), len(p) 10 | ptr1 = 0 11 | ptr2 = Np - 1 12 | anagrams = [] 13 | freq_s, freq_p = Counter(s[ptr1:(ptr2 + 1)]), Counter(p) 14 | 15 | while ptr2 < Ns: 16 | if freq_s == freq_p: 17 | anagrams.append(ptr1) 18 | freq_s[s[ptr1]] -= 1 19 | ptr1 += 1 20 | ptr2 += 1 21 | if ptr2 != Ns: 22 | freq_s[s[ptr2]] = 1 + freq_s.get(s[ptr2], 0) 23 | 24 | return anagrams -------------------------------------------------------------------------------- /14. Questions/leetcode 443 - string compression.py: -------------------------------------------------------------------------------- 1 | # string compression | leetcode 443 | https://leetcode.com/problems/string-compression/ 2 | # sliding window to keep track of a char's occurence 3 | 4 | class Solution: 5 | def compress(self, chars: list[str]) -> int: 6 | ptrL, ptrR = 0, 0 7 | total = 0 8 | chars += " " 9 | 10 | while ptrR < len(chars): 11 | if chars[ptrL] != chars[ptrR]: 12 | chars[total] = chars[ptrL] 13 | total += 1 14 | group = ptrR - ptrL 15 | if group > 1: 16 | for x in str(group): 17 | chars[total] = x 18 | total += 1 19 | ptrL = ptrR 20 | ptrR += 1 21 | 22 | return total 23 | -------------------------------------------------------------------------------- /14. Questions/leetcode 48 - rotate image (by rotation).py: -------------------------------------------------------------------------------- 1 | # rotate image | leetcode 48 | https://leetcode.com/problems/rotate-image/ 2 | # You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise). 3 | # method: actual rotation 4 | 5 | # testcase 1 6 | # matrix: list[list[int]] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 7 | 8 | # testcase 2 9 | matrix: list[list[int]] = [[5, 1, 9, 11], [2, 4, 8, 10],[13, 3, 6, 7],[15, 14, 12, 16]] 10 | 11 | def rotate(matrix: list[list[int]]) -> None: 12 | n: int = len(matrix) # size (n x n) 13 | left: int = 0 # left pointer 14 | right: int = n - 1 # right pointer 15 | top: int = 0 # top pointer 16 | bottom: int = 0 # bottom pointer 17 | temp: int = 0 # temp variable 18 | 19 | # if left pointer is on 20 | # the right of the right pointer 21 | # stop iterating 22 | while left < right: 23 | 24 | # for each square in layer 25 | for i in range(right - left): 26 | top: int = left 27 | bottom: int = right 28 | 29 | temp = matrix[top][left + i] # save top-left 30 | matrix[top][left + i] = matrix[bottom - i][left] # bottom-left to top-left 31 | matrix[bottom - i][left] = matrix[bottom][right - i] # bottom-right to bottom-left 32 | matrix[bottom][right - i] = matrix[top + i][right] # top-right to bottom-right 33 | matrix[top + i][right] = temp # (saved) top-left to top-right 34 | 35 | # next layer 36 | left = left + 1 37 | right = right - 1 38 | 39 | # output 40 | rotate(matrix) 41 | print(matrix) -------------------------------------------------------------------------------- /14. Questions/leetcode 48 - rotate image (by transpose).py: -------------------------------------------------------------------------------- 1 | # rotate image | leetcode 48 | https://leetcode.com/problems/rotate-image/ 2 | # You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise). 3 | # method: transpose + reflection 4 | 5 | # testcase 1 6 | # matrix: list[list[int]] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 7 | 8 | # testcase 2 9 | matrix: list[list[int]] = [[5, 1, 9, 11], [2, 4, 8, 10],[13, 3, 6, 7],[15, 14, 12, 16]] 10 | 11 | def rotate(matrix: list[list[int]]) -> None: 12 | n: int = len(matrix) 13 | 14 | # transpose of the matrix 15 | def transpose(matrix: list[list[int]]) -> None: 16 | for i in range(n): 17 | for j in range(i + 1, n): 18 | matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] 19 | 20 | # reflection of the matrix 21 | def reflect(matrix: list[list[int]]) -> None: 22 | for i in range(n): 23 | for j in range(n // 2): 24 | matrix[i][j], matrix[i][n - j - 1] = matrix[i][n - j - 1], matrix[i][j] 25 | 26 | transpose(matrix) 27 | reflect(matrix) 28 | 29 | # output 30 | rotate(matrix) 31 | print(matrix) -------------------------------------------------------------------------------- /14. Questions/leetcode 49 - group anagrams.py: -------------------------------------------------------------------------------- 1 | # group anagrams | leetcode 49 | https://leetcode.com/problems/group-anagrams/ 2 | # method: dictionary with char counter as key 3 | 4 | from collections import defaultdict 5 | 6 | class Solution: 7 | def groupAnagrams(self, strs): 8 | grouped = defaultdict(list) 9 | 10 | for each_word in strs: 11 | count_of_ch = [0] * 26 12 | for each_ch in each_word: 13 | count_of_ch[ord(each_ch) - ord("a")] += 1 14 | grouped[tuple(count_of_ch)].append(each_word) 15 | 16 | return grouped.values() 17 | -------------------------------------------------------------------------------- /14. Questions/leetcode 494 - target sum.py: -------------------------------------------------------------------------------- 1 | # target sum | leetcode 494 | https://leetcode.com/problems/target-sum/ 2 | # 0/1 knapsack to decide +/- and cache (index, total) 3 | 4 | class Solution: 5 | def findTargetSumWays(self, nums: list[int], target: int) -> int: 6 | N = len(nums) 7 | mem = dict() 8 | 9 | if N == 0: 10 | return 0 11 | 12 | def knapsack(n, s): 13 | if n == N: 14 | return 1 if s == target else 0 15 | 16 | if (n, s) in mem: 17 | return mem[(n, s)] 18 | 19 | mem[(n, s)] = knapsack(n+1, s + nums[n]) + knapsack(n+1, s - nums[n]) 20 | return mem[(n, s)] 21 | 22 | return knapsack(0, 0) 23 | -------------------------------------------------------------------------------- /14. Questions/leetcode 502 - ipo.py: -------------------------------------------------------------------------------- 1 | # IPO | leetcode 502 | https://leetcode.com/problems/ipo/ 2 | # min-heap to track capital and max-heap to track profits 3 | 4 | import heapq 5 | 6 | class Solution: 7 | def findMaximizedCapital(self, k: int, w: int, profits: list[int], capital: list[int]) -> int: 8 | maxHeap = [] 9 | minHeap = [(c, p) for c, p in zip(capital, profits)] 10 | heapq.heapify(minHeap) 11 | 12 | for _ in range(k): 13 | while minHeap and minHeap[0][0] <= w: 14 | _, p = heapq.heappop(minHeap) 15 | heapq.heappush(maxHeap, -1 * p) 16 | if not maxHeap: 17 | break 18 | w += -1 * heapq.heappop(maxHeap) 19 | 20 | return w 21 | 22 | -------------------------------------------------------------------------------- /14. Questions/leetcode 53 - maximum subarray.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. 3 | 4 | A subarray is a contiguous part of an array. 5 | 6 | Question: https://leetcode.com/problems/maximum-subarray/ 7 | 8 | 9 | """ 10 | 11 | class Solution: 12 | def maxSubArray(self, nums: List[int]) -> int: 13 | maxSub = nums[0] 14 | curSum = 0 15 | 16 | for n in nums: 17 | if curSum <0: 18 | curSum = 0 19 | curSum += n 20 | maxSub = max(maxSub, curSum) 21 | return maxSub -------------------------------------------------------------------------------- /14. Questions/leetcode 530 - minimum absolute difference in BST.py: -------------------------------------------------------------------------------- 1 | # minimum absolute difference in BST | leetcode 530 | https://leetcode.com/problems/minimum-absolute-difference-in-bst/ 2 | # method: dfs, inorder traversal 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def getMinimumDifference(self, root: TreeNode): 13 | minDiff = float('inf') 14 | prevNod = None 15 | 16 | def dfs(node): 17 | nonlocal minDiff, prevNod 18 | if node is None: 19 | return 20 | 21 | dfs(node.left) 22 | 23 | if prevNod != None: 24 | minDiff = min(minDiff, abs(node.val - prevNod)) 25 | prevNod = node.val 26 | 27 | dfs(node.right) 28 | 29 | dfs(root) 30 | return minDiff 31 | 32 | 33 | -------------------------------------------------------------------------------- /14. Questions/leetcode 540 - single element in a sorted array.py: -------------------------------------------------------------------------------- 1 | # single element in a sorted array | leetcode 540 | https://leetcode.com/problems/single-element-in-a-sorted-array/ 2 | # binary search over sorted array; check if mid is even and mid is the first of the duplicates 3 | 4 | class Solution: 5 | def singleNonDuplicate(self, nums: list[int]) -> int: 6 | N = len(nums) 7 | if N < 2: 8 | return nums[0] 9 | low, high, mid = 0, N, 0 10 | while low <= high: 11 | mid = low + ((high - low) // 2) 12 | 13 | if mid == N - 1: 14 | return nums[mid] 15 | 16 | if nums[mid] == nums[mid - 1] or nums[mid] == nums[mid + 1]: 17 | if (mid % 2 == 0) == (nums[mid] == nums[mid + 1]): 18 | low = mid 19 | else: 20 | high = mid 21 | else: 22 | return nums[mid] 23 | 24 | return nums[mid] -------------------------------------------------------------------------------- /14. Questions/leetcode 559 - max depth of n-ary tree.py: -------------------------------------------------------------------------------- 1 | # max depth of n-ary tree | leetcode 559 | https://leetcode.com/problems/maximum-depth-of-n-ary-tree/ 2 | # method: (dfs) return 1 + max(depths) at each node, return 1 if leaf 3 | 4 | # Definition for a Node. 5 | class Node: 6 | def __init__(self, val=None, children=None): 7 | self.val = val 8 | self.children = children 9 | 10 | class Solution: 11 | def maxDepth(self, root): 12 | if root is None: 13 | return 0 14 | 15 | depths = [self.maxDepth(child) for child in root.children] 16 | 17 | if depths: 18 | return 1 + max(depths) 19 | 20 | return 1 -------------------------------------------------------------------------------- /14. Questions/leetcode 58 - length of last word.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given a string s consisting of some words separated by some number of spaces, return the length of the last word in the string. 3 | 4 | A word is a maximal substring consisting of non-space characters only. 5 | 6 | Question: https://leetcode.com/problems/length-of-last-word/ 7 | 8 | """ 9 | 10 | #Solution usinf inbuilt fuctions : 11 | class Solution: 12 | def lengthOfLastWord(self, s: str) -> int: 13 | s = s.strip() 14 | s = s.split(" ") 15 | last = s[len(s)-1] 16 | return (len(last)) 17 | 18 | 19 | #Solution without inbuilt fuctions : 20 | class Solution: 21 | def lengthOfLastWord(self, s: str) -> int: 22 | i, length = len(s)-1, 0 23 | while s[i] == " ": 24 | i -= 1 25 | while i >= 0 and s[i] != " ": 26 | length += 1 27 | i -= 1 28 | return length 29 | 30 | -------------------------------------------------------------------------------- /14. Questions/leetcode 590 - n-ary tree postorder.py: -------------------------------------------------------------------------------- 1 | # n-ary tree postorder traversal | leetcode 590 | https://leetcode.com/problems/n-ary-tree-postorder-traversal/submissions/ 2 | # method: (dfs) postorder traversal is L R N, so iterate through all children and then save node 3 | 4 | # Definition for a Node. 5 | class Node: 6 | def __init__(self, val=None, children=None): 7 | self.val = val 8 | self.children = children 9 | 10 | class Solution: 11 | def postorder(self, root: Node): 12 | self.postTrv = [] 13 | def dfs(root): 14 | if root is None: 15 | return None 16 | 17 | [dfs(child) for child in root.children] 18 | self.postTrv.append(root.val) 19 | 20 | dfs(root) 21 | return self.postTrv 22 | -------------------------------------------------------------------------------- /14. Questions/leetcode 598 - n-ary tree preorder.py: -------------------------------------------------------------------------------- 1 | # n-ary tree postorder traversal | leetcode 590 | https://leetcode.com/problems/n-ary-tree-postorder-traversal/submissions/ 2 | # method: (dfs) postorder traversal is L R N, so iterate through all children and then save node 3 | 4 | 5 | # Definition for a Node. 6 | class Node: 7 | def __init__(self, val=None, children=None): 8 | self.val = val 9 | self.children = children 10 | 11 | class Solution: 12 | def preorder(self, root): 13 | def dfs(root): 14 | if root is None: 15 | return None 16 | 17 | self.postTrv.append(root.val) 18 | [dfs(child) for child in root.children] 19 | 20 | self.postTrv = [] 21 | dfs(root) 22 | return self.postTrv -------------------------------------------------------------------------------- /14. Questions/leetcode 617 - merge binary trees.py: -------------------------------------------------------------------------------- 1 | # merge two binary trees | leetcode 617 | https://leetcode.com/problems/merge-two-binary-trees/ 2 | # method: merge current, then merge left and right 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def mergeTrees(self, root1, root2): 13 | if root1 is None and root2 is None: 14 | return None 15 | 16 | curr = TreeNode((root1.val if root1 else 0) + (root2.val if root2 else 0)) 17 | curr.left = self.mergeTrees(root1.left if root1 else None, root2.left if root2 else None) 18 | curr.right = self.mergeTrees(root1.right if root1 else None, root2.right if root2 else None) 19 | 20 | return curr -------------------------------------------------------------------------------- /14. Questions/leetcode 637 - avg of levels in binary tree.py: -------------------------------------------------------------------------------- 1 | # average of levels in binary tree | leetcode 637 | https://leetcode.com/problems/average-of-levels-in-binary-tree/ 2 | # method: (bfs) level order traversal, but instead of appending level to a list, append its average 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def averageOfLevels(self, root): 13 | res = [] 14 | Q = [] 15 | 16 | Q.append(root) 17 | lQ = len(Q) 18 | 19 | while lQ is not 0: 20 | level = [] 21 | lQ = len(Q) 22 | for i in range(lQ): 23 | node = Q.pop(0) 24 | if node is not None: 25 | level.append(node.val) 26 | Q.append(node.left) 27 | Q.append(node.right) 28 | if level: 29 | res.append(sum(level) / len(level)) 30 | 31 | return res -------------------------------------------------------------------------------- /14. Questions/leetcode 653 - two sum with binary tree.py: -------------------------------------------------------------------------------- 1 | # two sum iv - input is a bst | leetcode 653 | https://leetcode.com/problems/two-sum-iv-input-is-a-bst/submissions/ 2 | # method: (dfs) bst inorder traversal gives a sorted array, run array two-sum. 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def twoSum(self, nums, target): 13 | prev = {} 14 | 15 | for i, num in enumerate(nums): 16 | diff = target - num 17 | if diff in prev: 18 | return [prev[diff], i] 19 | prev[num] = i 20 | 21 | def dfs(self, root): 22 | if root is None: 23 | return 24 | 25 | self.dfs(root.left) 26 | self.trv.append(root.val) 27 | self.dfs(root.right) 28 | 29 | def findTarget(self, root, k): 30 | self.trv = [] 31 | self.dfs(root) 32 | return self.twoSum(self.trv, k) 33 | 34 | -------------------------------------------------------------------------------- /14. Questions/leetcode 66 - plus one.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are given a large integer represented as an integer array digits, where each digits[i] is the ith digit of the integer. The digits are ordered from most significant to least significant in left-to-right order. The large integer does not contain any leading 0's. 3 | 4 | Increment the large integer by one and return the resulting array of digits. 5 | 6 | Question: https://leetcode.com/problems/plus-one/ 7 | 8 | """ 9 | 10 | class Solution: 11 | def plusOne(self, digits: List[int]) -> List[int]: 12 | n = 0 13 | for i in range(len(digits)): 14 | n = (n*10)+digits[i] 15 | n = n+1 16 | m = map(int, str(n)) 17 | return(m) 18 | -------------------------------------------------------------------------------- /14. Questions/leetcode 67 - add binary.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given two binary strings a and b, return their sum as a binary string. 3 | 4 | Question: https://leetcode.com/problems/add-binary/ 5 | 6 | """ 7 | 8 | class Solution: 9 | def addBinary(self, a: str, b: str) -> str: 10 | result = "" 11 | aCount = len(a) - 1 12 | bCount = len(b) - 1 13 | 14 | carry = 0 15 | 16 | while aCount >= 0 or bCount >= 0: 17 | totalSum = carry 18 | if aCount >= 0: 19 | totalSum += int(a[aCount]) 20 | aCount -= 1 21 | if bCount >= 0: 22 | totalSum += int(b[bCount]) 23 | bCount -= 1 24 | result = str(totalSum % 2) + result 25 | carry = totalSum // 2 26 | if carry > 0: 27 | result = str(1) + result 28 | return result -------------------------------------------------------------------------------- /14. Questions/leetcode 69 - sqrt(x).py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Given a non-negative integer x, compute and return the square root of x. 4 | 5 | Since the return type is an integer, the decimal digits are truncated, and only the integer part of the result is returned. 6 | 7 | Note: You are not allowed to use any built-in exponent function or operator, such as pow(x, 0.5) or x ** 0.5. 8 | 9 | Question : https://leetcode.com/problems/sqrtx/ 10 | 11 | """ 12 | 13 | # import numpy as np 14 | # class Solution: 15 | # def mySqrt(self, x: int) -> int: 16 | # return int(np.sqrt(x)) 17 | 18 | class Solution: 19 | def mySqrt(self, x: int) -> int: 20 | 21 | start = 0 22 | end = x 23 | 24 | while start + 1 < end: 25 | mid = start + (end - start) // 2 26 | 27 | if mid * mid == x: 28 | return mid 29 | elif mid * mid < x: 30 | start = mid 31 | else: 32 | end = mid 33 | 34 | if end * end == x: 35 | return end 36 | 37 | return start -------------------------------------------------------------------------------- /14. Questions/leetcode 70 - climbing stairs.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | You are climbing a staircase. It takes n steps to reach the top. 4 | 5 | Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 6 | 7 | Question: https://leetcode.com/problems/climbing-stairs/ 8 | 9 | """ 10 | 11 | class Solution: 12 | def climbStairs(self, n: int) -> int: 13 | one, two = 1,1 14 | 15 | for i in range (n-1): 16 | temp = one 17 | one = one +two 18 | two = temp 19 | 20 | return one -------------------------------------------------------------------------------- /14. Questions/leetcode 705 - hash set.py: -------------------------------------------------------------------------------- 1 | # hashset | leetcode 705 | https://leetcode.com/problems/design-hashset/ 2 | 3 | class HashSet: 4 | 5 | # constructor 6 | def __init__ (self, hash_set = None): 7 | '''initialize a hash set''' 8 | self.hash_set: dict = {} if hash_set == None else hash_set 9 | self.size: int = len(self.hash_set.keys()) 10 | 11 | # initialize iterator for __iter__ 12 | def __iter__ (self): 13 | self.n = 0 14 | return self 15 | 16 | # return next element for __next__ 17 | def __next__ (self): 18 | if self.n < self.size: 19 | result = list(self.hash_set.keys())[self.n] 20 | self.n = self.n + 1 21 | return result 22 | else: 23 | raise StopIteration 24 | 25 | def add (self, key) -> None: 26 | '''add element to hash set''' 27 | self.hash_set[key] = True 28 | self.size = self.size + 1 29 | 30 | def contains (self, key) -> bool: 31 | '''does hash set contain element''' 32 | return True if self.hash_set.get(key) else False 33 | 34 | def remove (self, key) -> None: 35 | '''remove element from hash set''' 36 | if self.contains(key): 37 | self.hash_set.pop(key) 38 | self.size = self.size - 1 39 | 40 | # initialize a new hashset 41 | hashSet = HashSet() 42 | 43 | # add values to a hash set 44 | hashSet.add('first') 45 | hashSet.add('second') 46 | hashSet.add('third') 47 | hashSet.add('fourth') 48 | 49 | # remove from a hash set 50 | hashSet.remove('fourth') 51 | 52 | # check if value exists in a hash set 53 | print(hashSet.contains('first')) 54 | print(hashSet.contains('fourth')) 55 | 56 | # iterate through a hash set 57 | for element in hashSet: 58 | print(element) -------------------------------------------------------------------------------- /14. Questions/leetcode 724 - find pivot index.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an array of integers nums, calculate the pivot index of this array. 3 | 4 | The pivot index is the index where the sum of all the numbers strictly to the left of the index is equal to the sum of all the numbers strictly to the index's right. 5 | 6 | If the index is on the left edge of the array, then the left sum is 0 because there are no elements to the left. This also applies to the right edge of the array. 7 | 8 | Return the leftmost pivot index. If no such index exists, return -1. 9 | 10 | Question : https://leetcode.com/problems/find-pivot-index/ 11 | 12 | 13 | """ 14 | 15 | class Solution: 16 | def pivotIndex(self, nums: List[int]) -> int: 17 | total = sum(nums) 18 | 19 | leftSum = 0 20 | for i in range(len(nums)): 21 | rightSum = total - nums[i] - leftSum 22 | if leftSum == rightSum: 23 | return i 24 | leftSum += nums[i] 25 | return -1 -------------------------------------------------------------------------------- /14. Questions/leetcode 783 - minimum distance between bst nodes.py: -------------------------------------------------------------------------------- 1 | # minimum distance between bst nodes | leetcode 783 | https://leetcode.com/problems/minimum-distance-between-bst-nodes 2 | # dfs; inorder; keep track of last traversed node and check against minimum difference 3 | 4 | 5 | # Definition for a binary tree node. 6 | class TreeNode: 7 | def __init__(self, val=0, left=None, right=None): 8 | self.val = val 9 | self.left = left 10 | self.right = right 11 | 12 | 13 | class Solution: 14 | def minDiffInBST(self, root: TreeNode) -> int: 15 | stack = [] 16 | curr = root 17 | last = None 18 | minDiff = float("inf") 19 | while True: 20 | if curr is not None: 21 | stack.append(curr) 22 | curr = curr.left 23 | elif stack: 24 | curr = stack.pop() 25 | if last is not None: 26 | minDiff = min(abs(last.val - curr.val), minDiff) 27 | last = curr 28 | curr = curr.right 29 | else: 30 | break 31 | 32 | return int(minDiff) 33 | 34 | -------------------------------------------------------------------------------- /14. Questions/leetcode 797 - all paths from source to target.py: -------------------------------------------------------------------------------- 1 | # all paths from source to target | leetcode 797 | https://leetcode.com/problems/all-paths-from-source-to-target/ 2 | # method: dfs 3 | 4 | class Solution: 5 | def allPathsSourceTarget(self, graph: list[list[int]]) -> list[list[int]]: 6 | possiblePaths = [] 7 | 8 | def dfs(node, visited): 9 | if node == len(graph) - 1: 10 | possiblePaths.append(visited) 11 | 12 | for neighbour in graph[node]: 13 | dfs(neighbour, [*visited, node]) 14 | 15 | dfs(0, [0]) 16 | return possiblePaths 17 | -------------------------------------------------------------------------------- /14. Questions/leetcode 83 - remove duplicates from sorted list.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given the head of a sorted linked list, delete all duplicates such that each element appears only once. Return the linked list sorted as well. 3 | 4 | Question: https://leetcode.com/problems/remove-duplicates-from-sorted-list/ 5 | 6 | """ 7 | 8 | # Definition for singly-linked list. 9 | # class ListNode: 10 | # def __init__(self, val=0, next=None): 11 | # self.val = val 12 | # self.next = next 13 | 14 | class Solution: 15 | def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: 16 | cur = head 17 | 18 | while cur: 19 | while cur.next and cur.next.val == cur .val: 20 | cur.next = cur.next.next 21 | cur = cur.next 22 | 23 | return head 24 | -------------------------------------------------------------------------------- /14. Questions/leetcode 872 - leaf-similar trees.py: -------------------------------------------------------------------------------- 1 | # leaf-similar trees | leetcode 872 | https://leetcode.com/problems/leaf-similar-trees/ 2 | # match the leaves of both trees 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def leafSimilar(self, root1, root2) -> bool: 13 | def getLeaves(root): 14 | if root is None: 15 | return 16 | 17 | getLeaves(root.left) 18 | if root.left is None and root.right is None: 19 | self.leaves.append(root.val) 20 | getLeaves(root.right) 21 | 22 | self.leaves = [] 23 | getLeaves(root1) 24 | leaves1 = self.leaves 25 | 26 | self.leaves = [] 27 | getLeaves(root2) 28 | leaves2 = self.leaves 29 | 30 | return leaves1 == leaves2 -------------------------------------------------------------------------------- /14. Questions/leetcode 88 - merger sort array.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and two integers m and n, representing the number of elements in nums1 and nums2 respectively. 3 | 4 | Merge nums1 and nums2 into a single array sorted in non-decreasing order. 5 | 6 | The final sorted array should not be returned by the function, but instead be stored inside the array nums1. To accommodate this, nums1 has a length of m + n, where the first m elements denote the elements that should be merged, and the last n elements are set to 0 and should be ignored. nums2 has a length of n. 7 | 8 | Question: https://leetcode.com/problems/merge-sorted-array/ 9 | 10 | """ 11 | 12 | class Solution: 13 | def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: 14 | """ 15 | Do not return anything, modify nums1 in-place instead. 16 | """ 17 | last = m + n - 1 18 | 19 | #merge in reverse order 20 | while m>0 and n>0: 21 | if nums1[m-1] > nums2[n-1]: 22 | nums1[last] = nums1[m-1] 23 | m = m-1 24 | else: 25 | nums1[last] = nums2[n-1] 26 | n = n-1 27 | last = last - 1 28 | 29 | #fill nums1 with leftover nums2 elements 30 | while n>0: 31 | nums1[last] = nums2[n-1] 32 | n, last = n-1, last -1 -------------------------------------------------------------------------------- /14. Questions/leetcode 897 - increasing order bst.py: -------------------------------------------------------------------------------- 1 | # increasing order search tree | leetcode 897 | https://leetcode.com/problems/increasing-order-search-tree/ 2 | # rearrange a bst with each node having only a right child, and the originally left-most leaf as the new root 3 | # method: inorder traversal to return sorted array, insert all elements as right child (since sorted array) 4 | 5 | 6 | # Definition for a binary tree node. 7 | class TreeNode: 8 | def __init__(self, val=0, left=None, right=None): 9 | self.val = val 10 | self.left = left 11 | self.right = right 12 | 13 | class Solution: 14 | def increasingBST(self, root): 15 | self.inorderTrv = [] 16 | def inorder(root): 17 | if root is None: 18 | return None 19 | 20 | inorder(root.left) 21 | self.inorderTrv.append(root.val) 22 | inorder(root.right) 23 | 24 | inorder(root) 25 | newRoot = TreeNode(self.inorderTrv[0]) 26 | toReturn = newRoot 27 | for x in self.inorderTrv[1:]: 28 | newRoot.right = TreeNode(x) 29 | newRoot = newRoot.right 30 | 31 | return toReturn -------------------------------------------------------------------------------- /14. Questions/leetcode 94 - binary tree inorder traversal.py: -------------------------------------------------------------------------------- 1 | # binary tree inorder traversal | leetcode 94 | https://leetcode.com/problems/binary-tree-inorder-traversal/ 2 | # method: left subtree, node, right subtree recursively 3 | 4 | # Definition for a binary tree node. 5 | class TreeNode: 6 | def __init__(self, val=0, left=None, right=None): 7 | self.val = val 8 | self.left = left 9 | self.right = right 10 | 11 | class Solution: 12 | def inorderTraversal(self, root): 13 | travList = [] 14 | 15 | def traverse(root, travList): 16 | if root is None: 17 | return None 18 | 19 | traverse(root.left, travList) # traverse left subtree and add nodes 20 | travList.append(root.val) # add this node 21 | traverse(root.right, travList) # traverse right subtree and add nodes 22 | 23 | traverse(root, travList) 24 | return travList 25 | -------------------------------------------------------------------------------- /14. Questions/leetcode 944 - delete columns to make sorted.py: -------------------------------------------------------------------------------- 1 | # delete columns to make sorted | leetcode 944 | https://leetcode.com/problems/delete-columns-to-make-sorted/ 2 | 3 | class Solution: 4 | def minDeletionSize(self, strs: list[str]) -> int: 5 | n_cols = len(strs[0]) 6 | n_rows = len(strs) 7 | cols_d = 0 8 | 9 | for col in range(n_cols): 10 | for row in range(1, n_rows): 11 | if strs[row][col] < strs[row - 1][col]: 12 | cols_d += 1 13 | break 14 | 15 | return cols_d -------------------------------------------------------------------------------- /14. Questions/leetcode 96 - unique binary search trees.py: -------------------------------------------------------------------------------- 1 | # unique binary search trees | leetcode 96 | https://leetcode.com/problems/unique-binary-search-trees/ 2 | # method: dp, use cached results for subtrees of all possible roots 3 | 4 | class Solution: 5 | def numTrees(self, n: int) -> int: 6 | # cache of possible trees 7 | possibleTrees = [1] * (n + 1) 8 | 9 | # for each number of nodes 10 | for numNodes in range(2, n + 1): 11 | 12 | # for each possible root 13 | possibleSubTrees = 0 14 | for possibleRoot in range(1, numNodes + 1): 15 | Left = possibleRoot - 1 16 | Right = numNodes - possibleRoot 17 | possibleSubTrees += possibleTrees[Left] * possibleTrees[Right] 18 | possibleTrees[numNodes] = possibleSubTrees 19 | 20 | return possibleTrees[n] 21 | 22 | -------------------------------------------------------------------------------- /14. Questions/leetcode 98 - validate binary search tree.py: -------------------------------------------------------------------------------- 1 | # validate binary search tree | leetcode 98 | https://leetcode.com/problems/validate-binary-search-tree/ 2 | # Given the root of a binary tree, determine if it is a valid binary search tree (BST). 3 | # method: in-order traversal of a valid bst gives a sorted array 4 | # tip: use `prev` pointer instead of an array to keep space complexity as O(1) 5 | 6 | # Definition for a binary tree node. 7 | class TreeNode: 8 | def __init__(self, val=0, left=None, right=None): 9 | self.val = val 10 | self.left = left 11 | self.right = right 12 | 13 | 14 | class Solution: 15 | 16 | # initialise a prev pointer 17 | def __init__(self): 18 | self.prev = None 19 | 20 | # in-order traversal (L M R) 21 | # should return a sorted array 22 | def isValidBST(self, root) -> bool: 23 | 24 | # if this node is none, its a leaf 25 | if root is None: 26 | return True 27 | 28 | if not self.isValidBST(root.left): 29 | return False 30 | 31 | if self.prev is not None and self.prev.val >= root.val: 32 | return False 33 | 34 | self.prev = root 35 | 36 | return self.isValidBST(root.right) -------------------------------------------------------------------------------- /14. Questions/leetcode 989 - add to array form of integer.py: -------------------------------------------------------------------------------- 1 | # add to array form of integer | leetcode 989 | https://leetcode.com/problems/add-to-array-form-of-integer 2 | 3 | class Solution: 4 | def addToArrayForm(self, num: list[int], k: int) -> list[int]: 5 | n = len(num) - 1 6 | carry = 0 7 | while k or carry: 8 | k, digit = k // 10, k % 10 9 | each = carry + digit 10 | if n < 0: 11 | num.insert(0, each % 10) 12 | else: 13 | each = each + num[n] 14 | num[n] = each % 10 15 | carry = each // 10 16 | n -= 1 17 | 18 | return num 19 | 20 | -------------------------------------------------------------------------------- /14. Questions/leetcode 997 - find the town judge.py: -------------------------------------------------------------------------------- 1 | # find the town judge | leetcode 997 | https://leetcode.com/problems/find-the-town-judge/submissions/ 2 | # method: decrement trust value if you trust someone, increment if someone trusts you 3 | 4 | class Solution: 5 | def findJudge(self, n: int, trust: list[list[int]]) -> int: 6 | 7 | # for each person 8 | # trust += 1 if someone trusts you 9 | # trust -= 1 if you trust someone 10 | trustValue = [0] * (n + 1) 11 | 12 | for edge in trust: 13 | trustValue[edge[0]] -= 1 14 | trustValue[edge[1]] += 1 15 | 16 | for i in range(1, n + 1): 17 | if trustValue[i] == (n - 1): 18 | return i 19 | 20 | return -1 21 | 22 | -------------------------------------------------------------------------------- /DSA_Cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/DSA_Cheatsheet.pdf -------------------------------------------------------------------------------- /Python_Cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/Python_Cheatsheet.pdf -------------------------------------------------------------------------------- /Python_Cheatsheet_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shushrutsharma/Data-Structures-and-Algorithms-Python/da9bc1e63d57a0b4edeeb334d476141a04e478b9/Python_Cheatsheet_2.pdf -------------------------------------------------------------------------------- /loggerMiddleware.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | // Middleware function to log requests 5 | app.use((req, res, next) => { 6 | console.log(`Received a ${req.method} request to ${req.url}`); 7 | next(); // Call next() to move to the next middleware or route handler 8 | }); 9 | 10 | // Middleware function to check if the request contains a specific header 11 | app.use((req, res, next) => { 12 | if (req.headers.authorization) { 13 | console.log('Authorization header present'); 14 | } else { 15 | console.log('Authorization header not present'); 16 | } 17 | next(); 18 | }); 19 | 20 | // Route handler 21 | app.get('/', (req, res) => { 22 | res.send('Hello, World!'); 23 | }); 24 | 25 | // Starting the server 26 | const PORT = process.env.PORT || 3000; 27 | app.listen(PORT, () => { 28 | console.log(`Server is running on port ${PORT}`); 29 | }); 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Data Structures and Algorithms 2 | 3 | ![Issues](https://img.shields.io/github/issues/shushrutsharma/Data-Structures-and-Algorithms-Python?color=red&style=for-the-badge) 4 | ![PRs](https://img.shields.io/github/issues-pr-closed/shushrutsharma/Data-Structures-and-Algorithms-Python?style=for-the-badge) 5 | ![Stars](https://img.shields.io/github/stars/shushrutsharma/Data-Structures-and-Algorithms-Python?style=for-the-badge) 6 | ![Built With](https://img.shields.io/badge/Built%20With-Python-blueviolet?style=for-the-badge) 7 | 8 | All the essential resources and template code needed to understand and practice data structures and algorithms in python with few small projects to demonstrate their practical application. 9 | 10 | ## Index 11 | 12 | ### 1. 📚 [Resources](/01.%20Resources/) 13 | 14 | 1. [Books](/01.%20Resources/Books/) 15 | - [Data Structures - Reema Thareja](/01.%20Resources/Books/Data%20Structures%20-%20Reema%20Thareja.pdf) 16 | - [competitiveCoding](https://github.com/mihirs16/Data-Structures-and-Algorithms-Python/blob/master/01.%20Resources/Books/competitiveCoding.pdf) 17 | 2. [Big-O Cheat Sheet](/01.%20Resources/BigO-cheat-sheet.pdf) 18 | 3. [DAA Syllabus](/01.%20Resources/DAA%20Syllabus.pdf) 19 | 4. [Interview Cheat sheet](/01.%20Resources/Interview%20cheatsheet.pdf) 20 | 5. [Master Plan](/01.%20Resources/Master%20Plan.pdf) 21 | 6. [Master the Interview](/01.%20Resources/Master_the_Interview.pdf) 22 | 23 | ### 2. ⭕ [Big-O](/02.%20Big-O/) 24 | 25 | 1. [O(1)](/02.%20Big-O/O(1).py) 26 | 2. [O(m+n)](/02.%20Big-O/O(m%20+%20n).py) 27 | 3. [O(n)](/02.%20Big-O/O(m%20x%20n).py) 28 | 4. [O(n^2)](/02.%20Big-O/O(n^2).py) 29 | 30 | ### 3. 🏢 [Data Structures](/03.%20Data%20Structures/) 31 | 32 | 1. [Arrays](/03.%20Data%20Structures/Arrays/) 33 | 2. [Graphs](/03.%20Data%20Structures/Graphs) 34 | 3. [Hash Tables (Dictionary)](/03.%20Data%20Structures/Hash%20Tables) 35 | 4. [Linked Lists](/03.%20Data%20Structures/Linked%20Lists) 36 | 5. [Stack](/03.%20Data%20Structures/Queues) 37 | 6. [Queues](/03.%20Data%20Structures/Stacks) 38 | 7. [Trees](/03.%20Data%20Structures/Trees) 39 | 40 | ### 4. 🛠 [Algorithms](/04.%20Algorithms/) 41 | 42 | 1. [Divide and Conquer](/04.%20Algorithms/Divide%20and%20Conquer/) 43 | 2. [Dynamic Programming](/04.%20Algorithms/Dynamic%20Programming/) 44 | 3. [Recursion](/04.%20Algorithms/Recursion/) 45 | 4. [Sorting](/04.%20Algorithms/Sorting/) 46 | 5. [Traversals](/04.%20Algorithms/Traversals) 47 | 48 | ### 5. 📂 [File Handling and OOPS](/05.%20File%20Handling%20and%20OOPS/) 49 | 50 | 1. [File + Classes Demo](/05.%20File%20Handling%20and%20OOPS/file%2Bclasses.py) 51 | 52 | ## Additional 53 | 54 | ### 6. ❗ [Error Handling](/06.%20Error%20Handling/) 55 | 56 | ### 7. ➗ [Functional Programming](/07.%20Functional%20Programming/) 57 | 58 | ### 8. 🎉 [Decorators](/08.%20Decorators/) 59 | 60 | ### 9. 👾 [Debugging](/09.%20Debugging/) 61 | 62 | ### 10. ➰ [Generators](/10.%20Generators/) 63 | 64 | ### 11. 🆎 [Regular Expressions](/11.%20Regular%20Expressions/) 65 | 66 | ### 12. 🧪 [Unit Testing](/12.%20Unit%20Testing/) 67 | 68 | ### 13. 👷 ‍[Projects](/13.%20Mini-Projects/) 69 | 70 | 1. [Job Scheduler](/13.%20Mini-Projects/Job%20Scheduler) 71 | 2. [Email Project](/13.%20Mini-Projects/email_project) 72 | 3. [Hash Project](/13.%20Mini-Projects/hash_project) 73 | 4. [Recursion Miniprojects](/13.%20Mini-Projects/recursion_miniprojects) 74 | 5. [Runtime Analyser](/13.%20Mini-Projects/runtime_analyser) 75 | 76 | ### 14. ❓ [Questions](/14.%20Questions/) 77 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Import required modules 2 | const express = require('express'); 3 | 4 | // Create an Express application 5 | const app = express(); 6 | 7 | // Middleware function to log requests 8 | app.use((req, res, next) => { 9 | console.log(`Received a ${req.method} request to ${req.url}`); 10 | next(); // Call next() to move to the next middleware or route handler 11 | }); 12 | 13 | // Middleware function to check if the request contains a specific header 14 | app.use((req, res, next) => { 15 | if (req.headers.authorization) { 16 | console.log('Authorization header present'); 17 | } else { 18 | console.log('Authorization header not present'); 19 | } 20 | next(); 21 | }); 22 | 23 | // Route handler 24 | app.get('/', (req, res) => { 25 | res.send('Hello, World!'); 26 | }); 27 | 28 | // Starting the server 29 | const PORT = process.env.PORT || 3000; 30 | app.listen(PORT, () => { 31 | console.log(`Server is running on port ${PORT}`); 32 | }); 33 | --------------------------------------------------------------------------------