-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtwo_pointers.py
More file actions
430 lines (344 loc) · 11.8 KB
/
two_pointers.py
File metadata and controls
430 lines (344 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
"""
Two Pointers Algorithm
======================
Used for: Array/string problems, finding pairs, palindromes, sorted data
Time Complexity: O(n) - single pass through data
Space Complexity: O(1) - constant extra space
"""
from typing import List, Optional, Tuple
# ==================== BRUTE FORCE APPROACH ====================
def two_sum_brute_force(nums: List[int], target: int) -> Optional[List[int]]:
"""
Brute Force Two Sum: Check all pairs
Time Complexity: O(n²) - nested loops
Space Complexity: O(1) - no extra space
Problems:
- Quadratic time complexity
- Inefficient for large arrays
- Multiple unnecessary comparisons
"""
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]
return None
def three_sum_brute_force(nums: List[int]) -> List[List[int]]:
"""
Brute Force Three Sum: Check all triplets
Time Complexity: O(n³) - three nested loops
Space Complexity: O(1) - excluding result space
"""
n = len(nums)
result = []
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] + nums[j] + nums[k] == 0:
triplet = sorted([nums[i], nums[j], nums[k]])
if triplet not in result:
result.append(triplet)
return result
# ==================== OPTIMIZED APPROACH ====================
def two_sum_sorted(nums: List[int], target: int) -> Optional[List[int]]:
"""
Two Pointers Two Sum for sorted array
Time Complexity: O(n) - single pass
Space Complexity: O(1) - constant space
Advantages:
- Linear time complexity
- No extra space needed
- Works on sorted arrays
"""
left, right = 0, len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return None
def three_sum_optimized(nums: List[int]) -> List[List[int]]:
"""
Optimized Three Sum using sorting + two pointers
Time Complexity: O(n²) - sorting O(n log n) + O(n²) for pairs
Space Complexity: O(1) - excluding result space
"""
nums.sort()
n = len(nums)
result = []
for i in range(n - 2):
# Skip duplicates for first number
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, n - 1
while left < right:
current_sum = nums[i] + nums[left] + nums[right]
if current_sum == 0:
result.append([nums[i], nums[left], nums[right]])
# Skip duplicates
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif current_sum < 0:
left += 1
else:
right -= 1
return result
def container_with_most_water(heights: List[int]) -> int:
"""
Container with most water using two pointers
Time Complexity: O(n)
Space Complexity: O(1)
"""
left, right = 0, len(heights) - 1
max_area = 0
while left < right:
# Calculate area with current pointers
width = right - left
height = min(heights[left], heights[right])
area = width * height
max_area = max(max_area, area)
# Move pointer with smaller height
if heights[left] < heights[right]:
left += 1
else:
right -= 1
return max_area
def is_palindrome_two_pointers(s: str) -> bool:
"""
Check if string is palindrome using two pointers
Time Complexity: O(n)
Space Complexity: O(1)
"""
left, right = 0, len(s) - 1
while left < right:
# Skip non-alphanumeric characters
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
# Compare characters (case insensitive)
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
def remove_duplicates_sorted_array(nums: List[int]) -> int:
"""
Remove duplicates from sorted array in-place
Time Complexity: O(n)
Space Complexity: O(1)
"""
if not nums:
return 0
slow = 0 # Pointer for next unique position
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
def move_zeros_to_end(nums: List[int]) -> None:
"""
Move all zeros to end while maintaining relative order
Time Complexity: O(n)
Space Complexity: O(1)
"""
slow = 0 # Position for next non-zero element
# Move all non-zero elements to front
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
# Fill remaining positions with zeros
while slow < len(nums):
nums[slow] = 0
slow += 1
def partition_array(nums: List[int], pivot: int) -> int:
"""
Partition array around pivot value (Dutch National Flag style)
Time Complexity: O(n)
Space Complexity: O(1)
"""
left = 0
right = len(nums) - 1
i = 0
while i <= right:
if nums[i] < pivot:
nums[i], nums[left] = nums[left], nums[i]
left += 1
i += 1
elif nums[i] > pivot:
nums[i], nums[right] = nums[right], nums[i]
right -= 1
# Don't increment i here, need to check swapped element
else:
i += 1
return left # Return position where pivot region starts
def trapping_rain_water(heights: List[int]) -> int:
"""
Calculate trapped rainwater using two pointers
Time Complexity: O(n)
Space Complexity: O(1)
"""
if not heights:
return 0
left, right = 0, len(heights) - 1
left_max = right_max = 0
water_trapped = 0
while left < right:
if heights[left] < heights[right]:
if heights[left] >= left_max:
left_max = heights[left]
else:
water_trapped += left_max - heights[left]
left += 1
else:
if heights[right] >= right_max:
right_max = heights[right]
else:
water_trapped += right_max - heights[right]
right -= 1
return water_trapped
def longest_palindromic_substring(s: str) -> str:
"""
Find longest palindromic substring using expand around centers
Time Complexity: O(n²)
Space Complexity: O(1)
"""
if not s:
return ""
start = 0
max_len = 1
def expand_around_center(left: int, right: int) -> int:
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
for i in range(len(s)):
# Check for odd length palindromes (center at i)
len1 = expand_around_center(i, i)
# Check for even length palindromes (center between i and i+1)
len2 = expand_around_center(i, i + 1)
current_max = max(len1, len2)
if current_max > max_len:
max_len = current_max
start = i - (current_max - 1) // 2
return s[start:start + max_len]
def sort_colors_dutch_flag(nums: List[int]) -> None:
"""
Sort array of 0s, 1s, 2s using Dutch National Flag algorithm
Time Complexity: O(n)
Space Complexity: O(1)
"""
red = 0 # Boundary for 0s
white = 0 # Current position
blue = len(nums) - 1 # Boundary for 2s
while white <= blue:
if nums[white] == 0:
nums[red], nums[white] = nums[white], nums[red]
red += 1
white += 1
elif nums[white] == 1:
white += 1
else: # nums[white] == 2
nums[white], nums[blue] = nums[blue], nums[white]
blue -= 1
# Don't increment white, need to check swapped element
def find_closest_pair_sum(nums: List[int], target: int) -> Tuple[int, int]:
"""
Find pair with sum closest to target in sorted array
Time Complexity: O(n)
Space Complexity: O(1)
"""
left, right = 0, len(nums) - 1
closest_sum = float('inf')
result_pair = (0, 0)
while left < right:
current_sum = nums[left] + nums[right]
if abs(current_sum - target) < abs(closest_sum - target):
closest_sum = current_sum
result_pair = (nums[left], nums[right])
if current_sum < target:
left += 1
else:
right -= 1
return result_pair
# ==================== EXAMPLE USAGE ====================
if __name__ == "__main__":
print("=== Two Sum ===")
sorted_nums = [1, 2, 3, 4, 6, 8, 9]
print(f"Array: {sorted_nums}")
print(f"Two sum (target=10): {two_sum_sorted(sorted_nums, 10)}")
print("\n=== Three Sum ===")
nums = [-1, 0, 1, 2, -1, -4]
print(f"Array: {nums}")
print(f"Three sum (target=0): {three_sum_optimized(nums)}")
print("\n=== Container With Most Water ===")
heights = [1, 8, 6, 2, 5, 4, 8, 3, 7]
print(f"Heights: {heights}")
print(f"Max water: {container_with_most_water(heights)}")
print("\n=== Palindrome Check ===")
test_string = "A man, a plan, a canal: Panama"
print(f"String: '{test_string}'")
print(f"Is palindrome: {is_palindrome_two_pointers(test_string)}")
print("\n=== Remove Duplicates ===")
duplicate_array = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
print(f"Original: {duplicate_array}")
length = remove_duplicates_sorted_array(duplicate_array)
print(f"After removal: {duplicate_array[:length]}")
print("\n=== Trapping Rain Water ===")
heights = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
print(f"Heights: {heights}")
print(f"Trapped water: {trapping_rain_water(heights)}")
print("\n=== Longest Palindromic Substring ===")
text = "babad"
print(f"String: '{text}'")
print(f"Longest palindrome: '{longest_palindromic_substring(text)}'")
print("\n=== Sort Colors (Dutch Flag) ===")
colors = [2, 0, 2, 1, 1, 0]
print(f"Before: {colors}")
sort_colors_dutch_flag(colors)
print(f"After: {colors}")
"""
TWO POINTERS PATTERNS:
1. Opposite Direction (Most Common):
- Start from both ends, move towards center
- Two sum in sorted array
- Palindrome checking
- Container with most water
2. Same Direction (Fast/Slow):
- Both pointers start from beginning
- Remove duplicates
- Move zeros
- Cycle detection in linked lists
3. Sliding Window Variation:
- Maintain window with two pointers
- Subarray problems
- String matching
WHEN TO USE TWO POINTERS:
- Array is sorted or can be sorted
- Looking for pairs or triplets
- Need to reduce O(n²) to O(n)
- Palindrome-related problems
- Partitioning problems
ADVANTAGES:
- Reduces time complexity (often from O(n²) to O(n))
- Uses constant extra space O(1)
- Simple and intuitive approach
- No need for extra data structures
COMMON MISTAKES:
- Forgetting to handle duplicates
- Not considering edge cases (empty arrays)
- Moving wrong pointer in optimization problems
- Off-by-one errors in boundary conditions
RELATED TECHNIQUES:
- Sliding Window: Two pointers with variable distance
- Fast/Slow Pointers: Different speed movement
- Dutch National Flag: Three-way partitioning
"""