Foundations
4 / 170FoundationsLesson 4 of 5About 39 min
How to say what an approach costs, and how to read the constraints as a hint about which cost is acceptable.
Look at the loops. A loop over n items that does constant work per item is O(n). Nesting a second such loop inside it is O(n squared). Loops that run in sequence add, so O(n) then another O(n) is still O(n).
# O(n): one passfor x in nums: total += x # O(n squared): every pairfor i in range(len(nums)): for j in range(i + 1, len(nums)): check(nums[i], nums[j]) # still O(n): two passes in sequence, not nestedfor x in nums: total += xfor x in nums: largest = max(largest, x)A loop that halves the remaining range each pass runs about log base 2 of n times, because that is how many times you can halve n before reaching 1.
That number is much smaller than people expect, which is the whole reason the shape is worth recognizing. Counting it once is more convincing than the formula.
# O(log n): however you halve, this is how many passes you getremaining = 1_000_000passes = 0 while remaining > 1: remaining //= 2 passes += 1 print(passes, "passes to get from a million down to one")Space is the extra memory you allocate, not counting the input itself. A hash set holding up to n values is O(n) space. A handful of scalar variables is O(1), no matter how big the input is.
Recursion costs space too, through the call stack. A recursion that goes n levels deep before returning uses O(n) space even if it allocates nothing. That is the answer to why an O(n) recursive solution can still be worse than an O(n) iterative one.
Use this before you write anything. If n can be a million and your first idea checks every pair, that is 10^12 operations, four orders of magnitude past the budget. The idea is dead and you know it in ten seconds rather than after twenty minutes of implementation.
The constraint is not decoration. It is the clearest signal in the problem statement about which solution is wanted.
Give the time and space complexity of this function in terms of n, the length of nums.
def has_pair_with_sum(nums, target): for i in range(len(nums)): for j in range(i + 1, len(nums)): if nums[i] + nums[j] == target: return True return FalseA problem states that the array length is at most 200,000 and asks for the number of pairs meeting a condition. Your first idea checks every pair. What should you conclude?
count_duplicates is correct but quadratic: it calls .count() on the list for every element, and each call scans the whole list. Rewrite it to run in one pass using a dict or Counter, keeping the same answer. It returns how many values appear more than once.
Tests
print(count_duplicates([1, 2, 2, 3, 3, 3, 4]))print(count_duplicates([]))print(count_duplicates([5, 5, 5, 5]))print(count_duplicates([1, 2, 3]))Output
Run tests to see output.