Skip to content

Commit fb0f681

Browse files
authored
Merge pull request #2718 from keon/feature/community-algorithms
Add 15 community-contributed algorithms
2 parents 914b481 + f6ad224 commit fb0f681

25 files changed

Lines changed: 1059 additions & 0 deletions

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
163163
| Graph | `graph.py` | `Node`, `DirectedEdge`, `DirectedGraph` |
164164
| Hash Table | `hash_table.py` | `HashTable`, `ResizableHashTable` |
165165
| Heap | `heap.py` | `BinaryHeap` |
166+
| KD Tree | `kd_tree.py` | `KDTree` |
166167
| Linked List | `linked_list.py` | `SinglyLinkedListNode`, `DoublyLinkedListNode` |
167168
| Priority Queue | `priority_queue.py` | `PriorityQueue` |
168169
| Queue | `queue.py` | `ArrayQueue`, `LinkedListQueue` |
@@ -213,6 +214,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
213214
- [permute](algorithms/backtracking/permute.py) — generate all permutations of distinct elements
214215
- [permute_unique](algorithms/backtracking/permute_unique.py) — generate unique permutations when duplicates exist
215216
- [subsets](algorithms/backtracking/subsets.py) — generate all subsets (power set)
217+
- [minimax](algorithms/backtracking/minimax.py) — game-tree search with alpha-beta pruning
216218
- [subsets_unique](algorithms/backtracking/subsets_unique.py) — generate unique subsets when duplicates exist
217219

218220
### Bit Manipulation
@@ -226,6 +228,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
226228
- [find_difference](algorithms/bit_manipulation/find_difference.py) — find the added character between two strings using XOR
227229
- [find_missing_number](algorithms/bit_manipulation/find_missing_number.py) — find a missing number in a sequence using XOR
228230
- [flip_bit_longest_sequence](algorithms/bit_manipulation/flip_bit_longest_sequence.py) — longest run of 1s after flipping a single 0
231+
- [gray_code](algorithms/bit_manipulation/gray_code.py) — generate Gray code sequences and convert between Gray and binary
229232
- [has_alternative_bit](algorithms/bit_manipulation/has_alternative_bit.py) — check if binary representation has alternating bits
230233
- [insert_bit](algorithms/bit_manipulation/insert_bit.py) — insert bits at a specific position in an integer
231234
- [power_of_two](algorithms/bit_manipulation/power_of_two.py) — check if an integer is a power of two
@@ -245,10 +248,12 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
245248

246249
### Dynamic Programming
247250

