Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 37 additions & 62 deletions algorithms/array/n_sum.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,71 +57,46 @@ def _compare_closure_default(num: Any, target: Any) -> int:
def _same_closure_default(a: Any, b: Any) -> bool:
return a == b

def _n_sum_inner(n: int, nums: list[Any], target: Any) -> list[list[Any]]:
if n == 2:
results = _two_sum(nums, target)
else:
results = []
prev_num = None
for index, num in enumerate(nums):
if prev_num is not None and same_closure(prev_num, num):
continue

prev_num = num
n_minus1_results = _n_sum_inner(
n - 1,
nums[index + 1 :],
target - num,
)
n_minus1_results = _append_elem_to_each_list(num, n_minus1_results)
results += n_minus1_results
return _union(results)

def _two_sum(nums: list[Any], target: Any) -> list[list[Any]]:
nums.sort()
left = 0
right = len(nums) - 1
sum_closure = kv.get("sum_closure", _sum_closure_default)
same_closure = kv.get("same_closure", _same_closure_default)
compare_closure = kv.get("compare_closure", _compare_closure_default)

def _find_n_sum(n: int, start: int, target: Any) -> list[list[Any]]:
results = []
while left < right:
current_sum = sum_closure(nums[left], nums[right])
flag = compare_closure(current_sum, target)
if flag == -1:
left += 1
elif flag == 1:
right -= 1
else:
results.append(sorted([nums[left], nums[right]]))
left += 1
right -= 1
while left < len(nums) and same_closure(nums[left - 1], nums[left]):
if n == 2:
left = start
right = len(nums) - 1
while left < right:
current_sum = sum_closure(nums[left], nums[right])
flag = compare_closure(current_sum, target)
if flag == 0:
results.append([nums[left], nums[right]])
left += 1
while right >= 0 and same_closure(nums[right], nums[right + 1]):
right -= 1
while left < right and same_closure(nums[left], nums[left - 1]):
left += 1
while left < right and same_closure(nums[right], nums[right + 1]):
right -= 1
elif flag == -1:
left += 1
else:
right -= 1
return results

for i in range(start, len(nums) - n + 1):
if i > start and same_closure(nums[i], nums[i - 1]):
continue

# Optimization: if the smallest possible sum is greater than target, break
# This only works for numbers/types where sum is monotonic
# Since we have custom closures, we can't always assume this,
# but it's a standard optimization for the numeric case.

sub_results = _find_n_sum(n - 1, i + 1, target - nums[i])
for res in sub_results:
results.append([nums[i]] + res)

return results

def _append_elem_to_each_list(
elem: Any, container: list[list[Any]]
) -> list[list[Any]]:
results = []
for elems in container:
elems.append(elem)
results.append(sorted(elems))
return results

def _union(
duplicate_results: list[list[Any]],
) -> list[list[Any]]:
results = []
if len(duplicate_results) != 0:
duplicate_results.sort()
results.append(duplicate_results[0])
for result in duplicate_results[1:]:
if results[-1] != result:
results.append(result)
return results

sum_closure = kv.get("sum_closure", _sum_closure_default)
same_closure = kv.get("same_closure", _same_closure_default)
compare_closure = kv.get("compare_closure", _compare_closure_default)
nums.sort()
return _n_sum_inner(n, nums, target)
return _find_n_sum(n, 0, target)
37 changes: 35 additions & 2 deletions algorithms/sorting/quick_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
Quick sort selects a pivot element, partitions the array around the
pivot, and recursively sorts the two partitions.

This implementation uses Median-of-Three pivot selection and switches to
Insertion Sort for small subarrays to improve performance and avoid
O(n^2) worst-case on nearly sorted arrays.

Reference: https://en.wikipedia.org/wiki/Quicksort

Complexity:
Expand Down Expand Up @@ -33,21 +37,50 @@ def quick_sort(array: list[int]) -> list[int]:

def _quick_sort_recursive(array: list[int], first: int, last: int) -> None:
"""Recursively sort *array[first..last]* in-place."""
# Threshold for switching to insertion sort
if last - first < 10:
_insertion_sort(array, first, last)
return

if first < last:
pivot = _partition(array, first, last)
_quick_sort_recursive(array, first, pivot - 1)
_quick_sort_recursive(array, pivot + 1, last)


