Skip to content

Commit 400f4ea

Browse files
authored
Merge branch 'TheAlgorithms:master' into master
2 parents 36700a1 + 50aca04 commit 400f4ea

File tree

9 files changed

+395
-46
lines changed

9 files changed

+395
-46
lines changed

backtracking/word_break.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Word Break Problem is a well-known problem in computer science.
3+
Given a string and a dictionary of words, the task is to determine if
4+
the string can be segmented into a sequence of one or more dictionary words.
5+
6+
Wikipedia: https://en.wikipedia.org/wiki/Word_break_problem
7+
"""
8+
9+
10+
def backtrack(input_string: str, word_dict: set[str], start: int) -> bool:
11+
"""
12+
Helper function that uses backtracking to determine if a valid
13+
word segmentation is possible starting from index 'start'.
14+
15+
Parameters:
16+
input_string (str): The input string to be segmented.
17+
word_dict (set[str]): A set of valid dictionary words.
18+
start (int): The starting index of the substring to be checked.
19+
20+
Returns:
21+
bool: True if a valid segmentation is possible, otherwise False.
22+
23+
Example:
24+
>>> backtrack("leetcode", {"leet", "code"}, 0)
25+
True
26+
27+
>>> backtrack("applepenapple", {"apple", "pen"}, 0)
28+
True
29+
30+
>>> backtrack("catsandog", {"cats", "dog", "sand", "and", "cat"}, 0)
31+
False
32+
"""
33+
34+
# Base case: if the starting index has reached the end of the string
35+
if start == len(input_string):
36+
return True
37+
38+
# Try every possible substring from 'start' to 'end'
39+
for end in range(start + 1, len(input_string) + 1):
40+
if input_string[start:end] in word_dict and backtrack(
41+
input_string, word_dict, end
42+
):
43+
return True
44+
45+
return False
46+
47+
48+
def word_break(input_string: str, word_dict: set[str]) -> bool:
49+
"""
50+
Determines if the input string can be segmented into a sequence of
51+
valid dictionary words using backtracking.
52+
53+
Parameters:
54+
input_string (str): The input string to segment.
55+
word_dict (set[str]): The set of valid words.
56+
57+
Returns:
58+
bool: True if the string can be segmented into valid words, otherwise False.
59+
60+
Example:
61+
>>> word_break("leetcode", {"leet", "code"})
62+
True
63+
64+
>>> word_break("applepenapple", {"apple", "pen"})
65+
True
66+
67+
>>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"})
68+
False
69+
"""
70+
71+
return backtrack(input_string, word_dict, 0)

data_structures/linked_list/has_loop.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ def __init__(self, data: Any) -> None:
1414

1515
def __iter__(self):
1616
node = self
17-
visited = []
17+
visited = set()
1818
while node:
1919
if node in visited:
2020
raise ContainsLoopError
21-
visited.append(node)
21+
visited.add(node)
2222
yield node.data
2323
node = node.next_node
2424

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from collections.abc import Iterator
2+
3+
4+
def lexical_order(max_number: int) -> Iterator[int]:
5+
"""
6+
Generate numbers in lexical order from 1 to max_number.
7+
8+
>>> " ".join(map(str, lexical_order(13)))
9+
'1 10 11 12 13 2 3 4 5 6 7 8 9'
10+
>>> list(lexical_order(1))
11+
[1]
12+
>>> " ".join(map(str, lexical_order(20)))
13+
'1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9'
14+
>>> " ".join(map(str, lexical_order(25)))
15+
'1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 3 4 5 6 7 8 9'
16+
>>> list(lexical_order(12))
17+
[1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9]
18+
"""
19+
20+
stack = [1]
21+
22+
while stack:
23+
num = stack.pop()
24+
if num > max_number:
25+
continue
26+
27+
yield num
28+
if (num % 10) != 9:
29+
stack.append(num + 1)
30+
31+
stack.append(num * 10)
32+
33+
34+
if __name__ == "__main__":
35+
from doctest import testmod
36+
37+
testmod()
38+
print(f"Numbers from 1 to 25 in lexical order: {list(lexical_order(26))}")

data_structures/stacks/next_greater_element.py

+46-14
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,20 @@
66

77
def next_greatest_element_slow(arr: list[float]) -> list[float]:
88
"""
9-
Get the Next Greatest Element (NGE) for all elements in a list.
10-
Maximum element present after the current one which is also greater than the
11-
current one.
9+
Get the Next Greatest Element (NGE) for each element in the array
10+
by checking all subsequent elements to find the next greater one.
11+
12+
This is a brute-force implementation, and it has a time complexity
13+
of O(n^2), where n is the size of the array.
14+
15+
Args:
16+
arr: List of numbers for which the NGE is calculated.
17+
18+
Returns:
19+
List containing the next greatest elements. If no
20+
greater element is found, -1 is placed in the result.
21+
22+
Example:
1223
>>> next_greatest_element_slow(arr) == expect
1324
True
1425
"""
@@ -28,9 +39,21 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]:
2839

2940
def next_greatest_element_fast(arr: list[float]) -> list[float]:
3041
"""
31-
Like next_greatest_element_slow() but changes the loops to use
32-
enumerate() instead of range(len()) for the outer loop and
33-
for in a slice of arr for the inner loop.
42+
Find the Next Greatest Element (NGE) for each element in the array
43+
using a more readable approach. This implementation utilizes
44+
enumerate() for the outer loop and slicing for the inner loop.
45+
46+
While this improves readability over next_greatest_element_slow(),
47+
it still has a time complexity of O(n^2).
48+
49+
Args:
50+
arr: List of numbers for which the NGE is calculated.
51+
52+
Returns:
53+
List containing the next greatest elements. If no
54+
greater element is found, -1 is placed in the result.
55+
56+
Example:
3457
>>> next_greatest_element_fast(arr) == expect
3558
True
3659
"""
@@ -47,14 +70,23 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]:
4770

4871
def next_greatest_element(arr: list[float]) -> list[float]:
4972
"""
50-
Get the Next Greatest Element (NGE) for all elements in a list.
51-
Maximum element present after the current one which is also greater than the
52-
current one.
53-
54-
A naive way to solve this is to take two loops and check for the next bigger
55-
number but that will make the time complexity as O(n^2). The better way to solve
56-
this would be to use a stack to keep track of maximum number giving a linear time
57-
solution.
73+
Efficient solution to find the Next Greatest Element (NGE) for all elements
74+
using a stack. The time complexity is reduced to O(n), making it suitable
75+
for larger arrays.
76+
77+
The stack keeps track of elements for which the next greater element hasn't
78+
been found yet. By iterating through the array in reverse (from the last
79+
element to the first), the stack is used to efficiently determine the next
80+
greatest element for each element.
81+
82+
Args:
83+
arr: List of numbers for which the NGE is calculated.
84+
85+
Returns:
86+
List containing the next greatest elements. If no
87+
greater element is found, -1 is placed in the result.
88+
89+
Example:
5890
>>> next_greatest_element(arr) == expect
5991
True
6092
"""

dynamic_programming/floyd_warshall.py

+45-2
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,58 @@ def __init__(self, n=0): # a graph with Node 0,1,...,N-1
1212
] # dp[i][j] stores minimum distance from i to j
1313

1414
def add_edge(self, u, v, w):
15+
"""
16+
Adds a directed edge from node u
17+
to node v with weight w.
18+
19+
>>> g = Graph(3)
20+
>>> g.add_edge(0, 1, 5)
21+
>>> g.dp[0][1]
22+
5
23+
"""
1524
self.dp[u][v] = w
1625

1726
def floyd_warshall(self):
27+
"""
28+
Computes the shortest paths between all pairs of
29+
nodes using the Floyd-Warshall algorithm.
30+
31+
>>> g = Graph(3)
32+
>>> g.add_edge(0, 1, 1)
33+
>>> g.add_edge(1, 2, 2)
34+
>>> g.floyd_warshall()
35+
>>> g.show_min(0, 2)
36+
3
37+
>>> g.show_min(2, 0)
38+
inf
39+
"""
1840
for k in range(self.n):
1941
for i in range(self.n):
2042
for j in range(self.n):
2143
self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j])
2244

2345
def show_min(self, u, v):
46+
"""
47+
Returns the minimum distance from node u to node v.
48+
49+
>>> g = Graph(3)
50+
>>> g.add_edge(0, 1, 3)
51+
>>> g.add_edge(1, 2, 4)
52+
>>> g.floyd_warshall()
53+
>>> g.show_min(0, 2)
54+
7
55+
>>> g.show_min(1, 0)
56+
inf
57+
"""
2458
return self.dp[u][v]
2559

2660

2761
if __name__ == "__main__":
62+
import doctest
63+
64+
doctest.testmod()
65+
66+
# Example usage
2867
graph = Graph(5)
2968
graph.add_edge(0, 2, 9)
3069
graph.add_edge(0, 4, 10)
@@ -38,5 +77,9 @@ def show_min(self, u, v):
3877
graph.add_edge(4, 2, 4)
3978
graph.add_edge(4, 3, 9)
4079
graph.floyd_warshall()
41-
graph.show_min(1, 4)
42-
graph.show_min(0, 3)
80+
print(
81+
graph.show_min(1, 4)
82+
) # Should output the minimum distance from node 1 to node 4
83+
print(
84+
graph.show_min(0, 3)
85+
) # Should output the minimum distance from node 0 to node 3

dynamic_programming/longest_common_subsequence.py

+18
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ def longest_common_subsequence(x: str, y: str):
2828
(2, 'ph')
2929
>>> longest_common_subsequence("computer", "food")
3030
(1, 'o')
31+
>>> longest_common_subsequence("", "abc") # One string is empty
32+
(0, '')
33+
>>> longest_common_subsequence("abc", "") # Other string is empty
34+
(0, '')
35+
>>> longest_common_subsequence("", "") # Both strings are empty
36+
(0, '')
37+
>>> longest_common_subsequence("abc", "def") # No common subsequence
38+
(0, '')
39+
>>> longest_common_subsequence("abc", "abc") # Identical strings
40+
(3, 'abc')
41+
>>> longest_common_subsequence("a", "a") # Single character match
42+
(1, 'a')
43+
>>> longest_common_subsequence("a", "b") # Single character no match
44+
(0, '')
45+
>>> longest_common_subsequence("abcdef", "ace") # Interleaved subsequence
46+
(3, 'ace')
47+
>>> longest_common_subsequence("ABCD", "ACBD") # No repeated characters
48+
(3, 'ABD')
3149
"""
3250
# find the length of strings
3351