251+
- [bitmask](algorithms/dynamic_programming/bitmask.py) — travelling salesman problem via bitmask dynamic programming
248252
- [buy_sell_stock](algorithms/dynamic_programming/buy_sell_stock.py) — maximize profit from a stock price array
249253
- [climbing_stairs](algorithms/dynamic_programming/climbing_stairs.py) — count ways to climb stairs taking 1 or 2 steps
250254
- [coin_change](algorithms/dynamic_programming/coin_change.py) — minimum coins to make a given amount
251255
- [combination_sum](algorithms/dynamic_programming/combination_sum.py) — count combinations that sum to a target (with reuse)
256+
- [count_paths_dp](algorithms/dynamic_programming/count_paths_dp.py) — count paths in a grid using recursion, memoization, and bottom-up DP
252257
- [edit_distance](algorithms/dynamic_programming/edit_distance.py) — minimum edits to transform one string into another
253258
- [egg_drop](algorithms/dynamic_programming/egg_drop.py) — minimize trials to find the critical floor
254259
- [fibonacci](algorithms/dynamic_programming/fib.py) — compute Fibonacci numbers with memoization
@@ -276,6 +281,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
276281
- [all_factors](algorithms/graph/all_factors.py) — find all factor combinations of a number
277282
- [all_pairs_shortest_path](algorithms/graph/all_pairs_shortest_path.py) — Floyd-Warshall all-pairs shortest paths
278283
- [bellman_ford](algorithms/graph/bellman_ford.py) — single-source shortest path with negative edge weights
284+
- [blossom](algorithms/graph/blossom.py) — Edmonds' blossom algorithm for maximum matching in general graphs
279285
- [check_bipartite](algorithms/graph/check_bipartite.py) — determine if a graph is two-colorable
280286
- [check_digraph_strongly_connected](algorithms/graph/check_digraph_strongly_connected.py) — check if a directed graph is strongly connected
281287
- [clone_graph](algorithms/graph/clone_graph.py) — deep-copy an undirected graph
@@ -371,7 +377,9 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
371377
- [hailstone](algorithms/math/hailstone.py) — Collatz conjecture (hailstone) sequence
372378
- [is_strobogrammatic](algorithms/math/is_strobogrammatic.py) — check if a number looks the same upside-down
373379
- [krishnamurthy_number](algorithms/math/krishnamurthy_number.py) — check if a number equals the sum of the factorials of its digits
380+
- [linear_regression](algorithms/math/linear_regression.py) — ordinary least-squares linear regression with R² and RMSE
374381
- [magic_number](algorithms/math/magic_number.py) — check if a number is a magic number
382+
- [manhattan_distance](algorithms/math/manhattan_distance.py) — compute Manhattan (L1) distance between two points in any dimension
375383
- [modular_exponential](algorithms/math/modular_exponential.py) — compute (base^exp) mod m efficiently
376384
- [modular_inverse](algorithms/math/modular_inverse.py) — compute the modular multiplicative inverse
377385
- [next_bigger](algorithms/math/next_bigger.py) — next larger number with the same digits
@@ -380,6 +388,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
380388
- [num_digits](algorithms/math/num_digits.py) — count the number of digits in an integer
381389
- [num_perfect_squares](algorithms/math/num_perfect_squares.py) — minimum perfect squares that sum to n
382390
- [polynomial](algorithms/math/polynomial.py) — polynomial and monomial arithmetic operations
391+
- [polynomial_division](algorithms/math/polynomial_division.py) — polynomial long division returning quotient and remainder
383392
- [power](algorithms/math/power.py) — compute x^n via binary exponentiation
384393
- [prime_check](algorithms/math/prime_check.py) — check if a number is prime
385394
- [primes_sieve_of_eratosthenes](algorithms/math/primes_sieve_of_eratosthenes.py) — generate primes up to n using the Sieve of Eratosthenes
@@ -421,6 +430,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
421430
### Searching
422431

423432
- [binary_search](algorithms/searching/binary_search.py) — search a sorted array in O(log n)
433+
- [exponential_search](algorithms/searching/exponential_search.py) — search a sorted array by doubling the range then binary searching
424434
- [find_min_rotate](algorithms/searching/find_min_rotate.py) — find the minimum in a rotated sorted array
425435
- [first_occurrence](algorithms/searching/first_occurrence.py) — find the first occurrence of a target value
426436
- [generalized_binary_search](algorithms/searching/generalized_binary_search.py) — binary search with a custom predicate
@@ -432,6 +442,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
432442
- [search_insert](algorithms/searching/search_insert.py) — find the insertion position for a target value
433443
- [search_range](algorithms/searching/search_range.py) — find the first and last positions of a target
434444
- [search_rotate](algorithms/searching/search_rotate.py) — search in a rotated sorted array
445+
- [sentinel_search](algorithms/searching/sentinel_search.py) — linear search optimized by placing a sentinel at the end
435446
- [ternary_search](algorithms/searching/ternary_search.py) — search by dividing the array into three parts
436447
- [two_sum](algorithms/searching/two_sum.py) — find two numbers that sum to a target
437448

@@ -488,6 +499,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
488499
### String
489500

