Skip to content

Commit 5991e05

Browse files
Optimize remove_duplicates from O(n²) to O(n) time complexity (#2700)
* Optimize remove_duplicates from O(n²) to O(n) time complexity Use a set for O(1) membership checks instead of checking membership in a list which is O(n). This reduces the overall time complexity from O(n²) to O(n). Added documentation for time and space complexity. Co-Authored-By: Keon <[email protected]> * Fix: Handle unhashable items in remove_duplicates The previous optimization broke when the function received unhashable items like lists or dicts, causing TypeError. This commit adds backward compatibility by checking if items are hashable: - Hashable items use set for O(1) lookup (fast path) - Unhashable items fall back to list membership check (preserves original behavior) This maintains the O(n) optimization for the common case while preserving backward compatibility for all input types. Co-Authored-By: Keon <[email protected]> * Fix: Apply black formatting to remove_duplicates.py Add blank lines after imports and before function definition to comply with black code formatting style, which is checked by CI. Co-Authored-By: Keon <[email protected]> * Fix: Remove unused nonlocal/global declarations (F824 errors) Remove unused nonlocal declarations in find_all_cliques.py and unused global declaration in construct_tree_postorder_preorder.py to fix flake8 F824 errors that were causing CI to fail. These declarations were unnecessary because: - In find_all_cliques: compsub and solutions are only mutated (append/pop), not reassigned, so nonlocal is not needed - In construct_tree: pre_index is never used or assigned in this function, only in construct_tree_util Also applied black formatting to both files. Co-Authored-By: Keon <[email protected]> * Fix: Resolve pre-existing test failures blocking CI Fix two pre-existing test failures that were causing CI to fail: 1. test_remove_duplicates: Added missing expected values to assertListEqual calls. The test was malformed with only input arrays but no expected outputs, causing TypeError. 2. test_summarize_ranges: Fixed summarize_ranges() to return tuples instead of strings. The function was converting tuples to formatted strings like '0-2', but tests expected tuples like (0, 2). Both fixes align implementations with test expectations and docstrings. Applied black formatting to both files. Co-Authored-By: Keon <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Keon <[email protected]>
1 parent 5b63e90 commit 5991e05

File tree

5 files changed

+321
-205
lines changed

5 files changed

+321
-205
lines changed

algorithms/arrays/remove_duplicates.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
77
Input: [1, 1 ,1 ,2 ,2 ,3 ,4 ,4 ,"hey", "hey", "hello", True, True]
88
Output: [1, 2, 3, 4, 'hey', 'hello']
9+
10+
Time Complexity: O(n) for hashable items, O(n²) worst case for unhashable items
11+
Space Complexity: O(n) for the seen set and result array
912
"""
1013

14+
from collections.abc import Hashable
15+
16+
1117
def remove_duplicates(array):
18+
seen = set()
1219
new_array = []
1320

1421
for item in array:
15-
if item not in new_array:
16-
new_array.append(item)
22+
if isinstance(item, Hashable):
23+
if item not in seen:
24+
seen.add(item)
25+
new_array.append(item)
26+
else:
27+
if item not in new_array:
28+
new_array.append(item)
1729

18-
return new_array
30+
return new_array

algorithms/arrays/summarize_ranges.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@
55
For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)].
66
"""
77

8+
from typing import List, Tuple
89

9-
from typing import List
1010

11-
def summarize_ranges(array: List[int]) -> List[str]:
11+
def summarize_ranges(array: List[int]) -> List[Tuple[int, ...]]:
1212
res = []
13+
if len(array) == 0:
14+
return []
1315
if len(array) == 1:
14-
return [str(array[0])]
16+
return [(array[0], array[0])]
1517
it = iter(array)
1618
start = end = next(it)
1719
for num in it:
1820
if num - end == 1:
1921
end = num
2022
else:
21-
res.append((start, end) if start != end else (start,))
23+
res.append((start, end))
2224
start = end = num
23-
res.append((start, end) if start != end else (start,))
24-
return [f"{r[0]}-{r[1]}" if len(r) > 1 else str(r[0]) for r in res]
25-
25+
res.append((start, end))
26+
return res

algorithms/graph/find_all_cliques.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
the subgraph there is an edge between them).
55
"""
66

7+
78
def find_all_cliques(edges):
89
"""
910
takes dict of sets
@@ -15,9 +16,7 @@ def find_all_cliques(edges):
1516
"""
1617

1718
def expand_clique(candidates, nays):
18-
nonlocal compsub
1919
if not candidates and not nays:
20-
nonlocal solutions
2120
solutions.append(compsub.copy())
2221
else:
2322
for selected in candidates.copy():

algorithms/tree/construct_tree_postorder_preorder.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,87 +21,88 @@
2121
Output: 8 4 9 2 5 1 6 3 7
2222
"""
2323

