Skip to content

Commit ea97258

Browse files
committed
feat(dsa): find duplicate using fast and slow pointers
1 parent 04b6cf0 commit ea97258

File tree

9 files changed

+226
-103
lines changed

9 files changed

+226
-103
lines changed

algorithms/fast_and_slow/__init__.py

Whitespace-only changes.

puzzles/find_duplicate/README.md renamed to algorithms/fast_and_slow/find_duplicate/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ No number repeats itself in the array [1, 2, 3]
3535
Explanation 3:
3636
1 and 4 repeats itself in the array [3, 4, 1, 4, 1], we can return 1 or 4
3737
```
38+
39+
> Note: You cannot modify the given array nums. You have to solve the problem using only constant extra space.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from typing import List
2+
3+
4+
def find_duplicate_floyd_algo(numbers: List[int]) -> int:
5+
"""
6+
Finds the duplicate number in a list of integers.
7+
8+
This code implements Floyd's Tortoise and Hare algorithm to find the duplicate number in a list of integers. It
9+
uses two pointers, slow and fast, that move at different speeds through the list, eventually meeting at the
10+
duplicate number. The algorithm assumes that the list contains a duplicate number and that the numbers in the list
11+
are indices that point to other numbers in the list.
12+
13+
Args:
14+
numbers (list): list of integers
15+
Returns:
16+
int: duplicated integer
17+
"""
18+
if not numbers:
19+
raise ValueError("List is empty")
20+
21+
if not all(isinstance(num, int) for num in numbers):
22+
raise ValueError("List contains non-integer values")
23+
24+
if len(numbers) == len(set(numbers)):
25+
return -1
26+
27+
if len(numbers) <= 1:
28+
return -1
29+
30+
slow = numbers[0]
31+
fast = numbers[numbers[0]]
32+
33+
while slow != fast:
34+
slow = numbers[slow]
35+
fast = numbers[numbers[fast]]
36+
37+
fast = 0
38+
while fast != slow:
39+
slow = numbers[slow]
40+
fast = numbers[fast]
41+
42+
return slow
43+
44+
45+
def find_duplicate(numbers: List[int]) -> int:
46+
"""
47+
Finds the duplicate number in a list of integers.
48+
@param numbers: List of integers
49+
@return: duplicated integer
50+
"""
51+
if not numbers:
52+
raise ValueError("List is empty")
53+
54+
if not all(isinstance(num, int) for num in numbers):
55+
raise ValueError("List contains non-integer values")
56+
57+
if len(numbers) == len(set(numbers)):
58+
return -1
59+
60+
if len(numbers) <= 1:
61+
return -1
62+
63+
for i in range(len(numbers)):
64+
idx = abs(numbers[i])
65+
if numbers[idx] < 0:
66+
return abs(numbers[i])
67+
else:
68+
numbers[idx] = -numbers[idx]
69+
70+
return -1
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import unittest
2+
3+
from . import find_duplicate, find_duplicate_floyd_algo
4+
5+
6+
class FindDuplicateTestCases(unittest.TestCase):
7+
def test_1(self):
8+
"""nums = [3, 4, 1, 4, 2] should return 4"""
9+
nums = [3, 4, 1, 4, 2]
10+
expected = 4
11+
actual = find_duplicate(nums)
12+
13+
self.assertEqual(expected, actual)
14+
15+
def test_2(self):
16+
"""nums = [1, 2, 3] should return -1"""
17+
nums = [1, 2, 3]
18+
expected = -1
19+
actual = find_duplicate(nums)
20+
21+
self.assertEqual(expected, actual)
22+
23+
def test_3(self):
24+
"""nums = [3, 4, 1, 4, 1] should return 1 or 4"""
25+
nums = [3, 4, 1, 4, 1]
26+
expected_1 = 1
27+
expected_4 = 4
28+
29+
actual = find_duplicate(nums)
30+
31+
self.assertIn(actual, [expected_1, expected_4])
32+
33+
34+
class FindDuplicateWithFastAndSlowPointersTestCases(unittest.TestCase):
35+
def test_1(self):
36+
"""nums = [3, 4, 1, 4, 2] should return 4"""
37+
nums = [3, 4, 1, 4, 2]
38+
expected = 4
39+
actual = find_duplicate_floyd_algo(nums)
40+
41+
self.assertEqual(expected, actual)
42+
43+
# @unittest.skip("Failing due to IndexError. Needs further investigation")
44+
def test_2(self):
45+
"""nums = [1, 2, 3] should return -1"""
46+
nums = [1, 2, 3]
47+
expected = -1
48+
actual = find_duplicate_floyd_algo(nums)
49+
50+
self.assertEqual(expected, actual)
51+
52+
def test_3(self):
53+
"""nums = [3, 4, 1, 4, 1] should return 1 or 4"""
54+
nums = [3, 4, 1, 4, 1]
55+
expected_1 = 1
56+
expected_4 = 4
57+
58+
actual = find_duplicate_floyd_algo(nums)
59+
60+
self.assertIn(actual, [expected_1, expected_4])
61+
62+
def test_4(self):
63+
"""nums = [1, 3, 3, 4, 2, 5] should return 3"""
64+
nums = [1, 3, 3, 4, 2, 5]
65+
expected = 3
66+
67+
actual = find_duplicate_floyd_algo(nums)
68+
69+
self.assertEqual(actual, expected)
70+
71+
def test_5(self):
72+
"""nums = [1, 5, 3, 4, 2, 5] should return 5"""
73+
nums = [1, 5, 3, 4, 2, 5]
74+
expected = 5
75+
76+
actual = find_duplicate_floyd_algo(nums)
77+
78+
self.assertEqual(actual, expected)
79+
80+
def test_6(self):
81+
"""nums = [1, 2, 3, 4, 5, 6, 6, 7] should return 6"""
82+
nums = [1, 2, 3, 4, 5, 6, 6, 7]
83+
expected = 6
84+
85+
actual = find_duplicate_floyd_algo(nums)
86+
87+
self.assertEqual(actual, expected)
88+
89+
def test_7(self):
90+
"""nums = [4, 6, 7, 7, 3, 5, 2, 8, 1] should return 7"""
91+
nums = [4, 6, 7, 7, 3, 5, 2, 8, 1]
92+
expected = 7
93+
94+
actual = find_duplicate_floyd_algo(nums)
95+
96+
self.assertEqual(actual, expected)
97+
98+
def test_8(self):
99+
"""nums = [9, 8, 7, 6, 2, 3, 5, 4, 1, 9] should return 9"""
100+
nums = [9, 8, 7, 6, 2, 3, 5, 4, 1, 9]
101+
expected = 9
102+
103+
actual = find_duplicate_floyd_algo(nums)
104+
105+
self.assertEqual(actual, expected)
106+
107+
def test_9(self):
108+
"""nums = [3, 4, 4, 4, 2] should return 4"""
109+
nums = [3, 4, 4, 4, 2]
110+
expected = 4
111+
112+
actual = find_duplicate_floyd_algo(nums)
113+
114+
self.assertEqual(actual, expected)
115+
116+
def test_10(self):
117+
"""nums = [1, 1] should return 1"""
118+
nums = [1, 1]
119+
expected = 1
120+
121+
actual = find_duplicate_floyd_algo(nums)
122+
123+
self.assertEqual(actual, expected)
124+
125+
def test_11(self):
126+
"""nums = [1,3,4,2,2] should return 2"""
127+
nums = [1,3,4,2,2]
128+
expected = 2
129+
130+
actual = find_duplicate_floyd_algo(nums)
131+
132+
self.assertEqual(actual, expected)
133+
134+
def test_12(self):
135+
"""nums = [1,3,6,2,7,3,5,4] should return 3"""
136+
nums = [1,3,6,2,7,3,5,4]
137+
expected = 3
138+
139+
actual = find_duplicate_floyd_algo(nums)
140+
141+
self.assertEqual(actual, expected)
142+
143+
def test_13(self):
144+
"""nums = [1,2,2] should return 2"""
145+
nums = [1,2,2]
146+
expected = 2
147+
148+
actual = find_duplicate_floyd_algo(nums)
149+
150+
self.assertEqual(actual, expected)
151+
152+
153+
if __name__ == "__main__":
154+
unittest.main()
File renamed without changes.
File renamed without changes.

algorithms/two_pointers/sort_patterns/test_sort_colors.py renamed to algorithms/two_pointers/sort_colors/test_sort_colors.py

File renamed without changes.

puzzles/find_duplicate/__init__.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

puzzles/find_duplicate/test_find_duplicate.py

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)