graphs/kahns_algorithm_topo.py

+46-21
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,61 @@
1-
def topological_sort(graph):
1+
def topological_sort(graph: dict[int, list[int]]) -> list[int] | None:
22
"""
3-
Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph
4-
using BFS
3+
Perform topological sorting of a Directed Acyclic Graph (DAG)
4+
using Kahn's Algorithm via Breadth-First Search (BFS).
5+
6+
Topological sorting is a linear ordering of vertices in a graph such that for
7+
every directed edge u → v, vertex u comes before vertex v in the ordering.
8+
9+
Parameters:
10+
graph: Adjacency list representing the directed graph where keys are
11+
vertices, and values are lists of adjacent vertices.
12+
13+
Returns:
14+
The topologically sorted order of vertices if the graph is a DAG.
15+
Returns None if the graph contains a cycle.
16+
17+
Example:
18+
>>> graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}
19+
>>> topological_sort(graph)
20+
[0, 1, 2, 3, 4, 5]
21+
22+
>>> graph_with_cycle = {0: [1], 1: [2], 2: [0]}
23+
>>> topological_sort(graph_with_cycle)
524
"""
25+
626
indegree = [0] * len(graph)
727
queue = []
8-
topo = []
9-
cnt = 0
28+
topo_order = []
29+
processed_vertices_count = 0
1030

