From 991a03ecec9d8f4de57e864e51217868aca3f9c1 Mon Sep 17 00:00:00 2001 From: Ankur Gokhale Date: Sun, 6 Sep 2026 02:53:07 -0500 Subject: [PATCH 1/2] PreCourse-2: Exercise 1,2,3,4,5 Complete --- Exercise_1.py | 138 ++++++++++++++++++++++++++++++++++++++++++-------- Exercise_2.py | 133 +++++++++++++++++++++++++++++++++++++++--------- Exercise_3.py | 137 +++++++++++++++++++++++++++++++++++++++---------- Exercise_4.py | 89 ++++++++++++++++++++++++++++---- Exercise_5.py | 134 +++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 544 insertions(+), 87 deletions(-) diff --git a/Exercise_1.py b/Exercise_1.py index 3e6adcf4..399bde97 100644 --- a/Exercise_1.py +++ b/Exercise_1.py @@ -1,22 +1,116 @@ -# Python code to implement iterative Binary -# Search. - -# It returns location of x in given array arr -# if present, else returns -1 -def binarySearch(arr, l, r, x): - - #write your code here - - - -# Test array -arr = [ 2, 3, 4, 10, 40 ] -x = 10 - -# Function call -result = binarySearch(arr, 0, len(arr)-1, x) - -if result != -1: - print "Element is present at index % d" % result -else: - print "Element is not present in array" +# Python code to implement iterative Binary +# Search. + +# It returns location of x in given array arr +# if present, else returns -1 +""" +Time Complexity: O(log n) since we are dividing the array into half in each iteration. +Space Complexity: O(1) since we are not using any extra space or any other data structure. + +Did this code successfully run on Leetcode : Yes (Problem: 704. Binary Search) +Approach: +1. We will use two pointers left and right to keep track of the current search space. +2. We will calculate the mid index and compare the mid element with the target value x. +3. If the mid element is equal to x, we return the mid index. +4. If the mid element is less than x, we move the left pointer to mid + 1 to search in the right half of the array. +5. If the mid element is greater than x, we move the right pointer to mid - 1 to search in the left half of the array. +6. We repeat steps 2-5 until the left pointer is less than or equal to the right pointer. If we exit the loop without finding x, we return -1 indicating that x is not present in the array. +""" + + +def binarySearch(arr, l, r, x): + + # write your code here + # Handling edge cases + if arr is None or len(arr) == 0: + return -1 + left = 0 + right = len(arr) - 1 + while left <= right: + # To avoid integer overflow use left + (right - left) // 2 instead of (left + right) // 2 + mid = left + (right - left) // 2 + if arr[mid] == x: + return mid + elif arr[mid] < x: + left = mid + 1 + else: + right = mid - 1 + return -1 + + +def run_tests(): + """A small test suite covering typical, edge, and boundary cases.""" + + # 1. Original example: element present in the middle-ish of the array + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 10) == 3, "element present (10)" + + # 2. Element is the first item + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 2) == 0, "element is first item" + + # 3. Element is the last item + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 40) == 4, "element is last item" + + # 4. Element not present (falls between two values) + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 5) == -1, "element not present (between values)" + + # 5. Element smaller than everything in the array + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 1) == -1, "element smaller than min" + + # 6. Element larger than everything in the array + arr = [2, 3, 4, 10, 40] + assert binarySearch(arr, 0, len(arr) - 1, 100) == -1, "element larger than max" + + # 7. Empty array + arr = [] + assert binarySearch(arr, 0, -1, 5) == -1, "empty array" + + # 8. None array (edge case handled explicitly in the function) + assert binarySearch(None, 0, -1, 5) == -1, "None array" + + # 9. Single-element array, element present + arr = [7] + assert binarySearch(arr, 0, 0, 7) == 0, "single element, present" + + # 10. Single-element array, element absent + arr = [7] + assert binarySearch(arr, 0, 0, 3) == -1, "single element, absent" + + # 11. Two-element array, both possibilities + arr = [1, 5] + assert binarySearch(arr, 0, 1, 1) == 0, "two elements, find first" + assert binarySearch(arr, 0, 1, 5) == 1, "two elements, find second" + assert binarySearch(arr, 0, 1, 3) == -1, "two elements, not found" + + # 12. Larger array with even length + arr = list(range(0, 100, 2)) # [0, 2, 4, ..., 98] + assert binarySearch(arr, 0, len(arr) - 1, 50) == 25, "large even-length array, present" + assert binarySearch(arr, 0, len(arr) - 1, 51) == -1, "large even-length array, absent (odd number)" + + # 13. Negative numbers + arr = [-20, -10, -5, 0, 5, 10] + assert binarySearch(arr, 0, len(arr) - 1, -10) == 1, "negative numbers, present" + assert binarySearch(arr, 0, len(arr) - 1, -1) == -1, "negative numbers, absent" + + print("All tests passed!") + + +if __name__ == "__main__": + # Test array + arr = [2, 3, 4, 10, 40] + x = 10 + + # Function call + result = binarySearch(arr, 0, len(arr) - 1, x) + + if result != -1: + print("Element is present at index % d" % result) + else: + print("Element is not present in array") + + print("\nRunning test suite...\n") + run_tests() \ No newline at end of file diff --git a/Exercise_2.py b/Exercise_2.py index 35abf0dd..800040e1 100644 --- a/Exercise_2.py +++ b/Exercise_2.py @@ -1,23 +1,110 @@ -# Python program for implementation of Quicksort Sort - -# give you explanation for the approach -def partition(arr,low,high): - - - #write your code here - - -# Function to do Quick sort -def quickSort(arr,low,high): - - #write your code here - -# Driver code to test above -arr = [10, 7, 8, 9, 1, 5] -n = len(arr) -quickSort(arr,0,n-1) -print ("Sorted array is:") -for i in range(n): - print ("%d" %arr[i]), - - +# Python program for implementation of Quicksort Sort +""" +Time Complexity: O(n log n) on average, O(n^2) in worst case) +Space Complexity: O(n) in worst case due to recursion stack, O(log n) on average. + +Did this code successfully run on Leetcode : Yes (Problem: 912. Sort an Array) + +Approach: + +I have implemented the quicksort using Median-of-three pivot selection +and partitioning the array into two halves based on the pivot (Standard Lomuto Partitioning). +The algorithm recursively sorts the left and right halves of the array until the entire array is sorted. + + +The use of the median-of-three pivot selection helps +to reduce the likelihood of encountering the worst-case scenario with +Standard lomuto partitioning based pivot selection, which can occur when +the pivot is consistently chosen poorly especially for already sorted, +reverse sorted or nearly sorted arrays. +""" + +def median_of_three(arr, low, high): + # Sort arr[low], arr[mid], arr[high] in place so arr[mid] ends up the median + mid = low + (high - low) // 2 + if arr[low] > arr[mid]: + arr[low], arr[mid] = arr[mid], arr[low] + if arr[low] > arr[high]: + arr[low], arr[high] = arr[high], arr[low] + if arr[mid] > arr[high]: + arr[mid], arr[high] = arr[high], arr[mid] + return mid # Return the index of the median value + + +def partition(arr, low, high): + + mid = median_of_three(arr, low, high) + + # Swap the median value with the last element to use it as the pivot + arr[mid], arr[high] = arr[high], arr[mid] + pivot = arr[high] + + # Standard Lomuto partitioning + i = low - 1 + + for j in range(low, high): + if arr[j] <= pivot: + i += 1 + arr[i], arr[j] = arr[j], arr[i] + arr[i + 1], arr[high] = arr[high], arr[i + 1] + return i + 1 + + +# Function to do Quick sort +def quickSort(arr, low=0, high=None): + if high is None: + high = len(arr) - 1 + if low >= high or low < 0: + return arr + if low < high: + pivot_index = partition(arr, low, high) + # Recursively sort elements before partition and after partition + quickSort(arr, low, pivot_index - 1) + quickSort(arr, pivot_index + 1, high) + return arr + + +def run_tests(): + # Original driver example + assert quickSort([10, 7, 8, 9, 1, 5]) == [1, 5, 7, 8, 9, 10] + + # Empty array + assert quickSort([]) == [] + + # Single element + assert quickSort([1]) == [1] + + # Two elements + assert quickSort([2, 1]) == [1, 2] + + # Already sorted + assert quickSort([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5] + + # Reverse sorted + assert quickSort([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5] + + # All duplicates + assert quickSort([3, 3, 3, 3]) == [3, 3, 3, 3] + + # Duplicates mixed in + assert quickSort([4, 2, 2, 8, 4, 1]) == [1, 2, 2, 4, 4, 8] + + # Negative numbers + assert quickSort([-5, 3, -2, 0, 8, -1]) == [-5, -2, -1, 0, 3, 8] + + # Duplicates + negatives + assert quickSort([9, -3, 7, 0, 0, -3, 9]) == [-3, -3, 0, 0, 7, 9, 9] + + print("All tests passed!") + + +if __name__ == "__main__": + run_tests() + + print("\nDriver code demo:") + arr = [10, 7, 8, 9, 1, 5] + n = len(arr) + quickSort(arr, 0, n - 1) + print("Sorted array is:") + for i in range(n): + print("%d" % arr[i]) \ No newline at end of file diff --git a/Exercise_3.py b/Exercise_3.py index a26a69b8..21bdd6d0 100644 --- a/Exercise_3.py +++ b/Exercise_3.py @@ -1,26 +1,111 @@ -# Node class -class Node: - - # Function to initialise the node object - def __init__(self, data): - -class LinkedList: - - def __init__(self): - - - def push(self, new_data): - - - # Function to get the middle of - # the linked list - def printMiddle(self): - -# Driver code -list1 = LinkedList() -list1.push(5) -list1.push(4) -list1.push(2) -list1.push(3) -list1.push(1) -list1.printMiddle() +""" +Time Complexity: O(n) — the fast pointer traverses the list once, visiting each node at most once (in steps of 2), + so it's linear in the number of nodes. +Space Complexity: O(1) — only two pointers are used regardless of list size; no extra data structures. + + +Approach: +This uses the slow/fast pointer (tortoise and hare) technique: + +1. Slow_pointer moves one node at a time. +2. Fast_pointer moves two nodes at a time. +3. When fast_pointer reaches the end of the list, slow_pointer will be at the middle, + since it has traveled half the distance. +4. This works in a single pass without needing to know the length of the list in advance. +""" + + +# Node class +class Node: + + # Function to initialise the node object + def __init__(self, data): + self.data = data # Assign data + self.next = None # Initialize next as null + + +class LinkedList: + + def __init__(self): + self.head = None + + def push(self, new_data): + new_node = Node(new_data) + new_node.next = self.head + self.head = new_node + + def to_list(self): + """Helper to convert linked list to a Python list (for testing).""" + result = [] + current = self.head + while current: + result.append(current.data) + current = current.next + return result + + # Function to get the middle of + # the linked list + def printMiddle(self): + middle_value = self.get_middle() + print("The middle element is:", middle_value) + + def get_middle(self): + """Returns the middle element's data (or None if list is empty).""" + slow_pointer = self.head + fast_pointer = self.head + if self.head is not None: + while (fast_pointer is not None and + fast_pointer.next is not None): + fast_pointer = fast_pointer.next.next + slow_pointer = slow_pointer.next + return slow_pointer.data + return None + + +def run_tests(): + # Test 1: Odd number of elements + # push order: 5,4,2,3,1 -> list: 1 -> 3 -> 2 -> 4 -> 5 + list1 = LinkedList() + for val in [5, 4, 2, 3, 1]: + list1.push(val) + assert list1.to_list() == [1, 3, 2, 4, 5], "List construction failed" + assert list1.get_middle() == 2, f"Expected 2, got {list1.get_middle()}" + print("Test 1 passed: odd-length list middle =", list1.get_middle()) + + # Test 2: Even number of elements + # push order: 4,3,2,1 -> list: 1 -> 2 -> 3 -> 4 + list2 = LinkedList() + for val in [4, 3, 2, 1]: + list2.push(val) + assert list2.to_list() == [1, 2, 3, 4], "List construction failed" + # With slow/fast pointer approach, for even-length lists the + # "middle" returned is the second of the two middle elements. + assert list2.get_middle() == 3, f"Expected 3, got {list2.get_middle()}" + print("Test 2 passed: even-length list middle =", list2.get_middle()) + + # Test 3: Single element + list3 = LinkedList() + list3.push(42) + assert list3.get_middle() == 42, f"Expected 42, got {list3.get_middle()}" + print("Test 3 passed: single-element list middle =", list3.get_middle()) + + # Test 4: Empty list + list4 = LinkedList() + assert list4.get_middle() is None, "Expected None for empty list" + print("Test 4 passed: empty list returns None") + + print("\nAll tests passed!") + + +if __name__ == "__main__": + # Driver code (original demonstration) + list1 = LinkedList() + list1.push(5) + list1.push(4) + list1.push(2) + list1.push(3) + list1.push(1) + list1.printMiddle() + + print("\nRunning tests...\n") + run_tests() \ No newline at end of file diff --git a/Exercise_4.py b/Exercise_4.py index 9bc25d3d..7794c66e 100644 --- a/Exercise_4.py +++ b/Exercise_4.py @@ -1,18 +1,87 @@ +""" +Time: O(n log n) in all cases (same halving + linear merge structure) +Space: O(n) auxiliary space (each recursive call still creates new sliced lists) + +# Approach: +1. Divide the array into two halves +2. Recursively sort the left half +3. Recursively sort the right half +4. Merge the two sorted halves back into the original array +""" + # Python program for implementation of MergeSort + def mergeSort(arr): #write your code here + if arr is None or len(arr) < 2: + return arr # Already sorted or invalid input + left = 0 + right = len(arr) - 1 + mid = left + (right - left) // 2 + left_half = arr[left:mid + 1] + right_half = arr[mid + 1:right + 1] + mergeSort(left_half) + mergeSort(right_half) + i = j = k = 0 + while i < len(left_half) and j < len(right_half): + if left_half[i] <= right_half[j]: + arr[k] = left_half[i] + i += 1 + else: + arr[k] = right_half[j] + j += 1 + k += 1 + while i < len(left_half): + arr[k] = left_half[i] + i += 1 + k += 1 + while j < len(right_half): + arr[k] = right_half[j] + j += 1 + k += 1 + return arr # Code to print the list def printList(arr): - - #write your code here + if arr is None or len(arr) == 0: + print("[]") + return + for i in range(len(arr)): + print(arr[i], end=" ") + print() # New line at the end + -# driver code to test the above code -if __name__ == '__main__': - arr = [12, 11, 13, 5, 6, 7] - print ("Given array is", end="\n") - printList(arr) - mergeSort(arr) - print("Sorted array is: ", end="\n") - printList(arr) +def run_test(): + test_cases = [ + ("Unsorted array", [12, 11, 13, 5, 6, 7], [5, 6, 7, 11, 12, 13]), + ("Already sorted", [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]), + ("Reverse sorted", [5, 4, 3, 2, 1], [1, 2, 3, 4, 5]), + ("Duplicates", [4, 2, 2, 8, 4, 1], [1, 2, 2, 4, 4, 8]), + ("Single element", [42], [42]), + ("Empty array", [], []), + ("Two elements", [2, 1], [1, 2]), + ("Negative numbers", [-3, -1, -7, 2, 0], [-7, -3, -1, 0, 2]), + ("All same elements", [7, 7, 7, 7], [7, 7, 7, 7]), + ("None input", None, None), + ] + + for name, input_arr, expected in test_cases: + result = input_arr[:] if input_arr is not None else None # copy so original isn't mutated + result = mergeSort(result) + status = "PASS" if result == expected else "FAIL" + print(f"{status}: {name} -> got {result}, expected {expected}") + + +# driver code to test the above code +if __name__ == '__main__': + arr = [12, 11, 13, 5, 6, 7] + print("Given array is") + printList(arr) + mergeSort(arr) + print("Sorted array is: ") + printList(arr) + + print("\nRunning tests...") + run_test() + print("\nAll tests completed.") diff --git a/Exercise_5.py b/Exercise_5.py index 1da24ffb..0eee3c3e 100644 --- a/Exercise_5.py +++ b/Exercise_5.py @@ -1,10 +1,132 @@ -# Python program for implementation of Quicksort +# Python program for implementation of Quicksort Sort (Iterative) +""" +Time Complexity: O(n log n) on average, O(n^2) in worst case (same as recursive version; +median-of-three pivot selection makes the worst case rare in practice). -# This function is same in both iterative and recursive -def partition(arr, l, h): - #write your code here +Space Complexity: O(log n) in the average case, O(log n) in the worst case due to handling how elements +are pushed onto the stack and not naive implementation. +Did this code successfully run on Leetcode : Yes (Problem: 912. Sort an Array) -def quickSortIterative(arr, l, h): - #write your code here +Approach: +Same median-of-three pivot selection and Lomuto partitioning as the recursive +version, but recursion is replaced with an explicit stack of (low, high) index +pairs to avoid using the call stack. + +After partitioning, instead of pushing both the left and right sub-ranges onto +the stack (which can still lead to O(n) stack depth in worst case), +we compare their sizes: + - The smaller sub-range is handled by updating (low, high) and looping again + immediately - no stack push needed. + - The larger sub-range is pushed onto the stack to be processed later. + + +""" + +def median_of_three(arr, low, high): + # Sort arr[low], arr[mid], arr[high] in place so arr[mid] ends up the median + mid = low + (high - low) // 2 + if arr[low] > arr[mid]: + arr[low], arr[mid] = arr[mid], arr[low] + if arr[low] > arr[high]: + arr[low], arr[high] = arr[high], arr[low] + if arr[mid] > arr[high]: + arr[mid], arr[high] = arr[high], arr[mid] + return mid # Return the index of the median value + + +def partition(arr, low, high): + + mid = median_of_three(arr, low, high) + + # Swap the median value with the last element to use it as the pivot + arr[mid], arr[high] = arr[high], arr[mid] + pivot = arr[high] + + # Standard Lomuto partitioning + i = low - 1 + + for j in range(low, high): + if arr[j] <= pivot: + i += 1 + arr[i], arr[j] = arr[j], arr[i] + arr[i + 1], arr[high] = arr[high], arr[i + 1] + return i + 1 + + +# Function to do Quick sort (iterative) +def quickSort(arr, low=0, high=None): + if high is None: + high = len(arr) - 1 + if low < 0 or high < 0 or low >= high: + return arr + + stack = [(low, high)] + + while stack: + low, high = stack.pop() + + # Loop directly on whichever side is smaller, only pushing the larger side. + while low < high: + pivot_index = partition(arr, low, high) + + left_size = pivot_index - 1 - low + right_size = high - (pivot_index + 1) + + if left_size < right_size: + # Left is smaller: push the larger right side, keep looping on left + stack.append((pivot_index + 1, high)) + high = pivot_index - 1 + else: + # Right is smaller (or equal): push the larger left side, keep looping on right + stack.append((low, pivot_index - 1)) + low = pivot_index + 1 + + return arr + + +def run_tests(): + # Original driver example + assert quickSort([10, 7, 8, 9, 1, 5]) == [1, 5, 7, 8, 9, 10] + + # Empty array + assert quickSort([]) == [] + + # Single element + assert quickSort([1]) == [1] + + # Two elements + assert quickSort([2, 1]) == [1, 2] + + # Already sorted + assert quickSort([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5] + + # Reverse sorted + assert quickSort([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5] + + # All duplicates + assert quickSort([3, 3, 3, 3]) == [3, 3, 3, 3] + + # Duplicates mixed in + assert quickSort([4, 2, 2, 8, 4, 1]) == [1, 2, 2, 4, 4, 8] + + # Negative numbers + assert quickSort([-5, 3, -2, 0, 8, -1]) == [-5, -2, -1, 0, 3, 8] + + # Duplicates + negatives + assert quickSort([9, -3, 7, 0, 0, -3, 9]) == [-3, -3, 0, 0, 7, 9, 9] + + print("All tests passed!") + + +if __name__ == "__main__": + run_tests() + + print("\nDriver code demo:") + arr = [10, 7, 8, 9, 1, 5] + n = len(arr) + quickSort(arr, 0, n - 1) + print("Sorted array is:") + for i in range(n): + print("%d" % arr[i]) \ No newline at end of file From 2df0729641c3697501a3ee0593fb24b25176c076 Mon Sep 17 00:00:00 2001 From: Ankur Gokhale Date: Sun, 6 Sep 2026 02:56:35 -0500 Subject: [PATCH 2/2] PreCourse-2 Exercise 1,2,3,4,5 Complete --- Exercise_3.py | 1 + Exercise_4.py | 1 + 2 files changed, 2 insertions(+) diff --git a/Exercise_3.py b/Exercise_3.py index 21bdd6d0..6d8e654b 100644 --- a/Exercise_3.py +++ b/Exercise_3.py @@ -3,6 +3,7 @@ so it's linear in the number of nodes. Space Complexity: O(1) — only two pointers are used regardless of list size; no extra data structures. +Did this code successfully run on Leetcode: Yes (Problem: 876. Middle of the Linked List) Approach: This uses the slow/fast pointer (tortoise and hare) technique: diff --git a/Exercise_4.py b/Exercise_4.py index 7794c66e..8b8dd84e 100644 --- a/Exercise_4.py +++ b/Exercise_4.py @@ -1,6 +1,7 @@ """ Time: O(n log n) in all cases (same halving + linear merge structure) Space: O(n) auxiliary space (each recursive call still creates new sliced lists) +Did this code successfully run on Leetcode : Yes (Problem: 912. Sort an Array) # Approach: 1. Divide the array into two halves