490501
- [add_binary](algorithms/string/add_binary.py) — add two binary number strings
502+
- [alphabet_board_path](algorithms/string/alphabet_board_path.py) — navigate a 5×5 alphabet board to spell a target word
491503
- [atbash_cipher](algorithms/string/atbash_cipher.py) — Atbash substitution cipher (reverse the alphabet)
492504
- [breaking_bad](algorithms/string/breaking_bad.py) — spell a string using periodic-table element symbols
493505
- [caesar_cipher](algorithms/string/caesar_cipher.py) — Caesar shift cipher encryption / decryption
@@ -510,6 +522,7 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
510522
- [longest_common_prefix](algorithms/string/longest_common_prefix.py) — find the longest common prefix among strings
511523
- [longest_palindromic_substring](algorithms/string/longest_palindromic_substring.py) — find the longest palindromic substring
512524
- [make_sentence](algorithms/string/make_sentence.py) — break a string into valid dictionary words
525+
- [manacher](algorithms/string/manacher.py) — find the longest palindromic substring in O(n) time
513526
- [merge_string_checker](algorithms/string/merge_string_checker.py) — check if a string is a valid merge of two others
514527
- [min_distance](algorithms/string/min_distance.py) — minimum deletions to make two strings equal
515528
- [multiply_strings](algorithms/string/multiply_strings.py) — multiply two numbers represented as strings
@@ -524,11 +537,13 @@ All core data structures live in [`algorithms/data_structures/`](algorithms/data
524537
- [roman_to_int](algorithms/string/roman_to_int.py) — convert a Roman numeral string to an integer
525538
- [rotate](algorithms/string/rotate.py) — rotate a string by k positions
526539
- [strip_url_params](algorithms/string/strip_url_params.py) — remove duplicate query parameters from a URL
540+
- [swap_characters](algorithms/string/swap_characters.py) — check if one character swap can make two strings equal
527541
- [strong_password](algorithms/string/strong_password.py) — check minimum changes needed for a strong password
528542
- [text_justification](algorithms/string/text_justification.py) — justify text lines to a specified width
529543
- [unique_morse](algorithms/string/unique_morse.py) — count unique Morse code representations of words
530544
- [validate_coordinates](algorithms/string/validate_coordinates.py) — validate geographic latitude/longitude coordinates
531545
- [word_squares](algorithms/string/word_squares.py) — find all valid word squares from a word list
546+
- [z_algorithm](algorithms/string/z_algorithm.py) — Z-array computation for linear-time pattern matching
532547

533548
### Tree
534549

algorithms/backtracking/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .generate_abbreviations import generate_abbreviations
1111
from .generate_parenthesis import generate_parenthesis_v1, generate_parenthesis_v2
1212
from .letter_combination import letter_combinations
13+
from .minimax import minimax
1314
from .palindrome_partitioning import (
1415
palindromic_substrings,
1516
palindromic_substrings_iter,
@@ -43,4 +44,5 @@
4344
"subsets",
4445
"subsets_v2",
4546
"subsets_unique",
47+
"minimax",
4648
]

algorithms/backtracking/minimax.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Minimax — game-tree search with alpha-beta pruning.
2+
3+
The minimax algorithm finds the optimal move for a two-player zero-sum
4+
game. Alpha-beta pruning reduces the search space by eliminating branches
5+
that cannot influence the final decision.
6+
7+
Inspired by PR #860 (DD2480-group16).
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import math
13+
14+
15+
def minimax(
16+
depth: int,
17+
is_maximizing: bool,
18+
scores: list[int],
19+
alpha: float = -math.inf,
20+
beta: float = math.inf,
21+
) -> float:
22+
"""Return the minimax value of a perfect binary game tree.
23+
24+
*scores* contains the leaf values (length must be a power of 2).
25+
*depth* is the current depth (start with log2(len(scores))).
26+
27+
>>> minimax(2, True, [3, 5, 2, 9])
28+
5
29+
>>> minimax(3, True, [3, 5, 2, 9, 12, 5, 23, 23])
30+
12
31+
"""
32+
if depth == 0:
33+
return scores[0]
34+
mid = len(scores) // 2
35+
if is_maximizing:
36+
value = -math.inf
37+
value = max(
38+
value,
39+
minimax(depth - 1, False, scores[:mid], alpha, beta),
40+
)
41+
alpha = max(alpha, value)
42+
if alpha < beta:
43+
value = max(
44+
value,
45+
minimax(depth - 1, False, scores[mid:], alpha, beta),
46+
)
47+
return value
48+
else:
49+
value = math.inf
50+
value = min(
51+
value,
52+
minimax(depth - 1, True, scores[:mid], alpha, beta),
53+
)
54+
beta = min(beta, value)
55+
if alpha < beta:
56+
value = min(
57+
value,
58+
minimax(depth - 1, True, scores[mid:], alpha, beta),
59+
)
60+
return value

algorithms/bit_manipulation/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .find_difference import find_difference
1313
from .find_missing_number import find_missing_number, find_missing_number2
1414
from .flip_bit_longest_sequence import flip_bit_longest_seq
15+
from .gray_code import gray_code, gray_to_binary
1516
from .has_alternative_bit import has_alternative_bit, has_alternative_bit_fast
1617
from .insert_bit import insert_mult_bits, insert_one_bit
1718
from .power_of_two import is_power_of_two
@@ -53,4 +54,6 @@
5354
"subsets",
5455
"swap_pair",
5556
"update_bit",
57+
"gray_code",
58+
"gray_to_binary",
5659
]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Gray code — generate n-bit Gray code sequences.
2+
3+
A Gray code is an ordering of binary numbers such that successive values
4+
differ in exactly one bit. Used in error correction and rotary encoders.
5+
6+
Inspired by PR #932 (Simranstha045).
7+
"""
8+
9+
from __future__ import annotations
10+
11+
12+
def gray_code(n: int) -> list[int]:
13+
"""Return the n-bit Gray code sequence as a list of integers.
14+
15+
Uses the reflection (mirror) construction:
16+
gray(i) = i ^ (i >> 1)
17+
18+
>>> gray_code(2)
19+
[0, 1, 3, 2]
20+
>>> gray_code(3)
21+
[0, 1, 3, 2, 6, 7, 5, 4]
22+
"""
23+
return [i ^ (i >> 1) for i in range(1 << n)]
24+
25+
26+
def gray_to_binary(gray: int) -> int:
27+
"""Convert a Gray-coded integer back to standard binary."""
28+
mask = gray >> 1
29+
while mask:
30+
gray ^= mask
31+
mask >>= 1
32+
return gray

algorithms/data_structures/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from algorithms.data_structures.hash_table import HashTable, ResizableHashTable
1616
from algorithms.data_structures.heap import AbstractHeap, BinaryHeap
1717
from algorithms.data_structures.iterative_segment_tree import SegmentTree
18+
from algorithms.data_structures.kd_tree import KDTree
1819
from algorithms.data_structures.linked_list import (
1920
DoublyLinkedListNode,
2021
SinglyLinkedListNode,
@@ -71,4 +72,5 @@
7172
"SegmentTree",
7273
"SegmentTreeRecursive",
7374
"Trie",
75+
"KDTree",
7476
]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""KD-tree — a space-partitioning tree for k-dimensional points.
2+
3+
Supports efficient nearest-neighbour and range queries.
4+
5+
Inspired by PR #915 (gjones1077).
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import math
11+
from typing import Any
12+
13+
14+
class KDNode:
15+
"""A single node in a KD-tree."""
16+
17+
__slots__ = ("point", "left", "right", "axis")
18+
19+
def __init__(
20+
self,
21+
point: tuple[float, ...],
22+
left: KDNode | None = None,
23+
right: KDNode | None = None,
24+
axis: int = 0,
25+
) -> None:
26+
self.point = point
27+
self.left = left
28+
self.right = right
29+
self.axis = axis
30+
31+
32+
class KDTree:
33+
"""A k-dimensional tree built from a list of points.
34+
35+
>>> tree = KDTree([(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)])
36+
>>> tree.nearest((9, 2))
37+
(8, 1)
38+
"""
39+
40+
def __init__(self, points: list[tuple[float, ...]]) -> None:
41+
self.k = len(points[0]) if points else 0
42+
self.root = self._build(list(points), depth=0)
43+
44+
def _build(self, points: list[tuple[float, ...]], depth: int) -> KDNode | None:
45+
if not points:
46+
return None
47+
axis = depth % self.k
48+
points.sort(key=lambda p: p[axis])
49+
mid = len(points) // 2
50+
return KDNode(
51+
point=points[mid],
52+
left=self._build(points[:mid], depth + 1),
53+
right=self._build(points[mid + 1 :], depth + 1),
54+
axis=axis,
55+
)
56+
57+
def nearest(self, target: tuple[float, ...]) -> tuple[float, ...]:
58+
"""Return the point closest to *target*."""
59+
best: list[Any] = [None, math.inf]
60+
self._nearest(self.root, target, best)
61+
return best[0]
62+
63+
def _nearest(
64+
self,
65+
node: KDNode | None,
66+
target: tuple[float, ...],
67+
best: list[Any],
68+
) -> None:
69+
if node is None:
70+
return
71+
dist = _sq_dist(node.point, target)
72+
if dist < best[1]:
73+
best[0], best[1] = node.point, dist
74+
axis = node.axis
75+
diff = target[axis] - node.point[axis]
76+
close, away = (node.left, node.right) if diff <= 0 else (node.right, node.left)
77+
self._nearest(close, target, best)
78+
if diff * diff < best[1]:
79+
self._nearest(away, target, best)
80+
81+
82+
def _sq_dist(a: tuple[float, ...], b: tuple[float, ...]) -> float:
83+
return sum((x - y) ** 2 for x, y in zip(a, b, strict=False))

algorithms/dynamic_programming/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
"""
66