31+
# Calculate the indegree of each vertex
1132
for values in graph.values():
1233
for i in values:
1334
indegree[i] += 1
1435

36+
# Add all vertices with 0 indegree to the queue
1537
for i in range(len(indegree)):
1638
if indegree[i] == 0:
1739
queue.append(i)
1840

41+
# Perform BFS
1942
while queue:
2043
vertex = queue.pop(0)
21-
cnt += 1
22-
topo.append(vertex)
23-
for x in graph[vertex]:
24-
indegree[x] -= 1
25-
if indegree[x] == 0:
26-
queue.append(x)
27-
28-
if cnt != len(graph):
29-
print("Cycle exists")
30-
else:
31-
print(topo)
32-
33-
34-
# Adjacency List of Graph
35-
graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}
36-
topological_sort(graph)
44+
processed_vertices_count += 1
45+
topo_order.append(vertex)
46+
47+
# Traverse neighbors
48+
for neighbor in graph[vertex]:
49+
indegree[neighbor] -= 1
50+
if indegree[neighbor] == 0:
51+
queue.append(neighbor)
52+
53+
if processed_vertices_count != len(graph):
54+
return None # no topological ordering exists due to cycle
55+
return topo_order # valid topological ordering
56+
57+
58+
if __name__ == "__main__":
59+
import doctest
60+
61+
doctest.testmod()

0 commit comments

Comments
 (0)