Skip to content

Commit cc52cb5

Browse files
committed
add python solutions for fast and slow pointers pattern
1 parent 6c2aa45 commit cc52cb5

File tree

4 files changed

+558
-0
lines changed

4 files changed

+558
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
2+
# Find the Duplicate Number
3+
You are given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive. There is exactly one duplicate number in nums, return this duplicate number.
4+
5+
You must solve the problem without modifying the array and use only constant extra space.
6+
7+
### Constraints:
8+
- 1 <= n <= 10^5
9+
- nums.length == n + 1
10+
- 1 <= nums[i] <= n
11+
- All the integers in nums appear only once except for exactly one integer which appears twice.
12+
13+
### Follow-up:
14+
- How can we prove that at least one duplicate number must exist?
15+
- Can you solve the problem in O(n) time and without modifying the array?
16+
17+
### Examples
18+
```javascript
19+
Input: nums = [1,3,4,2,2]
20+
Output: 2
21+
22+
Input: nums = [3,1,3,4,2]
23+
Output: 3
24+
```
25+
26+
## Approaches to Solve the Problem
27+
### Approach 1: Sort the Array (Not Optimal)
28+
##### Intuition:
29+
One of the simplest approaches is to sort the array. Once the array is sorted, the duplicate number will appear next to itself.
30+
31+
1. Sort the array.
32+
2. Iterate through the sorted array, checking if any two consecutive elements are the same.
33+
##### Time Complexity:
34+
O(n log n), because sorting the array takes O(n log n).
35+
##### Space Complexity:
36+
O(1), if sorting is done in place, or O(n) if using additional memory for sorting.
37+
##### Python Code:
38+
```python
39+
def findDuplicate(nums):
40+
nums.sort() # Sort the array
41+
42+
# Find the first pair of consecutive duplicate elements
43+
for i in range(1, len(nums)):
44+
if nums[i] == nums[i - 1]:
45+
return nums[i]
46+
```
47+
### Approach 2: Hash Set to Track Seen Numbers
48+
##### Intuition:
49+
We can use a hash set to keep track of numbers we have already encountered. As we iterate through the array, if a number is found in the set, it is the duplicate.
50+
51+
1. Initialize an empty set.
52+
2. Traverse the array and check if the number is already in the set.
53+
3. If it is, return the duplicate number.
54+
##### Time Complexity:
55+
O(n), as we traverse the array once.
56+
##### Space Complexity:
57+
O(n), because we use a set to store the numbers we have seen.
58+
##### Python Code:
59+
```python
60+
def findDuplicate(nums):
61+
seen = set() # Set to track seen numbers
62+
63+
for num in nums:
64+
if num in seen:
65+
return num # Duplicate found
66+
seen.add(num)
67+
```
68+
### Approach 3: Binary Search on the Value Range
69+
##### Intuition:
70+
The key insight is that the array contains numbers in the range [1, n]. This allows us to apply binary search on the range of values instead of the array itself. For each mid-point of the range, count how many numbers in the array are less than or equal to mid. If this count exceeds mid, then the duplicate must be in the lower half. Otherwise, it must be in the upper half.
71+
72+
1. Perform binary search on the range of numbers [1, n].
73+
2. For each mid value, count how many numbers in the array are less than or equal to mid.
74+
3. Based on the count, adjust the search range.
75+
##### Visualization:
76+
```rust
77+
For example, with nums = [1, 3, 4, 2, 2], we perform the following:
78+
79+
- Initial range: 1 to 4 (n=4)
80+
- Mid = 2. Count of numbers <= 2 is 3 (1, 2, 2).
81+
Since 3 > 2, the duplicate is in the lower half.
82+
- Narrow down the range to 1 to 2.
83+
- Mid = 1. Count of numbers <= 1 is 1.
84+
The duplicate must be in the upper half.
85+
- Range becomes 2 to 2. Found duplicate = 2.
86+
```
87+
##### Time Complexity:
88+
O(n log n), because we perform binary search on the range and for each mid-point, we do a linear scan of the array.
89+
##### Space Complexity:
90+
O(1), since no extra space is used except for a few variables.
91+
##### Python Code:
92+
```python
93+
def findDuplicate(nums):
94+
left, right = 1, len(nums) - 1
95+
96+
while left < right:
97+
mid = (left + right) // 2
98+
count = sum(num <= mid for num in nums)
99+
100+
if count > mid:
101+
right = mid # Duplicate is in the lower half
102+
else:
103+
left = mid + 1 # Duplicate is in the upper half
104+
105+
return left # The duplicate number
106+
```
107+
### Approach 4: Floyd’s Tortoise and Hare (Cycle Detection)
108+
##### Intuition:
109+
This approach treats the problem as detecting a cycle in a linked list. Imagine the array as a linked list where each element points to another element in the array (the value at that index). The duplicate number creates a cycle. Using Floyd's Tortoise and Hare algorithm, we can detect this cycle.
110+
111+
1. Initialize two pointers, slow and fast.
112+
2. Move slow one step at a time and fast two steps at a time.
113+
3. If a cycle exists, slow and fast will meet at some point.
114+
4. Reset one pointer to the start and move both pointers one step at a time until they meet again, which will be at the duplicate number.
115+
##### Why This Works:
116+
- The duplicate number creates a cycle because it points to a number already visited. The cycle detection method will find the duplicate as the entry point of the cycle.
117+
##### Visualization:
118+
```rust
119+
Array: [1, 3, 4, 2, 2]
120+
121+
1 -> 3 -> 2 -> 4 -> 2 (cycle starts at 2)
122+
123+
Step 1:
124+
- slow = 1, fast = 3
125+
126+
Step 2:
127+
- slow = 3, fast = 4
128+
129+
Step 3:
130+
- slow = 2, fast = 2 (they meet)
131+
132+
Reset slow to the start, then move both pointers one step at a time:
133+
- slow = 1, fast = 2
134+
- slow = 3, fast = 2
135+
- slow = 2, fast = 2 (found duplicate = 2)
136+
```
137+
##### Time Complexity:
138+
O(n), as we traverse the array once.
139+
##### Space Complexity:
140+
O(1), since we only use constant extra space.
141+
##### Python Code:
142+
```python
143+
def findDuplicate(nums):
144+
slow = nums[0]
145+
fast = nums[0]
146+
147+
# First phase: detect the cycle
148+
while True:
149+
slow = nums[slow]
150+
fast = nums[nums[fast]]
151+
if slow == fast:
152+
break
153+
154+
# Second phase: find the entrance to the cycle
155+
slow = nums[0]
156+
while slow != fast:
157+
slow = nums[slow]
158+
fast = nums[fast]
159+
160+
return slow # The duplicate number
161+
```
162+
## Summary
163+
164+
| Approach | Time Complexity | Space Complexity |
165+
|-----------------------------------|-----------------|------------------|
166+
| Sort the Array | O(n log n) | O(1) |
167+
| Hash Set | O(n) | O(n) |
168+
| Binary Search on Value Range | O(n log n) | O(1) |
169+
| Floyd’s Tortoise and Hare | O(n) | O(1) |
170+
171+
The Floyd’s Tortoise and Hare approach is the most optimal, providing O(n) time complexity and O(1) space complexity without modifying the array

