diff --git a/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js b/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js index ce738c33..1e070ca6 100644 --- a/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js +++ b/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js @@ -9,21 +9,22 @@ * "product": 30 // 2 * 3 * 5 * } * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Time Complexity: O(n) + * Space Complexity:O(1) + * Optimal Time Complexity:O(n) * * @param {Array} numbers - Numbers to process * @returns {Object} Object containing running total and product */ export function calculateSumAndProduct(numbers) { let sum = 0; - for (const num of numbers) { - sum += num; - } - let product = 1; + + // Initial code looped twice through the array, i.e. 2 operations (n + n) = O(2n)operations => O(n). Limiting the loop to just one operation makes it n operation => O(n). + // The space complexity is constant as the variables do not grow when the function is called. + for (const num of numbers) { + sum += num; product *= num; } diff --git a/Sprint-1/JavaScript/findCommonItems/findCommonItems.js b/Sprint-1/JavaScript/findCommonItems/findCommonItems.js index 5619ae5d..6dc77e25 100644 --- a/Sprint-1/JavaScript/findCommonItems/findCommonItems.js +++ b/Sprint-1/JavaScript/findCommonItems/findCommonItems.js @@ -1,14 +1,21 @@ /** * Finds common items between two arrays. * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Initial code has this and can be refactored for optimisation + * Time Complexity: O(nm) + * Space Complexity: O(n) + * Optimal Time Complexity: O(n + m) * * @param {Array} firstArray - First array to compare * @param {Array} secondArray - Second array to compare * @returns {Array} Array containing unique common items */ -export const findCommonItems = (firstArray, secondArray) => [ - ...new Set(firstArray.filter((item) => secondArray.includes(item))), -]; +// export const findCommonItems = (firstArray, secondArray) => [ +// ...new Set(firstArray.filter((item) => secondArray.includes(item))), +// ]; +// The initial solution had an expensive operation of .includes(item) search on the secondArray, resulting in O(nm) time complexity. The optimised code stores the secondArray in a set and allows O(1) lookup and reduce the complexity to O(n+m) +export const findCommonItems = (firstArray, secondArray) => { + const secondSet = new Set(secondArray); + + return [...new Set(firstArray.filter((item) => secondSet.has(item)))]; +}; diff --git a/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js b/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js index dd2901f6..97bac660 100644 --- a/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js +++ b/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js @@ -1,21 +1,24 @@ /** * Find if there is a pair of numbers that sum to a given target value. * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Time Complexity: O(n^2) + * Space Complexity:O(1) + * Optimal Time Complexity: O(n) * * @param {Array} numbers - Array of numbers to search through * @param {number} target - Target sum to find * @returns {boolean} True if pair exists, false otherwise */ export function hasPairWithSum(numbers, target) { + const seenNumbers = new Set(); for (let i = 0; i < numbers.length; i++) { - for (let j = i + 1; j < numbers.length; j++) { - if (numbers[i] + numbers[j] === target) { - return true; - } + // initial solution iterates twice, having outer and inner loop which gives us a time complexity of O(n**2). To optimise this, using a set to store seen numbers, then check whether the complement(target - number) for each num in the array has been seen and in the set. This gives a complexity of O(n) + let complement = target - numbers[i]; + + if (seenNumbers.has(complement)) { + return true; } + seenNumbers.add(numbers[i]); } return false; } diff --git a/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs b/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs index dc5f7711..cfb1215f 100644 --- a/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs +++ b/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs @@ -1,36 +1,22 @@ /** * Remove duplicate values from a sequence, preserving the order of the first occurrence of each value. * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Time Complexity: O(n**2) + * Space Complexity: O(n) + * Optimal Time Complexity: O(n) * * @param {Array} inputSequence - Sequence to remove duplicates from * @returns {Array} New sequence with duplicates removed */ export function removeDuplicates(inputSequence) { - const uniqueItems = []; - - for ( - let currentIndex = 0; - currentIndex < inputSequence.length; - currentIndex++ - ) { - let isDuplicate = false; - for ( - let compareIndex = 0; - compareIndex < uniqueItems.length; - compareIndex++ - ) { - if (inputSequence[currentIndex] === uniqueItems[compareIndex]) { - isDuplicate = true; - break; - } - } - if (!isDuplicate) { - uniqueItems.push(inputSequence[currentIndex]); + const seenItems = new Set(); + let result = []; + // The initial solution was a nested loop, with a time complexity of O(n**2). This was optimised by using a set to track seen items, thereby reducing the complexity to O(n) + for (const item of inputSequence) { + if (!seenItems.has(item)) { + seenItems.add(item); + result.push(item); } } - - return uniqueItems; + return result; } diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29b..8375940d 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,108 @@ +class Node: + """ + A node in a double linked list. + + Each node supports a data, previous, and next + - data: the value + - previous: pointer to the previous node + - next: pointer to the next node + """ + + def __init__(self, data): + self.data = data + self.previous = None + self.next = None + +class LinkedList: + """ + Double Linked List + + Supports O(1) operations on insertion at head, removal from tail, and removal from arbitrary node + """ + def __init__(self): + # Head points to the first node in the list + self.head = None + + # Tail points to the last node in the list + self.tail = None + + def is_empty(self): + """Returns True if the list contains no nodes.""" + return self.head is None + + def push_head(self, data): + """ + Insert a new node at the head of the list. + + Time Complexity: O(1) + + Returns: + Node: reference to the inserted node (handle for later removal) + """ + node = Node(data) + + if self.is_empty(): + # If list is empty, head and tail both point to new node + self.head = node + self.tail = node + else: + # Link new node before current head + node.next = self.head + self.head.previous = node + + # Update head pointer + self.head = node + + return node + + def pop_tail(self): + """ + Remove and return the value at the tail of the list. + + Time Complexity: O(1) + + Raises: + ValueError: if the list is empty + """ + + if self.is_empty(): + raise ValueError("List is empty.") + + # store value before removing node + value = self.tail.data + + if self.head == self.tail: + # Only one element in the list + self.head = None + self.tail = None + + else: + # Move tail pointer backwards + self.tail = self.tail.previous + + # Disconnect old tail node + self.tail.next = None + + return value + + def remove(self, node): + """ + Remove a node from the list using its reference. + + Time Complexity: O(1) + + Args: + node (Node): the node to remove + """ + if node.previous: + node.previous.next = node.next + else: + self.head = node.next + + if node.next: + node.next.previous = node.previous + else: + self.tail = node.previous + + node.previous = None + node.next = None