Skip to content

Commit 6bc6891

Browse files
Merge branch 'TheAlgorithms:master' into master
2 parents e5fb384 + 40f65e8 commit 6bc6891

File tree

4 files changed

+164
-18
lines changed

4 files changed

+164
-18
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

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

0 commit comments

Comments
 (0)