def _insertion_sort(array: list[int], first: int, last: int) -> None:
"""Sort *array[first..last]* in-place using insertion sort."""
for i in range(first + 1, last + 1):
key = array[i]
j = i - 1
while j >= first and array[j] > key:
array[j + 1] = array[j]
j -= 1
array[j + 1] = key


def _partition(array: list[int], first: int, last: int) -> int:
"""Partition *array[first..last]* using the last element as pivot.
"""Partition *array[first..last]* using Median-of-Three pivot.

Returns:
The final index of the pivot element.
"""
# Median-of-Three pivot selection
mid = (first + last) // 2
if array[first] > array[mid]:
array[first], array[mid] = array[mid], array[first]
if array[first] > array[last]:
array[first], array[last] = array[last], array[first]
if array[mid] > array[last]:
array[mid], array[last] = array[last], array[mid]

# Move pivot to the end
array[mid], array[last] = array[last], array[mid]
pivot = array[last]

wall = first
for pos in range(first, last):
if array[pos] < array[last]:
if array[pos] < pivot:
array[pos], array[wall] = array[wall], array[pos]
wall += 1
array[wall], array[last] = array[last], array[wall]
Expand Down
67 changes: 30 additions & 37 deletions algorithms/string/is_palindrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
from __future__ import annotations

from collections import deque
from string import ascii_letters


def is_palindrome(text: str) -> bool:
"""Check if a string is a palindrome using two pointers on the original.
This version is optimized for space (O(1)) and correctly handles strings
with no alphanumeric characters.

Args:
text: The input string to check.
Expand All @@ -29,13 +30,15 @@ def is_palindrome(text: str) -> bool:
Examples:
>>> is_palindrome("Otto")
True
>>> is_palindrome("!!!")
True
"""
left = 0
right = len(text) - 1
while left < right:
while not text[left].isalnum():
while left < right and not text[left].isalnum():
left += 1
while not text[right].isalnum():
while left < right and not text[right].isalnum():
right -= 1
if text[left].lower() != text[right].lower():
return False
Expand All @@ -50,21 +53,9 @@ def _remove_punctuation(text: str) -> str:
text: The input string to clean.

Returns:
A lowercase string with only alphabetic characters.
"""
return "".join(char.lower() for char in text if char in ascii_letters)


def _string_reverse(text: str) -> str:
"""Reverse a string using slicing.

Args:
text: The string to reverse.

Returns:
The reversed string.
A lowercase string with only alphanumeric characters.
"""
return text[::-1]
return "".join(char.lower() for char in text if char.isalnum())


def is_palindrome_reverse(text: str) -> bool:
Expand All @@ -80,8 +71,8 @@ def is_palindrome_reverse(text: str) -> bool:
>>> is_palindrome_reverse("Otto")
True
"""
text = _remove_punctuation(text)
return text == _string_reverse(text)
clean_text = _remove_punctuation(text)
return clean_text == clean_text[::-1]


def is_palindrome_two_pointer(text: str) -> bool:
Expand All @@ -98,8 +89,9 @@ def is_palindrome_two_pointer(text: str) -> bool:
True
"""
text = _remove_punctuation(text)
for index in range(0, len(text) // 2):
if text[index] != text[len(text) - index - 1]:
n = len(text)
for index in range(n // 2):
if text[index] != text[n - index - 1]:
return False
return True

Expand All @@ -117,11 +109,17 @@ def is_palindrome_stack(text: str) -> bool:
>>> is_palindrome_stack("Otto")
True
"""
stack: list[str] = []
text = _remove_punctuation(text)
for index in range(len(text) // 2, len(text)):
stack.append(text[index])
return all(text[index] == stack.pop() for index in range(0, len(text) // 2))
n = len(text)
stack = list(text[:n // 2])

# Skip middle element for odd length strings
start_second_half = n // 2 if n % 2 == 0 else n // 2 + 1

for i in range(start_second_half, n):
if text[i] != stack.pop():
return False
return True


def is_palindrome_deque(text: str) -> bool:
Expand All @@ -138,15 +136,10 @@ def is_palindrome_deque(text: str) -> bool:
True
"""
text = _remove_punctuation(text)
character_deque: deque[str] = deque()
for char in text:
character_deque.appendleft(char)

equal = True
while len(character_deque) > 1 and equal:
first = character_deque.pop()
last = character_deque.popleft()
if first != last:
equal = False

return equal
character_deque = deque(text)

while len(character_deque) > 1:
if character_deque.popleft() != character_deque.pop():
return False

return True