77
from . import regex_matching
8+
from .bitmask import tsp
89
from .buy_sell_stock import max_profit_naive, max_profit_optimized
910
from .climbing_stairs import climb_stairs, climb_stairs_optimized
1011
from .coin_change import count
1112
from .combination_sum import combination_sum_bottom_up, combination_sum_topdown
13+
from .count_paths_dp import count_paths_dp, count_paths_memo, count_paths_recursive
1214
from .edit_distance import edit_distance
1315
from .egg_drop import egg_drop
1416
from .fib import fib_iter, fib_list, fib_recursive
@@ -96,4 +98,10 @@
9698
"cut_rod",
9799
# word_break
98100
"word_break",
101+
# bitmask
102+
"tsp",
103+
# count_paths_dp
104+
"count_paths_dp",
105+
"count_paths_memo",
106+
"count_paths_recursive",
99107
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Bitmask dynamic programming — Travelling Salesman Problem (TSP).
2+
3+
Uses DP with bitmask to find the minimum-cost Hamiltonian cycle in a
4+
weighted graph. The state (visited_mask, current_city) encodes which
5+
cities have been visited and where we are now.
6+
7+
Time: O(2^n * n^2). Space: O(2^n * n).
8+
9+
Inspired by PR #855 (AmandaStromdahl).
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import math
15+
16+
17+
def tsp(dist: list[list[float]]) -> float:
18+
"""Return the minimum cost of a Hamiltonian cycle.
19+
20+
*dist* is an n x n distance matrix.
21+
22+
>>> tsp([[0, 10, 15, 20],
23+
... [10, 0, 35, 25],
24+
... [15, 35, 0, 30],
25+
... [20, 25, 30, 0]])
26+
80
27+
"""
28+
n = len(dist)
29+
full_mask = (1 << n) - 1
30+
dp: dict[tuple[int, int], float] = {}
31+
32+
def solve(mask: int, pos: int) -> float:
33+
if mask == full_mask:
34+
return dist[pos][0]
35+
key = (mask, pos)
36+
if key in dp:
37+
return dp[key]
38+
ans = math.inf
39+
for city in range(n):
40+
if not (mask & (1 << city)):
41+
ans = min(ans, dist[pos][city] + solve(mask | (1 << city), city))
42+
dp[key] = ans
43+
return ans
44+
45+
return solve(1, 0)

0 commit comments

Comments
 (0)