python/happy-number.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
2+
# Happy Number
3+
Write an algorithm to determine if a number n is a "happy number."
4+
5+
A happy number is defined by the following process:
6+
7+
- Starting with any positive integer, replace the number by the sum of the squares of its digits.
8+
- Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle that does not include 1.
9+
- Those numbers for which this process ends in 1 are happy numbers.
10+
11+
### Constraints:
12+
1 <= n <= 2^31 - 1
13+
14+
### Examples
15+
```javascript
16+
Input: n = 19
17+
Output: true
18+
Explanation:
19+
1² + 9² = 82
20+
8² + 2² = 68
21+
6² + 8² = 100
22+
1² + 0² + 0² = 1
23+
24+
Input: n = 2
25+
Output: false
26+
```
27+
28+
## Approaches to Solve the Problem
29+
### Approach 1: Hash Set to Track Seen Numbers
30+
##### Intuition:
31+
One way to detect a cycle is by keeping track of all numbers we've already seen. If we see the same number again, it means we are in a cycle, and the number is not happy. If the process eventually reaches 1, it is a happy number.
32+
33+
1. Initialize an empty set to store previously seen numbers.
34+
2. Repeatedly compute the sum of the squares of the digits of the number.
35+
3. If the sum equals 1, return True.
36+
4. If the sum is already in the set, return False (indicating a cycle).
37+
5. Add the sum to the set and repeat.
38+
##### Time Complexity:
39+
O(log n), where n is the input number. We break down the number into digits and repeatedly compute their squares.
40+
##### Space Complexity:
41+
O(log n), since we store previously seen numbers in a set, which can grow based on the number of digits of n.
42+
##### Python Code:
43+
```python
44+
def isHappy(n: int) -> bool:
45+
def sum_of_squares(num):
46+
total = 0
47+
while num:
48+
digit = num % 10 # Extract the last digit
49+
total += digit ** 2 # Add square of the digit to total
50+
num //= 10 # Remove the last digit
51+
return total
52+
53+
seen = set() # Set to store numbers we have seen
54+
55+
while n != 1 and n not in seen:
56+
seen.add(n) # Add current number to the seen set
57+
n = sum_of_squares(n) # Compute the sum of squares of digits
58+
59+
return n == 1 # Return True if 1 is reached, else False
60+
```
61+
### Approach 2: Two Pointers (Floyd’s Cycle Detection Algorithm)
62+
##### Intuition:
63+
A more efficient way to detect a cycle is by using two pointers—slow and fast. This is similar to Floyd's Cycle Detection algorithm (used for detecting cycles in linked lists). The slow pointer moves one step at a time (computing the sum of squares once), while the fast pointer moves two steps at a time. If there is a cycle, the two pointers will eventually meet. If the fast pointer reaches 1, then the number is happy.
64+
65+
1. Initialize two pointers slow and fast at n.
66+
2. Move slow by one step (compute sum of squares once).
67+
3. Move fast by two steps (compute sum of squares twice).
68+
4. If slow and fast meet, there is a cycle (return False).
69+
5. If fast reaches 1, return True.
70+
##### Why This Works:
71+
- If a cycle exists, the slow and fast pointers will meet within the cycle. If the number is happy, the fast pointer will eventually reach 1.
72+
##### Time Complexity:
73+
O(log n), since the sum of the squares of digits is computed repeatedly.
74+
##### Space Complexity:
75+
O(1), since only a constant amount of space is used for the pointers.
76+
##### Visualization:
77+
```rust
78+
Example: n = 19
79+
80+
Initial: slow = 19, fast = 19
81+
82+
Step 1: slow = sum_of_squares(19) = 82
83+
fast = sum_of_squares(sum_of_squares(19)) = sum_of_squares(82) = 68
84+
85+
Step 2: slow = sum_of_squares(82) = 68
86+
fast = sum_of_squares(sum_of_squares(68)) = sum_of_squares(100) = 1
87+
88+
Since fast reached 1, the number is happy (return True).
89+
```
90+
##### Python Code:
91+
```python
92+
def isHappy(n: int) -> bool:
93+
def sum_of_squares(num):
94+
total = 0
95+
while num:
96+
digit = num % 10
97+
total += digit ** 2
98+
num //= 10
99+
return total
100+
101+
slow = n
102+
fast = sum_of_squares(n)
103+
104+
while fast != 1 and slow != fast:
105+
slow = sum_of_squares(slow) # Slow pointer moves one step
106+
fast = sum_of_squares(sum_of_squares(fast)) # Fast pointer moves two steps
107+
108+
return fast == 1 # If fast reaches 1, it's a happy number
109+
```
110+
### Approach 3: Recursive Approach
111+
##### Intuition:
112+
We can also implement the solution recursively. At each step, compute the sum of squares of the digits and recursively check if this number is happy. To detect cycles, we use a set to keep track of previously seen numbers.
113+
114+
1. Create a recursive function to compute the sum of squares.
115+
2. If the current number is 1, return True.
116+
3. If the number is in the set, return False (cycle detected).
117+
4. Add the number to the set and continue the process.
118+
##### Time Complexity:
119+
O(log n), since the number is broken down into its digits recursively.
120+
##### Space Complexity:
121+
O(log n), because of the recursion depth and the set used to track seen numbers.
122+
##### Python Code:
123+
```python
124+
def isHappy(n: int, seen=None) -> bool:
125+
if seen is None:
126+
seen = set() # Set to track seen numbers
127+
128+
if n == 1:
129+
return True # Base case: if n is 1, it's a happy number
130+
if n in seen:
131+
return False # If n is already seen, it's a cycle
132+
133+
seen.add(n) # Add current number to the seen set
134+
sum_of_squares = sum(int(digit) ** 2 for digit in str(n)) # Compute sum of squares
135+
136+
return isHappy(sum_of_squares, seen) # Recurse with the new number
137+
```
138+
## Summary
139+
140+
| Approach | Time Complexity | Space Complexity |
141+
|-----------------------------------|-----------------|------------------|
142+
| Hash Set (Tracking Seen Numbers) | O(log n) | O(log n) |
143+
| Two Pointers (Floyd’s Algorithm) | O(log n) | O(1) |
144+
| Recursive Approach | O(logn) | O(log n) |
145+
146+
The Two Pointers (Floyd’s Algorithm) is the most optimal solution as it uses constant space and efficiently detects cycles.

0 commit comments

Comments
 (0)