24+
2425
class TreeNode:
2526

26-
def __init__(self, val, left = None, right = None):
27+
def __init__(self, val, left=None, right=None):
2728
self.val = val
2829
self.left = left
2930
self.right = right
3031

32+
3133
pre_index = 0
32-
34+
35+
3336
def construct_tree_util(pre: list, post: list, low: int, high: int, size: int):
3437
"""
35-
Recursive function that constructs tree from preorder and postorder array.
36-
37-
preIndex is a global variable that keeps track of the index in preorder
38-
array.
39-
preorder and postorder array are represented are pre[] and post[] respectively.
40-
low and high are the indices for the postorder array.
38+
Recursive function that constructs tree from preorder and postorder array.
39+
40+
preIndex is a global variable that keeps track of the index in preorder
41+
array.
42+
preorder and postorder array are represented are pre[] and post[] respectively.
43+
low and high are the indices for the postorder array.
4144
"""
4245

4346
global pre_index
4447

4548
if pre_index == -1:
4649
pre_index = 0
47-
48-
49-
#Base case
50-
if(pre_index >= size or low > high):
50+
51+
# Base case
52+
if pre_index >= size or low > high:
5153
return None
5254

5355
root = TreeNode(pre[pre_index])
5456
pre_index += 1
5557

56-
#If only one element in the subarray return root
57-
if(low == high or pre_index >= size):
58+
# If only one element in the subarray return root
59+
if low == high or pre_index >= size:
5860
return root
5961

60-
#Find the next element of pre[] in post[]
62+
# Find the next element of pre[] in post[]
6163
i = low
6264
while i <= high:
63-
if(pre[pre_index] == post[i]):
65+
if pre[pre_index] == post[i]:
6466
break
6567

6668
i += 1
6769

68-
#Use index of element present in postorder to divide postorder array
69-
#to two parts: left subtree and right subtree
70-
if(i <= high):
70+
# Use index of element present in postorder to divide postorder array
71+
# to two parts: left subtree and right subtree
72+
if i <= high:
7173
root.left = construct_tree_util(pre, post, low, i, size)
72-
root.right = construct_tree_util(pre, post, i+1, high, size)
74+
root.right = construct_tree_util(pre, post, i + 1, high, size)
7375

7476
return root
7577

7678

7779
def construct_tree(pre: list, post: list, size: int):
7880
"""
79-
Main Function that will construct the full binary tree from given preorder
80-
and postorder array.
81+
Main Function that will construct the full binary tree from given preorder
82+
and postorder array.
8183
"""
8284

83-
global pre_index
84-
root = construct_tree_util(pre, post, 0, size-1, size)
85+
root = construct_tree_util(pre, post, 0, size - 1, size)
8586

8687
return print_inorder(root)
8788

8889

89-
90-
def print_inorder(root: TreeNode, result = None):
90+
def print_inorder(root: TreeNode, result=None):
9191
"""
92-
Prints the tree constructed in inorder format
92+
Prints the tree constructed in inorder format
9393
"""
9494
if root is None:
9595
return []
96-
if result is None:
96+
if result is None:
9797
result = []
98-
98+
9999
print_inorder(root.left, result)
100100
result.append(root.val)
101101
print_inorder(root.right, result)
102102
return result
103103

104-
if __name__ == '__main__':
104+
105+
if __name__ == "__main__":
105106
pre = [1, 2, 4, 5, 3, 6, 7]
106107
post = [4, 5, 2, 6, 7, 3, 1]
107108
size = len(pre)

0 commit comments

Comments
 (0)