Skip to content

Commit 961e1a4

Browse files
authored
Merge branch 'TheAlgorithms:master' into master
2 parents cabb35a + 40f65e8 commit 961e1a4

File tree

8 files changed

+328
-24
lines changed

8 files changed

+328
-24
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
2121
- name: Install dependencies
2222
run: |
23-
python -m pip install --upgrade pip setuptools six wheel
23+
python -m pip install --upgrade pip setuptools wheel
2424
python -m pip install pytest-cov -r requirements.txt
2525
- name: Run tests
2626
# TODO: #8818 Re-enable quantum tests

DIRECTORY.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* [Rat In Maze](backtracking/rat_in_maze.py)
2323
* [Sudoku](backtracking/sudoku.py)
2424
* [Sum Of Subsets](backtracking/sum_of_subsets.py)
25+
* [Word Ladder](backtracking/word_ladder.py)
2526
* [Word Search](backtracking/word_search.py)
2627

2728
## Bit Manipulation

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)

backtracking/word_ladder.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
Word Ladder is a classic problem in computer science.
3+
The problem is to transform a start word into an end word
4+
by changing one letter at a time.
5+
Each intermediate word must be a valid word from a given list of words.
6+
The goal is to find a transformation sequence
7+
from the start word to the end word.
8+
9+
Wikipedia: https://en.wikipedia.org/wiki/Word_ladder
10+
"""
11+
12+
import string
13+
14+
15+
def backtrack(
16+
current_word: str, path: list[str], end_word: str, word_set: set[str]
17+
) -> list[str]:
18+
"""
19+
Helper function to perform backtracking to find the transformation
20+
from the current_word to the end_word.
21+
22+
Parameters:
23+
current_word (str): The current word in the transformation sequence.
24+
path (list[str]): The list of transformations from begin_word to current_word.
25+
end_word (str): The target word for transformation.
26+
word_set (set[str]): The set of valid words for transformation.
27+
28+
Returns:
29+
list[str]: The list of transformations from begin_word to end_word.
30+
Returns an empty list if there is no valid
31+
transformation from current_word to end_word.
32+
33+
Example:
34+
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"})
35+
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
36+
37+
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"})
38+
[]
39+
40+
>>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"})
41+
['lead', 'lead', 'load', 'goad', 'gold']
42+
43+
>>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"})
44+
['game', 'came', 'cade', 'code']
45+
"""
46+
47+
# Base case: If the current word is the end word, return the path
48+
if current_word == end_word:
49+
return path
50+
51+
# Try all possible single-letter transformations
52+
for i in range(len(current_word)):
53+
for c in string.ascii_lowercase: # Try changing each letter
54+
transformed_word = current_word[:i] + c + current_word[i + 1 :]
55+
if transformed_word in word_set:
56+
word_set.remove(transformed_word)
57+
# Recur with the new word added to the path
58+
result = backtrack(
59+
transformed_word, [*path, transformed_word], end_word, word_set
60+
)
61+
if result: # valid transformation found
62+
return result
63+
word_set.add(transformed_word) # backtrack
64+
65+
return [] # No valid transformation found
66+
67+
68+
def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]:
69+
"""
70+
Solve the Word Ladder problem using Backtracking and return
71+
the list of transformations from begin_word to end_word.
72+
73+
Parameters:
74+
begin_word (str): The word from which the transformation starts.
75+
end_word (str): The target word for transformation.
76+
word_list (list[str]): The list of valid words for transformation.
77+
78+
Returns:
79+
list[str]: The list of transformations from begin_word to end_word.
80+
Returns an empty list if there is no valid transformation.
81+
82+
Example:
83+
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"])
84+
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
85+
86+
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"])
87+
[]
88+
89+
>>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"])
90+
['lead', 'lead', 'load', 'goad', 'gold']
91+
92+
>>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"])
93+
['game', 'came', 'cade', 'code']
94+
"""
95+
96+
if end_word not in word_set: # no valid transformation possible
97+
return []
98+
99+
# Perform backtracking starting from the begin_word
100+
return backtrack(begin_word, [begin_word], end_word, word_set)

data_structures/binary_tree/symmetric_tree.py

+62-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@
1313
@dataclass
1414
class Node:
1515
"""
16-
A Node has data variable and pointers to Nodes to its left and right.
16+
A Node represents an element of a binary tree, which contains:
17+
18+
Attributes:
19+
data: The value stored in the node (int).
20+
left: Pointer to the left child node (Node or None).
21+
right: Pointer to the right child node (Node or None).
22+
23+
Example:
24+
>>> node = Node(1, Node(2), Node(3))
25+
>>> node.data
26+
1
27+
>>> node.left.data
28+
2
29+
>>> node.right.data
30+
3
1731
"""
1832

1933
data: int
@@ -24,12 +38,25 @@ class Node:
2438
def make_symmetric_tree() -> Node:
2539
r"""
2640
Create a symmetric tree for testing.
41+
2742
The tree looks like this:
2843
1
2944
/ \
3045
2 2
3146
/ \ / \
3247
3 4 4 3
48+
49+
Returns:
50+
Node: Root node of a symmetric tree.
51+
52+
Example:
53+
>>> tree = make_symmetric_tree()
54+
>>> tree.data
55+
1
56+
>>> tree.left.data == tree.right.data
57+
True
58+
>>> tree.left.left.data == tree.right.right.data
59+
True
3360
"""
3461
root = Node(1)
3562
root.left = Node(2)
@@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node:
4370

4471
def make_asymmetric_tree() -> Node:
4572
r"""
46-
Create a asymmetric tree for testing.
73+
Create an asymmetric tree for testing.
74+
4775
The tree looks like this:
4876
1
4977
/ \
5078
2 2
5179
/ \ / \
5280
3 4 3 4
81+
82+
Returns:
83+
Node: Root node of an asymmetric tree.
84+
85+
Example:
86+
>>> tree = make_asymmetric_tree()
87+
>>> tree.data
88+
1
89+
>>> tree.left.data == tree.right.data
90+
True
91+
>>> tree.left.left.data == tree.right.right.data
92+
False
5393
"""
5494
root = Node(1)
5595
root.left = Node(2)
@@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node:
63103

64104
def is_symmetric_tree(tree: Node) -> bool:
65105
"""
66-
Test cases for is_symmetric_tree function
106+
Check if a binary tree is symmetric (i.e., a mirror of itself).
107+
108+
Parameters:
109+
tree: The root node of the binary tree.
110+
111+
Returns:
112+
bool: True if the tree is symmetric, False otherwise.
113+
114+
Example:
67115
>>> is_symmetric_tree(make_symmetric_tree())
68116
True
69117
>>> is_symmetric_tree(make_asymmetric_tree())
@@ -76,8 +124,17 @@ def is_symmetric_tree(tree: Node) -> bool:
76124

77125
def is_mirror(left: Node | None, right: Node | None) -> bool:
78126
"""
127+
Check if two subtrees are mirror images of each other.
128+
129+
Parameters:
130+
left: The root node of the left subtree.
131+
right: The root node of the right subtree.
132+
133+
Returns:
134+
bool: True if the two subtrees are mirrors of each other, False otherwise.
135+
136+
Example:
79137
>>> tree1 = make_symmetric_tree()
80-
>>> tree1.right.right = Node(3)
81138
>>> is_mirror(tree1.left, tree1.right)
82139
True
83140
>>> tree2 = make_asymmetric_tree()
@@ -91,7 +148,7 @@ def is_mirror(left: Node | None, right: Node | None) -> bool:
91148
# One side is empty while the other is not, which is not symmetric.
92149
return False
93150
if left.data == right.data:
94-
# The values match, so check the subtree
151+
# The values match, so check the subtrees recursively.
95152
return is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
96153
return False
97154

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

0 commit comments

Comments
 (0)