Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

[pull] master from TheAlgorithms:master #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 2, 2024
Merged
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
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* [Rat In Maze](backtracking/rat_in_maze.py)
* [Sudoku](backtracking/sudoku.py)
* [Sum Of Subsets](backtracking/sum_of_subsets.py)
* [Word Ladder](backtracking/word_ladder.py)
* [Word Search](backtracking/word_search.py)

## Bit Manipulation
Expand Down
100 changes: 100 additions & 0 deletions backtracking/word_ladder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
Word Ladder is a classic problem in computer science.
The problem is to transform a start word into an end word
by changing one letter at a time.
Each intermediate word must be a valid word from a given list of words.
The goal is to find a transformation sequence
from the start word to the end word.

Wikipedia: https://en.wikipedia.org/wiki/Word_ladder
"""

import string


def backtrack(
current_word: str, path: list[str], end_word: str, word_set: set[str]
) -> list[str]:
"""
Helper function to perform backtracking to find the transformation
from the current_word to the end_word.

Parameters:
current_word (str): The current word in the transformation sequence.
path (list[str]): The list of transformations from begin_word to current_word.
end_word (str): The target word for transformation.
word_set (set[str]): The set of valid words for transformation.

Returns:
list[str]: The list of transformations from begin_word to end_word.
Returns an empty list if there is no valid
transformation from current_word to end_word.

Example:
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"})
['hit', 'hot', 'dot', 'lot', 'log', 'cog']

>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"})
[]

>>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"})
['lead', 'lead', 'load', 'goad', 'gold']

>>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"})
['game', 'came', 'cade', 'code']
"""

# Base case: If the current word is the end word, return the path
if current_word == end_word:
return path

# Try all possible single-letter transformations
for i in range(len(current_word)):
for c in string.ascii_lowercase: # Try changing each letter
transformed_word = current_word[:i] + c + current_word[i + 1 :]
if transformed_word in word_set:
word_set.remove(transformed_word)
# Recur with the new word added to the path
result = backtrack(
transformed_word, [*path, transformed_word], end_word, word_set
)
if result: # valid transformation found
return result
word_set.add(transformed_word) # backtrack

return [] # No valid transformation found


def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]:
"""
Solve the Word Ladder problem using Backtracking and return
the list of transformations from begin_word to end_word.

Parameters:
begin_word (str): The word from which the transformation starts.
end_word (str): The target word for transformation.
word_list (list[str]): The list of valid words for transformation.

Returns:
list[str]: The list of transformations from begin_word to end_word.
Returns an empty list if there is no valid transformation.

Example:
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"])
['hit', 'hot', 'dot', 'lot', 'log', 'cog']

>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"])
[]

>>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"])
['lead', 'lead', 'load', 'goad', 'gold']

>>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"])
['game', 'came', 'cade', 'code']
"""

if end_word not in word_set: # no valid transformation possible
return []

# Perform backtracking starting from the begin_word
return backtrack(begin_word, [begin_word], end_word, word_set)
67 changes: 62 additions & 5 deletions data_structures/binary_tree/symmetric_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,21 @@
@dataclass
class Node:
"""
A Node has data variable and pointers to Nodes to its left and right.
A Node represents an element of a binary tree, which contains:

Attributes:
data: The value stored in the node (int).
left: Pointer to the left child node (Node or None).
right: Pointer to the right child node (Node or None).

Example:
>>> node = Node(1, Node(2), Node(3))
>>> node.data
1
>>> node.left.data
2
>>> node.right.data
3
"""

data: int
Expand All @@ -24,12 +38,25 @@ class Node:
def make_symmetric_tree() -> Node:
r"""
Create a symmetric tree for testing.

The tree looks like this:
1
/ \
2 2
/ \ / \
3 4 4 3

Returns:
Node: Root node of a symmetric tree.

Example:
>>> tree = make_symmetric_tree()
>>> tree.data
1
>>> tree.left.data == tree.right.data
True
>>> tree.left.left.data == tree.right.right.data
True
"""
root = Node(1)
root.left = Node(2)
Expand All @@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node:

def make_asymmetric_tree() -> Node:
r"""
Create a asymmetric tree for testing.
Create an asymmetric tree for testing.

The tree looks like this:
1
/ \
2 2
/ \ / \
3 4 3 4

Returns:
Node: Root node of an asymmetric tree.

Example:
>>> tree = make_asymmetric_tree()
>>> tree.data
1
>>> tree.left.data == tree.right.data
True
>>> tree.left.left.data == tree.right.right.data
False
"""
root = Node(1)
root.left = Node(2)
Expand All @@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node:

def is_symmetric_tree(tree: Node) -> bool:
"""
Test cases for is_symmetric_tree function
Check if a binary tree is symmetric (i.e., a mirror of itself).

Parameters:
tree: The root node of the binary tree.

Returns:
bool: True if the tree is symmetric, False otherwise.

Example:
>>> is_symmetric_tree(make_symmetric_tree())
True
>>> is_symmetric_tree(make_asymmetric_tree())
Expand All @@ -76,8 +124,17 @@ def is_symmetric_tree(tree: Node) -> bool:

def is_mirror(left: Node | None, right: Node | None) -> bool:
"""
Check if two subtrees are mirror images of each other.

Parameters:
left: The root node of the left subtree.
right: The root node of the right subtree.

Returns:
bool: True if the two subtrees are mirrors of each other, False otherwise.

Example:
>>> tree1 = make_symmetric_tree()
>>> tree1.right.right = Node(3)
>>> is_mirror(tree1.left, tree1.right)
True
>>> tree2 = make_asymmetric_tree()
Expand All @@ -91,7 +148,7 @@ def is_mirror(left: Node | None, right: Node | None) -> bool:
# One side is empty while the other is not, which is not symmetric.
return False
if left.data == right.data:
# The values match, so check the subtree
# The values match, so check the subtrees recursively.
return is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
return False

Expand Down