Yes. Iterating over a list is O(n) and adding each element to the hash set is O(1), so the total operation is O(n).
Yes. Iterating over a list is O(n) and adding each element to the hash set is O(1), so the total operation is O(n).
I was asked the same question in my last interview and didn't get it right.
As Trilarion commented in the first solution, the worst-case complexity is O(n^2). Iterating through the list will need O(n), but you can not just add each element to the hash table (sets are implemented using hashtables). In the worst case, our hash function will hash each element to the same value, thus adding each element to the hash set is not O(1). In such a case, we need to add each element to a linked list - (note that hash sets have a linked list in case of collision). When adding to the linked list we need to make sure that the element doesn't already exist (as a Set by definition doesn't have duplicates). To do that we need to iterate through the same linked list for each element, which takes a total of n*(n-1)/2 = O(n^2).
Running time for converting list to set in Python - Stack Overflow
Algorithmic complexity to convert a set to a list in python - Stack Overflow
python - Why is converting a list to a set faster than using just list to compute a list difference? - Stack Overflow
python - Time complexity for function converting list to set and vice versa - Stack Overflow
What is the average time complexity for converting a list to a set in Python?
What causes the time complexity to become O(n^2) when converting a list to a set?
Converting a list to a set requires that every item in the list be visited once, O(n). Inserting an element into a set is O(1), so the overall time complexity would be O(n).
Space required for the new set is less than or equal to the length of the list, so that is also O(n).
Here's a good reference for Python data structures.
You have to iterate through the entire list, which is O(n) time, and then insert each into a set, which is O(1) time. So the overall time complexity is O(n), where n is the length of the list.
No other space other than the set being created or the list being used is needed.
You can easily see this with a simple benchmark:
import matplotlib.pyplot as plt
x = list(range(10, 20000, 20))
y = []
for n in x:
s = set(range(n))
res = %timeit -r2 -n2 -q -o list(s)
y.append(res.best)
plt.plot(x, y)

Which clearly shows a linear relationship -- modulo some noise.
(EDITED as the first version was benchmarking something different).
The time complexity in most cases will be O(n) where n is the size of the set, because:
- The set is implemented as a hashtable whose underlying array size is bounded by a fixed multiple of the set's size. Iterating over the set is done by iterating over the underlying array, so it takes O(n) time.
- Appending an item to a list takes O(1) amortized time, even if the list's underlying array is not originally allocated to be large enough for the whole set; so appending n items to an empty list takes O(n) time.
However, there is a caveat to this, which is that Python's sets have underlying array sizes based on the largest size the set object has had, not necessarily based on its current size; this is because the underlying array is not re-allocated to a smaller size when elements are removed from the set. If a set is small but used to be much larger, then iterating over it can be slower than O(n).
There is overhead to convert a list to a set, but a set is substantially faster than a list for those in tests.
You can instantly see if item x is in set y because there's a hash table being used underneath. No matter how large your set is, the lookup time is the same (basically instantaneous) - this is known in Big-O notation as O(1). For a list, you have to individually check every element to see if item x is in list z. As your list grows, the check will take longer - this is O(n), meaning the length of the operation is directly tied to how long the list is.
That increased speed can offset the set creation overhead, which is how your set check ends up being faster.
EDIT: to answer that other question, Python has no way of determining that your list is sorted - not if you're using a standard list object, anyway. So it can't achieve O(log n) performance with a list comprehension. If you wanted to write your own binary search method which assumes the list is sorted, you can certainly do so, but O(1) beats O(log n) any day.
EDIT 2:
I'm aware that a set's average O(1) lookup time beats that of a list's O(n) but if the original list A contains about a million or so integers, wouldn't the set creation actually take longer?
No, not at all. Creating a set out of a list is a O(n) operation, as inserting an item into a set is O(1) and you're doing that n times. If you have a list with a million integers in it, converting it into a set involves two O(n) steps, while repeatedly scanning the list is going to be n O(n) steps. In practice, creating the set is going to be about 250,000 times faster for a list with a million integers, and the speed difference will grow larger and larger the more items you have in your list.
According to the Python documentation on time complexity
- List membership
x in sis on average linear-time operation, orO(n). - Set membership
x in sis on average constant-time operation, orO(1).
Building a set is worst-case linear-time operation, because one would need to scan all the elements in a list to build a hash-table, so O(n). n is number of elements in a collection.
The key observation is that, in Method 1, building a set, s = set(B) is just a one-off operation, then after that we just have n total number of set-membership test as in x not in B, so in total O(n) + n * O(1), or O(n) time complexity.
Whereas in Method 2, the list-membership test x not in B is carried out for each element in A, so in total n * O(n) = O(n^2) time complexity.
One set conversion is unnecessary, as set.difference works with any iterable as it should:
final = list(set(list1).difference(list2))
But the asymptotic time and space complexity of the whole thing is still O(m+n) (the sizes of the two lists).
In both versions you are doing:
- convert a list to a set
- convert another list to a set
- subtract the two sets
- convert the resultant set to a list
Each of those is O(n), where n is a bound on the size of both your starting lists.
That's O(n) four times, which makes it overall O(n).
Your two versions are essentially identical.