Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>} 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,
};
}
27 changes: 21 additions & 6 deletions Sprint-1/JavaScript/findCommonItems/findCommonItems.js
Original file line number Diff line number Diff line change
@@ -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)))];
Comment on lines +26 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note:
Most Set implementations support the common set operations such as union, intersection, difference, subset, superset, equality, add, remove, contains, size, clear, and is empty.

You could also take advantage of these built-in methods for better performance.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so I guess I could something like

export const findCommonItems = (firstArray, secondArray) => {
    return [...new Set(firstArray).intersection(new Set(secondArray))];
};

The complexity would be the same as before.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Same complexity. Slightly more efficient.

};
23 changes: 15 additions & 8 deletions Sprint-1/JavaScript/hasPairWithSum/hasPairWithSum.js
Original file line number Diff line number Diff line change
@@ -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<number>} 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;
}
38 changes: 11 additions & 27 deletions Sprint-1/JavaScript/removeDuplicates/removeDuplicates.mjs
Original file line number Diff line number Diff line change
@@ -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)];
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}
26 changes: 19 additions & 7 deletions Sprint-1/Python/find_common_items/find_common_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would $k$ affect the complexity in the worst case scenario?

@RaihanSharif RaihanSharif Oct 24, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think k is what makes it a cubic complexity rather than quadratic complexity. Because the looping by itself is quadratic, but in the worst case there are n checks of the membership of the comon_items list, which itself is O(k), and in the worst case k is min(m, n).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suppose the lists store integers.

Basically, where there n ^2 checks, common_items will only have 1 item. So it becomes n^2 iterations * 1 check per iteration. O(n^2).

This describes the case list1 = list2 = [1, 1, 1, ..., 1]

If k is large, checking takes k time, but there are only n checks of the common_items list because out n*n iterations only n of them satisfy the i == j condition. Therefore, where k = n, the result is again n ^2.

This describes the case list1 = list2 = [1, 2, 3, 4, ..., n]

However, these two extreme but opposite cases are not the worst case scenario.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've finally figured it out.

If we take a pair of identical lists which have all unique values, and all values are in common, such as [1, 2, 3, 4], then the nested loop would have to run n^2 times, and i == j would be true once per outer loop, which is n times.

The common_items list would increase by 1 at each match i == j, so the next time it’s a match and common_items is checked, the time taken would increase by 1. So the total number of checks would be (1 + 2 + 3 + … + (n-1) = n(n-1)/2 . That’s just the complexity of the common_items membership check, there’s also the complexity of the number of equality checks, so the total is n^2 + n*(n-1)/2 which is O(n^2).

The other extreme is that, the lists contain one common element, like [1, 1, 1, 1, 1] and you’d have the nested loop n^2 iterations and at each inner loop it has to check the membership ans i == j is always true. So the number of check of common_items is n^2 * 1 since the size of common items would be constant. So again, O(n^2).

These are not the worst case scenarios. The worst scenario is when you have two identical lists, but only the first half is unique: [1, 2, 3, 4, 9, 9, 9, 9] so that initially you do the checking and populating of common_items same as in the first scenario. Half way through, you now have a common_items of size n/2 . Remaining outer loops = n/2 and for each outer loop iteration there are n inner loop iterations. For half of those there’s a check of common_items and checking common_items costs n/2. So the total is outer loop (n/2) * number of common_items checks (n/2) * cost of checking common_items (n/2).

(n/2) * (n/2) * (n/2) = n^3/8 for the second half of the list. As the list grows, the O(^3) of the second half of the list dominates the O(^2) of the first half of the list, so worst case is O(n^3).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spot on! Excellent work.


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
22 changes: 15 additions & 7 deletions Sprint-1/Python/has_pair_with_sum/has_pair_with_sum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 11 additions & 14 deletions Sprint-1/Python/remove_duplicates/remove_duplicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Loading