diff --git a/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js b/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js index ce738c33..d51a44a7 100644 --- a/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js +++ b/Sprint-1/JavaScript/calculateSumAndProduct/calculateSumAndProduct.js @@ -9,26 +9,23 @@ * "product": 30 // 2 * 3 * 5 * } * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Time Complexity: O(n) -- Two loops through array. + * Space Complexity: 0(1) -- sum, product, num are all fixed size. + * Optimal Time Complexity: O(n) -- every element can be read once, but in single loop. * * @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 sum = 0; + let product = 1; + for (const num of numbers) { + sum += num; + product *= num; + } - let product = 1; - for (const num of numbers) { - product *= num; - } - - return { - sum: sum, - product: product, - }; + return { + sum: sum, + product: product, + }; } diff --git a/Sprint-1/JavaScript/findCommonItems/findCommonItems.js b/Sprint-1/JavaScript/findCommonItems/findCommonItems.js index 5619ae5d..f6e494e0 100644 --- a/Sprint-1/JavaScript/findCommonItems/findCommonItems.js +++ b/Sprint-1/JavaScript/findCommonItems/findCommonItems.js @@ -1,14 +1,29 @@ /** * Finds common items between two arrays. * - * Time Complexity: - * Space Complexity: - * Optimal Time Complexity: + * Time Complexity: O(n * m) -- where n is length of firstArray and + * m is length of secondArray. filter is O(n) and includes is o(m) for every n. + * + * Space Complexity: O(n) -- every element in firstArray could have its equivalent in secondArray. + * So in the worst case, n elements would have to be stored in the filtered array. + * Size of filtered array is k, such that k <= n. the Set (l) and the final spread array (m) are + * also smaller than n. The space used is k + l + m, so ultimately it's still O(n). + * + * Optimal Time Complexity: O(n + m) -- converting secondArray to a set gives you a O(1) lookup, + * with secondSet.has(item). So filter becomes O(n * 1) rather than O(n * m). + * + * Converting an array to a Set is a O(m) operation, since every element in secondArray (size of m), + * must be checked. As this only done once at the start, rather than for every element in firstArray. + * The final time complexity is O(n + m). + * + * However, there is increased space complexity of O(n + m) as a set of size m now needs to be stored. * * @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) => { + // dedupe the secondArray to reduce the number of inclues() + 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..317d2726 100644 --- a/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js +++ b/Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js @@ -1,21 +1,28 @@ /** * 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) -- if there is no pair that their sum = target, + * or its is the last two items then the function has to loop over every element, + * and for each element loop n-1, n-2, n-3, ..., 0 times. + * Total comparisons is n(n-1)/2 = (n^2 - n)/2. In big O terms that's just O(n^2). + * + * Space Complexity: O(1) -- space used does not grow with input size. + * + * Optimal Time Complexity: O(n) -- At worst, makes one pass through the array. + * All operations inside of the loop are O(1). * * @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) { - 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; - } + const seen = new Set(); + + for (const num of numbers) { + if (seen.has(target - num)) { + return true; } + seen.add(num); } return false; } diff --git a/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs b/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs index dc5f7711..3a89c4ef 100644 --- a/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs +++ b/Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs @@ -1,36 +1,20 @@ /** * 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) -- In the worst case, the number of comparisons in each step of the iteration + * increases by 1 because uniqueItems grows by 1. It goes 0 + 1 + 2 + ... + (n-1), so total number of + * comparisons is n(n - 1)/2 = (n^2 - n)/2. Big O discards the constant factor (1/2) and the lower-order term (n), leaving n^2. + * + * There aren't exactly n^2 comparisons, but big O doesn't care about that. + * + * Space Complexity: O(n) -- uniqueItems can hold up to n items when all items are unique. + * + * Optimal Time Complexity: O(n) -- Adding each element of the input sequence to the set is a O(1) (on average) operation, + * and then another O(n) operation to convert to an array with the spread operation. * * @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]); - } - } - - return uniqueItems; + return [...new Set(inputSequence)]; } diff --git a/Sprint-1/Python/calculate_sum_and_product/calculate_sum_and_product.py b/Sprint-1/Python/calculate_sum_and_product/calculate_sum_and_product.py index cfd5cfdf..7c304b96 100644 --- a/Sprint-1/Python/calculate_sum_and_product/calculate_sum_and_product.py +++ b/Sprint-1/Python/calculate_sum_and_product/calculate_sum_and_product.py @@ -12,20 +12,24 @@ def calculate_sum_and_product(input_numbers: List[int]) -> Dict[str, int]: "sum": 10, // 2 + 3 + 5 "product": 30 // 2 * 3 * 5 } - Time Complexity: - Space Complexity: - Optimal time complexity: + Time Complexity: O(n) -- Two separate loops through the list so 2n, but big O does not care + about the coefficient, so the complexity is O(n). + + Space Complexity: O(1) -- Three fixed size variables (sum, product, and current_number). The output + dict is also fixed size regardless of input size. + + Optimal time complexity: O(n) -- Use one loop instead of two, cuts the time taken by half, but the relation + between input size and computation time remains the same. """ # Edge case: empty list if not input_numbers: return {"sum": 0, "product": 1} sum = 0 - for current_number in input_numbers: - sum += current_number - product = 1 for current_number in input_numbers: + sum += current_number product *= current_number + return {"sum": sum, "product": product} diff --git a/Sprint-1/Python/find_common_items/find_common_items.py b/Sprint-1/Python/find_common_items/find_common_items.py index 478e2efc..1281757f 100644 --- a/Sprint-1/Python/find_common_items/find_common_items.py +++ b/Sprint-1/Python/find_common_items/find_common_items.py @@ -9,13 +9,25 @@ def find_common_items( """ Find common items between two arrays. - Time Complexity: - Space Complexity: - Optimal time complexity: + Time Complexity: O(n * m * k) -- where n = length of first_sequence, m = length of second_sequence, + k = length of common_items. k <= min(n, m) because common_items can never be bigger than the smaller + of the two sequences. As common_items is a list, checking membership is a linear time operation. + + Complexity class is O(n^2) if two are sequences are same size. + + Space Complexity: O(n) -- If every item in first_sequence is also in second_sequence, then the entirety of n + has to be stored in common_items. Techincally it's O(min(n, m)). + + Optimal time complexity: O(m + n) -- O(m) to convert second_sequence to a set, then. O(n) to iterate through + the first_sequence. All operations inside the loop are O(1) (more or less), so additional complexity. """ + second_set = set(second_sequence) # O(m) + seen = set() common_items: List[ItemType] = [] - for i in first_sequence: - for j in second_sequence: - if i == j and i not in common_items: - common_items.append(i) + + for item in first_sequence: # n iterations + if item in second_set and item not in seen: # O(1) average lookups, times 2 + seen.add(item) # O(1) average insertion + common_items.append(item) # O(1) + return common_items diff --git a/Sprint-1/Python/has_pair_with_sum/has_pair_with_sum.py b/Sprint-1/Python/has_pair_with_sum/has_pair_with_sum.py index fe2da517..4148cac4 100644 --- a/Sprint-1/Python/has_pair_with_sum/has_pair_with_sum.py +++ b/Sprint-1/Python/has_pair_with_sum/has_pair_with_sum.py @@ -7,12 +7,20 @@ def has_pair_with_sum(numbers: List[Number], target_sum: Number) -> bool: """ Find if there is a pair of numbers that sum to a target value. - Time Complexity: - Space Complexity: - Optimal time complexity: + Time Complexity: O(n^2) -- if there is no pair that their sum = target_sum, + or its is the last two items then the function has to loop over every element, + and for each element loop n-1, n-2, n-3, ..., 0 times. + Total comparisons is n(n-1)/2 = (n^2 - n)/2. In big O terms that's just O(n^2). + + Space Complexity: O(1) -- no additional memory is used. + + Optimal time complexity: O(n) -- Goes through the list at most once. adding to set is + O(1). """ - for i in range(len(numbers)): - for j in range(i + 1, len(numbers)): - if numbers[i] + numbers[j] == target_sum: - return True + seen = set() + for num in numbers: + # check if there is a number in the set that added to num = target_sum + if target_sum - num in seen: + return True + seen.add(num) return False diff --git a/Sprint-1/Python/remove_duplicates/remove_duplicates.py b/Sprint-1/Python/remove_duplicates/remove_duplicates.py index c9fdbe80..b98f4968 100644 --- a/Sprint-1/Python/remove_duplicates/remove_duplicates.py +++ b/Sprint-1/Python/remove_duplicates/remove_duplicates.py @@ -7,19 +7,16 @@ def remove_duplicates(values: Sequence[ItemType]) -> List[ItemType]: """ Remove duplicate values from a sequence, preserving the order of the first occurrence of each value. - Time complexity: - Space complexity: - Optimal time complexity: - """ - unique_items = [] + Time complexity: O(n^2) -- In the worst case, when all items are unique, the number of comparisons + in each step of the iteration increases by 1 because unique_items grows by 1. + It goes 0 + 1 + 2 + ... + (n-1), so total number of comparisons is n(n - 1)/2 = (n^2 - n)/2. + Big O discards the constant factor (1/2) and the lower-order term (n), leaving n^2. - for value in values: - is_duplicate = False - for existing in unique_items: - if value == existing: - is_duplicate = True - break - if not is_duplicate: - unique_items.append(value) + Space complexity: O(n) -- unique_items can hold up to n items when all items are unique. + + Optimal time complexity: O(n) -- Adding each element of "values" to the dict is a O(1) (on average) operation, + and then another O(n) operation to convert to a list. + """ - return unique_items + # creates a dictionary, using the values of the input as as keys (unique, and preserves order) + return list(dict.fromkeys(values)) \ No newline at end of file