translation: Add Python and Java code for EN version (#1345)

* Add the intial translation of code of all the languages

* test

* revert

* Remove

* Add Python and Java code for EN version
This commit is contained in:
Yudong Jin
2024-05-06 05:21:51 +08:00
committed by GitHub
parent b5e198db7d
commit 1c0f350ad6
174 changed files with 12349 additions and 0 deletions

1
en/codes/python/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
__pycache__

View File

@@ -0,0 +1,100 @@
"""
File: array.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import random
def random_access(nums: list[int]) -> int:
"""Random access to elements"""
# Randomly select a number from the interval [0, len(nums)-1]
random_index = random.randint(0, len(nums) - 1)
# Retrieve and return a random element
random_num = nums[random_index]
return random_num
# Note that Python's list is a dynamic array that can be extended
# For ease of learning, this function treats the list as a static array
def extend(nums: list[int], enlarge: int) -> list[int]:
"""Extend array length"""
# Initialize an extended length array
res = [0] * (len(nums) + enlarge)
# Copy all elements from the original array to the new array
for i in range(len(nums)):
res[i] = nums[i]
# Return the new array after expansion
return res
def insert(nums: list[int], num: int, index: int):
"""Insert element num at `index`"""
# Move all elements after `index` one position backward
for i in range(len(nums) - 1, index, -1):
nums[i] = nums[i - 1]
# Assign num to the element at index
nums[index] = num
def remove(nums: list[int], index: int):
"""Remove the element at `index`"""
# Move all elements after `index` one position forward
for i in range(index, len(nums) - 1):
nums[i] = nums[i + 1]
def traverse(nums: list[int]):
"""Traverse array"""
count = 0
# Traverse array by index
for i in range(len(nums)):
count += nums[i]
# Traverse array elements
for num in nums:
count += num
# Traverse both data index and elements
for i, num in enumerate(nums):
count += nums[i]
count += num
def find(nums: list[int], target: int) -> int:
"""Search for a specified element in the array"""
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
"""Driver Code"""
if __name__ == "__main__":
# Initialize an array
arr = [0] * 5
print("Array arr =", arr)
nums = [1, 3, 2, 5, 4]
print("Array nums =", nums)
# Random access
random_num: int = random_access(nums)
print("Retrieve a random element in nums", random_num)
# Length extension
nums: list[int] = extend(nums, 3)
print("Extend the array length to 8, resulting in nums =", nums)
# Insert element
insert(nums, 6, 3)
print("Insert number 6 at index 3, resulting in nums =", nums)
# Remove element
remove(nums, 2)
print("Remove the element at index 2, resulting in nums =", nums)
# Traverse array
traverse(nums)
# Search for elements
index: int = find(nums, 3)
print("Search for element 3 in nums, resulting in index =", index)

View File

@@ -0,0 +1,85 @@
"""
File: linked_list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, print_linked_list
def insert(n0: ListNode, P: ListNode):
"""Insert node P after node n0 in the linked list"""
n1 = n0.next
P.next = n1
n0.next = P
def remove(n0: ListNode):
"""Remove the first node after node n0 in the linked list"""
if not n0.next:
return
# n0 -> P -> n1
P = n0.next
n1 = P.next
n0.next = n1
def access(head: ListNode, index: int) -> ListNode | None:
"""Access the node at `index` in the linked list"""
for _ in range(index):
if not head:
return None
head = head.next
return head
def find(head: ListNode, target: int) -> int:
"""Search for the first node with value target in the linked list"""
index = 0
while head:
if head.val == target:
return index
head = head.next
index += 1
return -1
"""Driver Code"""
if __name__ == "__main__":
# Initialize linked list
# Initialize each node
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# Build references between nodes
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
print("The initialized linked list is")
print_linked_list(n0)
# Insert node
p = ListNode(0)
insert(n0, p)
print("Linked list after inserting the node is")
print_linked_list(n0)
# Remove node
remove(n0)
print("Linked list after removing the node is")
print_linked_list(n0)
# Access node
node: ListNode = access(n0, 3)
print("The value of the node at index 3 in the linked list = {}".format(node.val))
# Search node
index: int = find(n0, 2)
print("The index of the node with value 2 in the linked list = {}".format(index))

View File

@@ -0,0 +1,56 @@
"""
File: list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
"""Driver Code"""
if __name__ == "__main__":
# Initialize list
nums: list[int] = [1, 3, 2, 5, 4]
print("\nList nums =", nums)
# Access element
x: int = nums[1]
print("\nAccess the element at index 1, resulting in x =", x)
# Update element
nums[1] = 0
print("\nUpdate the element at index 1 to 0, resulting in nums =", nums)
# Clear list
nums.clear()
print("\nAfter clearing the list, nums =", nums)
# Add element at the end
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
print("\nAfter adding the element, nums =", nums)
# Insert element in the middle
nums.insert(3, 6)
print("\nInsert number 6 at index 3, resulting in nums =", nums)
# Remove element
nums.pop(3)
print("\nRemove the element at index 3, resulting in nums =", nums)
# Traverse the list by index
count = 0
for i in range(len(nums)):
count += nums[i]
# Traverse the list elements
for num in nums:
count += num
# Concatenate two lists
nums1 = [6, 8, 7, 10, 9]
nums += nums1
print("\nConcatenate list nums1 to nums, resulting in nums =", nums)
# Sort list
nums.sort()
print("\nAfter sorting the list, nums =", nums)

View File

@@ -0,0 +1,118 @@
"""
File: my_list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
class MyList:
"""List class"""
def __init__(self):
"""Constructor"""
self._capacity: int = 10 # List capacity
self._arr: list[int] = [0] * self._capacity # Array (stores list elements)
self._size: int = 0 # List length (current number of elements)
self._extend_ratio: int = 2 # Multiple for each list expansion
def size(self) -> int:
"""Get list length (current number of elements)"""
return self._size
def capacity(self) -> int:
"""Get list capacity"""
return self._capacity
def get(self, index: int) -> int:
"""Access element"""
# If the index is out of bounds, throw an exception, as below
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
return self._arr[index]
def set(self, num: int, index: int):
"""Update element"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
self._arr[index] = num
def add(self, num: int):
"""Add element at the end"""
# When the number of elements exceeds capacity, trigger the expansion mechanism
if self.size() == self.capacity():
self.extend_capacity()
self._arr[self._size] = num
self._size += 1
def insert(self, num: int, index: int):
"""Insert element in the middle"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
# When the number of elements exceeds capacity, trigger the expansion mechanism
if self._size == self.capacity():
self.extend_capacity()
# Move all elements after `index` one position backward
for j in range(self._size - 1, index - 1, -1):
self._arr[j + 1] = self._arr[j]
self._arr[index] = num
# Update the number of elements
self._size += 1
def remove(self, index: int) -> int:
"""Remove element"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
num = self._arr[index]
# Move all elements after `index` one position forward
for j in range(index, self._size - 1):
self._arr[j] = self._arr[j + 1]
# Update the number of elements
self._size -= 1
# Return the removed element
return num
def extend_capacity(self):
"""Extend list"""
# Create a new array of _extend_ratio times the length of the original array and copy the original array to the new array
self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)
# Update list capacity
self._capacity = len(self._arr)
def to_array(self) -> list[int]:
"""Return a list of valid lengths"""
return self._arr[: self._size]
"""Driver Code"""
if __name__ == "__main__":
# Initialize list
nums = MyList()
# Add element at the end
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
print(f"List nums = {nums.to_array()} capacity = {nums.capacity()} length = {nums.size()}")
# Insert element in the middle
nums.insert(6, index=3)
print("Insert number 6 at index 3, resulting in nums =", nums.to_array())
# Remove element
nums.remove(3)
print("Remove the element at index 3, resulting in nums =", nums.to_array())
# Access element
num = nums.get(1)
print("Access the element at index 1, resulting in num =", num)
# Update element
nums.set(0, 1)
print("Update the element at index 1 to 0, resulting in nums =", nums.to_array())
# Test expansion mechanism
for i in range(10):
# At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism at this time
nums.add(i)
print(f"After expansion, the list {nums.to_array()} capacity = {nums.capacity()} length = {nums.size()}")

View File

@@ -0,0 +1,62 @@
"""
File: n_queens.py
Created Time: 2023-04-26
Author: krahets (krahets@163.com)
"""
def backtrack(
row: int,
n: int,
state: list[list[str]],
res: list[list[list[str]]],
cols: list[bool],
diags1: list[bool],
diags2: list[bool],
):
"""Backtracking algorithm: n queens"""
# When all rows are placed, record the solution
if row == n:
res.append([list(row) for row in state])
return
# Traverse all columns
for col in range(n):
# Calculate the main and minor diagonals corresponding to the cell
diag1 = row - col + n - 1
diag2 = row + col
# Pruning: do not allow queens on the column, main diagonal, or minor diagonal of the cell
if not cols[col] and not diags1[diag1] and not diags2[diag2]:
# Attempt: place the queen in the cell
state[row][col] = "Q"
cols[col] = diags1[diag1] = diags2[diag2] = True
# Place the next row
backtrack(row + 1, n, state, res, cols, diags1, diags2)
# Retract: restore the cell to an empty spot
state[row][col] = "#"
cols[col] = diags1[diag1] = diags2[diag2] = False
def n_queens(n: int) -> list[list[list[str]]]:
"""Solve n queens"""
# Initialize an n*n size chessboard, where 'Q' represents the queen and '#' represents an empty spot
state = [["#" for _ in range(n)] for _ in range(n)]
cols = [False] * n # Record columns with queens
diags1 = [False] * (2 * n - 1) # Record main diagonals with queens
diags2 = [False] * (2 * n - 1) # Record minor diagonals with queens
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
return res
"""Driver Code"""
if __name__ == "__main__":
n = 4
res = n_queens(n)
print(f"Input chessboard dimensions as {n}")
print(f"The total number of queen placement solutions is {len(res)}")
for state in res:
print("--------------------")
for row in state:
print(row)

View File

@@ -0,0 +1,44 @@
"""
File: permutations_i.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""Backtracking algorithm: Permutation I"""
# When the state length equals the number of elements, record the solution
if len(state) == len(choices):
res.append(list(state))
return
# Traverse all choices
for i, choice in enumerate(choices):
# Pruning: do not allow repeated selection of elements
if not selected[i]:
# Attempt: make a choice, update the state
selected[i] = True
state.append(choice)
# Proceed to the next round of selection
backtrack(state, choices, selected, res)
# Retract: undo the choice, restore to the previous state
selected[i] = False
state.pop()
def permutations_i(nums: list[int]) -> list[list[int]]:
"""Permutation I"""
res = []
backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
return res
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 2, 3]
res = permutations_i(nums)
print(f"Input array nums = {nums}")
print(f"All permutations res = {res}")

View File

@@ -0,0 +1,46 @@
"""
File: permutations_ii.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""Backtracking algorithm: Permutation II"""
# When the state length equals the number of elements, record the solution
if len(state) == len(choices):
res.append(list(state))
return
# Traverse all choices
duplicated = set[int]()
for i, choice in enumerate(choices):
# Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements
if not selected[i] and choice not in duplicated:
# Attempt: make a choice, update the state
duplicated.add(choice) # Record selected element values
selected[i] = True
state.append(choice)
# Proceed to the next round of selection
backtrack(state, choices, selected, res)
# Retract: undo the choice, restore to the previous state
selected[i] = False
state.pop()
def permutations_ii(nums: list[int]) -> list[list[int]]:
"""Permutation II"""
res = []
backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
return res
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 2, 2]
res = permutations_ii(nums)
print(f"Input array nums = {nums}")
print(f"All permutations res = {res}")

View File

@@ -0,0 +1,36 @@
"""
File: preorder_traversal_i_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Pre-order traversal: Example one"""
if root is None:
return
if root.val == 7:
# Record solution
res.append(root)
pre_order(root.left)
pre_order(root.right)
"""Driver Code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print_tree(root)
# Pre-order traversal
res = list[TreeNode]()
pre_order(root)
print("\nOutput all nodes with value 7")
print([node.val for node in res])

View File

@@ -0,0 +1,42 @@
"""
File: preorder_traversal_ii_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Pre-order traversal: Example two"""
if root is None:
return
# Attempt
path.append(root)
if root.val == 7:
# Record solution
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# Retract
path.pop()
"""Driver Code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print_tree(root)
# Pre-order traversal
path = list[TreeNode]()
res = list[list[TreeNode]]()
pre_order(root)
print("\nOutput all root-to-node 7 paths")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,43 @@
"""
File: preorder_traversal_iii_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Pre-order traversal: Example three"""
# Pruning
if root is None or root.val == 3:
return
# Attempt
path.append(root)
if root.val == 7:
# Record solution
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# Retract
path.pop()
"""Driver Code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print_tree(root)
# Pre-order traversal
path = list[TreeNode]()
res = list[list[TreeNode]]()
pre_order(root)
print("\nOutput all root-to-node 7 paths, not including nodes with value 3")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,71 @@
"""
File: preorder_traversal_iii_template.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def is_solution(state: list[TreeNode]) -> bool:
"""Determine if the current state is a solution"""
return state and state[-1].val == 7
def record_solution(state: list[TreeNode], res: list[list[TreeNode]]):
"""Record solution"""
res.append(list(state))
def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
"""Determine if the choice is legal under the current state"""
return choice is not None and choice.val != 3
def make_choice(state: list[TreeNode], choice: TreeNode):
"""Update state"""
state.append(choice)
def undo_choice(state: list[TreeNode], choice: TreeNode):
"""Restore state"""
state.pop()
def backtrack(
state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]
):
"""Backtracking algorithm: Example three"""
# Check if it's a solution
if is_solution(state):
# Record solution
record_solution(state, res)
# Traverse all choices
for choice in choices:
# Pruning: check if the choice is legal
if is_valid(state, choice):
# Attempt: make a choice, update the state
make_choice(state, choice)
# Proceed to the next round of selection
backtrack(state, [choice.left, choice.right], res)
# Retract: undo the choice, restore to the previous state
undo_choice(state, choice)
"""Driver Code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print_tree(root)
# Backtracking algorithm
res = []
backtrack(state=[], choices=[root], res=res)
print("\nOutput all root-to-node 7 paths, requiring paths not to include nodes with value 3")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,48 @@
"""
File: subset_sum_i.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]
):
"""Backtracking algorithm: Subset Sum I"""
# When the subset sum equals target, record the solution
if target == 0:
res.append(list(state))
return
# Traverse all choices
# Pruning two: start traversing from start to avoid generating duplicate subsets
for i in range(start, len(choices)):
# Pruning one: if the subset sum exceeds target, end the loop immediately
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
if target - choices[i] < 0:
break
# Attempt: make a choice, update target, start
state.append(choices[i])
# Proceed to the next round of selection
backtrack(state, target - choices[i], choices, i, res)
# Retract: undo the choice, restore to the previous state
state.pop()
def subset_sum_i(nums: list[int], target: int) -> list[list[int]]:
"""Solve Subset Sum I"""
state = [] # State (subset)
nums.sort() # Sort nums
start = 0 # Start point for traversal
res = [] # Result list (subset list)
backtrack(state, target, nums, start, res)
return res
"""Driver Code"""
if __name__ == "__main__":
nums = [3, 4, 5]
target = 9
res = subset_sum_i(nums, target)
print(f"Input array nums = {nums}, target = {target}")
print(f"All subsets equal to {target} res = {res}")

View File

@@ -0,0 +1,50 @@
"""
File: subset_sum_i_naive.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int],
target: int,
total: int,
choices: list[int],
res: list[list[int]],
):
"""Backtracking algorithm: Subset Sum I"""
# When the subset sum equals target, record the solution
if total == target:
res.append(list(state))
return
# Traverse all choices
for i in range(len(choices)):
# Pruning: if the subset sum exceeds target, skip that choice
if total + choices[i] > target:
continue
# Attempt: make a choice, update elements and total
state.append(choices[i])
# Proceed to the next round of selection
backtrack(state, target, total + choices[i], choices, res)
# Retract: undo the choice, restore to the previous state
state.pop()
def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:
"""Solve Subset Sum I (including duplicate subsets)"""
state = [] # State (subset)
total = 0 # Subset sum
res = [] # Result list (subset list)
backtrack(state, target, total, nums, res)
return res
"""Driver Code"""
if __name__ == "__main__":
nums = [3, 4, 5]
target = 9
res = subset_sum_i_naive(nums, target)
print(f"Input array nums = {nums}, target = {target}")
print(f"All subsets equal to {target} res = {res}")
print(f"Please note that the result of this method includes duplicate sets")

View File

@@ -0,0 +1,52 @@
"""
File: subset_sum_ii.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]
):
"""Backtracking algorithm: Subset Sum II"""
# When the subset sum equals target, record the solution
if target == 0:
res.append(list(state))
return
# Traverse all choices
# Pruning two: start traversing from start to avoid generating duplicate subsets
# Pruning three: start traversing from start to avoid repeatedly selecting the same element
for i in range(start, len(choices)):
# Pruning one: if the subset sum exceeds target, end the loop immediately
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
if target - choices[i] < 0:
break
# Pruning four: if the element equals the left element, it indicates that the search branch is repeated, skip it
if i > start and choices[i] == choices[i - 1]:
continue
# Attempt: make a choice, update target, start
state.append(choices[i])
# Proceed to the next round of selection
backtrack(state, target - choices[i], choices, i + 1, res)
# Retract: undo the choice, restore to the previous state
state.pop()
def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:
"""Solve Subset Sum II"""
state = [] # State (subset)
nums.sort() # Sort nums
start = 0 # Start point for traversal
res = [] # Result list (subset list)
backtrack(state, target, nums, start, res)
return res
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 4, 5]
target = 9
res = subset_sum_ii(nums, target)
print(f"Input array nums = {nums}, target = {target}")
print(f"All subsets equal to {target} res = {res}")

View File

@@ -0,0 +1,65 @@
"""
File: iteration.py
Created Time: 2023-08-24
Author: krahets (krahets@163.com)
"""
def for_loop(n: int) -> int:
"""for loop"""
res = 0
# Loop sum 1, 2, ..., n-1, n
for i in range(1, n + 1):
res += i
return res
def while_loop(n: int) -> int:
"""while loop"""
res = 0
i = 1 # Initialize condition variable
# Loop sum 1, 2, ..., n-1, n
while i <= n:
res += i
i += 1 # Update condition variable
return res
def while_loop_ii(n: int) -> int:
"""while loop (two updates)"""
res = 0
i = 1 # Initialize condition variable
# Loop sum 1, 4, 10, ...
while i <= n:
res += i
# Update condition variable
i += 1
i *= 2
return res
def nested_for_loop(n: int) -> str:
"""Double for loop"""
res = ""
# Loop i = 1, 2, ..., n-1, n
for i in range(1, n + 1):
# Loop j = 1, 2, ..., n-1, n
for j in range(1, n + 1):
res += f"({i}, {j}), "
return res
"""Driver Code"""
if __name__ == "__main__":
n = 5
res = for_loop(n)
print(f"\nfor loop sum result res = {res}")
res = while_loop(n)
print(f"\nwhile loop sum result res = {res}")
res = while_loop_ii(n)
print(f"\nwhile loop (two updates) sum result res = {res}")
res = nested_for_loop(n)
print(f"\nDouble for loop traversal result {res}")

View File

@@ -0,0 +1,69 @@
"""
File: recursion.py
Created Time: 2023-08-24
Author: krahets (krahets@163.com)
"""
def recur(n: int) -> int:
"""Recursion"""
# Termination condition
if n == 1:
return 1
# Recursive: recursive call
res = recur(n - 1)
# Return: return result
return n + res
def for_loop_recur(n: int) -> int:
"""Simulate recursion with iteration"""
# Use an explicit stack to simulate the system call stack
stack = []
res = 0
# Recursive: recursive call
for i in range(n, 0, -1):
# Simulate "recursive" by "pushing onto the stack"
stack.append(i)
# Return: return result
while stack:
# Simulate "return" by "popping from the stack"
res += stack.pop()
# res = 1+2+3+...+n
return res
def tail_recur(n, res):
"""Tail recursion"""
# Termination condition
if n == 0:
return res
# Tail recursive call
return tail_recur(n - 1, res + n)
def fib(n: int) -> int:
"""Fibonacci sequence: Recursion"""
# Termination condition f(1) = 0, f(2) = 1
if n == 1 or n == 2:
return n - 1
# Recursive call f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# Return result f(n)
return res
"""Driver Code"""
if __name__ == "__main__":
n = 5
res = recur(n)
print(f"\nRecursive function sum result res = {res}")
res = for_loop_recur(n)
print(f"\nSimulate recursion with iteration sum result res = {res}")
res = tail_recur(n, 0)
print(f"\nTail recursive function sum result res = {res}")
res = fib(n)
print(f"\nThe n th term of the Fibonacci sequence is {res}")

View File

@@ -0,0 +1,90 @@
"""
File: space_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, TreeNode, print_tree
def function() -> int:
"""Function"""
# Perform some operations
return 0
def constant(n: int):
"""Constant complexity"""
# Constants, variables, objects occupy O(1) space
a = 0
nums = [0] * 10000
node = ListNode(0)
# Variables in a loop occupy O(1) space
for _ in range(n):
c = 0
# Functions in a loop occupy O(1) space
for _ in range(n):
function()
def linear(n: int):
"""Linear complexity"""
# A list of length n occupies O(n) space
nums = [0] * n
# A hash table of length n occupies O(n) space
hmap = dict[int, str]()
for i in range(n):
hmap[i] = str(i)
def linear_recur(n: int):
"""Linear complexity (recursive implementation)"""
print("Recursive n =", n)
if n == 1:
return
linear_recur(n - 1)
def quadratic(n: int):
"""Quadratic complexity"""
# A two-dimensional list occupies O(n^2) space
num_matrix = [[0] * n for _ in range(n)]
def quadratic_recur(n: int) -> int:
"""Quadratic complexity (recursive implementation)"""
if n <= 0:
return 0
# Array nums length = n, n-1, ..., 2, 1
nums = [0] * n
return quadratic_recur(n - 1)
def build_tree(n: int) -> TreeNode | None:
"""Exponential complexity (building a full binary tree)"""
if n == 0:
return None
root = TreeNode(0)
root.left = build_tree(n - 1)
root.right = build_tree(n - 1)
return root
"""Driver Code"""
if __name__ == "__main__":
n = 5
# Constant complexity
constant(n)
# Linear complexity
linear(n)
linear_recur(n)
# Quadratic complexity
quadratic(n)
quadratic_recur(n)
# Exponential complexity
root = build_tree(n)
print_tree(root)

View File

@@ -0,0 +1,151 @@
"""
File: time_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
def constant(n: int) -> int:
"""Constant complexity"""
count = 0
size = 100000
for _ in range(size):
count += 1
return count
def linear(n: int) -> int:
"""Linear complexity"""
count = 0
for _ in range(n):
count += 1
return count
def array_traversal(nums: list[int]) -> int:
"""Linear complexity (traversing an array)"""
count = 0
# Loop count is proportional to the length of the array
for num in nums:
count += 1
return count
def quadratic(n: int) -> int:
"""Quadratic complexity"""
count = 0
# Loop count is squared in relation to the data size n
for i in range(n):
for j in range(n):
count += 1
return count
def bubble_sort(nums: list[int]) -> int:
"""Quadratic complexity (bubble sort)"""
count = 0 # Counter
# Outer loop: unsorted range is [0, i]
for i in range(len(nums) - 1, 0, -1):
# Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
for j in range(i):
if nums[j] > nums[j + 1]:
# Swap nums[j] and nums[j + 1]
tmp: int = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = tmp
count += 3 # Element swap includes 3 individual operations
return count
def exponential(n: int) -> int:
"""Exponential complexity (loop implementation)"""
count = 0
base = 1
# Cells split into two every round, forming the sequence 1, 2, 4, 8, ..., 2^(n-1)
for _ in range(n):
for _ in range(base):
count += 1
base *= 2
# count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count
def exp_recur(n: int) -> int:
"""Exponential complexity (recursive implementation)"""
if n == 1:
return 1
return exp_recur(n - 1) + exp_recur(n - 1) + 1
def logarithmic(n: int) -> int:
"""Logarithmic complexity (loop implementation)"""
count = 0
while n > 1:
n = n / 2
count += 1
return count
def log_recur(n: int) -> int:
"""Logarithmic complexity (recursive implementation)"""
if n <= 1:
return 0
return log_recur(n / 2) + 1
def linear_log_recur(n: int) -> int:
"""Linear logarithmic complexity"""
if n <= 1:
return 1
count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)
for _ in range(n):
count += 1
return count
def factorial_recur(n: int) -> int:
"""Factorial complexity (recursive implementation)"""
if n == 0:
return 1
count = 0
# From 1 split into n
for _ in range(n):
count += factorial_recur(n - 1)
return count
"""Driver Code"""
if __name__ == "__main__":
# Can modify n to experience the trend of operation count changes under various complexities
n = 8
print("Input data size n =", n)
count: int = constant(n)
print("Constant complexity operation count =", count)
count: int = linear(n)
print("Linear complexity operation count =", count)
count: int = array_traversal([0] * n)
print("Linear complexity (traversing an array) operation count =", count)
count: int = quadratic(n)
print("Quadratic complexity operation count =", count)
nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1]
count: int = bubble_sort(nums)
print("Quadratic complexity (bubble sort) operation count =", count)
count: int = exponential(n)
print("Exponential complexity (loop implementation) operation count =", count)
count: int = exp_recur(n)
print("Exponential complexity (recursive implementation) operation count =", count)
count: int = logarithmic(n)
print("Logarithmic complexity (loop implementation) operation count =", count)
count: int = log_recur(n)
print("Logarithmic complexity (recursive implementation) operation count =", count)
count: int = linear_log_recur(n)
print("Linear logarithmic complexity (recursive implementation) operation count =", count)
count: int = factorial_recur(n)
print("Factorial complexity (recursive implementation) operation count =", count)

View File

@@ -0,0 +1,36 @@
"""
File: worst_best_time_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import random
def random_numbers(n: int) -> list[int]:
"""Generate an array with elements: 1, 2, ..., n, order shuffled"""
# Generate array nums =: 1, 2, 3, ..., n
nums = [i for i in range(1, n + 1)]
# Randomly shuffle array elements
random.shuffle(nums)
return nums
def find_one(nums: list[int]) -> int:
"""Find the index of number 1 in array nums"""
for i in range(len(nums)):
# When element 1 is at the start of the array, achieve best time complexity O(1)
# When element 1 is at the end of the array, achieve worst time complexity O(n)
if nums[i] == 1:
return i
return -1
"""Driver Code"""
if __name__ == "__main__":
for i in range(10):
n = 100
nums: list[int] = random_numbers(n)
index: int = find_one(nums)
print("\nThe array [ 1, 2, ..., n ] after being shuffled =", nums)
print("Index of number 1 =", index)

View File

@@ -0,0 +1,40 @@
"""
File: binary_search_recur.py
Created Time: 2023-07-17
Author: krahets (krahets@163.com)
"""
def dfs(nums: list[int], target: int, i: int, j: int) -> int:
"""Binary search: problem f(i, j)"""
# If the interval is empty, indicating no target element, return -1
if i > j:
return -1
# Calculate midpoint index m
m = (i + j) // 2
if nums[m] < target:
# Recursive subproblem f(m+1, j)
return dfs(nums, target, m + 1, j)
elif nums[m] > target:
# Recursive subproblem f(i, m-1)
return dfs(nums, target, i, m - 1)
else:
# Found the target element, thus return its index
return m
def binary_search(nums: list[int], target: int) -> int:
"""Binary search"""
n = len(nums)
# Solve problem f(0, n-1)
return dfs(nums, target, 0, n - 1)
"""Driver Code"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (double closed interval)
index = binary_search(nums, target)
print("Index of target element 6 =", index)

View File

@@ -0,0 +1,54 @@
"""
File: build_tree.py
Created Time: 2023-07-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
def dfs(
preorder: list[int],
inorder_map: dict[int, int],
i: int,
l: int,
r: int,
) -> TreeNode | None:
"""Build binary tree: Divide and conquer"""
# Terminate when subtree interval is empty
if r - l < 0:
return None
# Initialize root node
root = TreeNode(preorder[i])
# Query m to divide left and right subtrees
m = inorder_map[preorder[i]]
# Subproblem: build left subtree
root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)
# Subproblem: build right subtree
root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)
# Return root node
return root
def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:
"""Build binary tree"""
# Initialize hash table, storing in-order elements to indices mapping
inorder_map = {val: i for i, val in enumerate(inorder)}
root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)
return root
"""Driver Code"""
if __name__ == "__main__":
preorder = [3, 9, 2, 1, 7]
inorder = [9, 3, 1, 2, 7]
print(f"Pre-order traversal = {preorder}")
print(f"In-order traversal = {inorder}")
root = build_tree(preorder, inorder)
print("The built binary tree is:")
print_tree(root)

View File

@@ -0,0 +1,53 @@
"""
File: hanota.py
Created Time: 2023-07-16
Author: krahets (krahets@163.com)
"""
def move(src: list[int], tar: list[int]):
"""Move a disc"""
# Take out a disc from the top of src
pan = src.pop()
# Place the disc on top of tar
tar.append(pan)
def dfs(i: int, src: list[int], buf: list[int], tar: list[int]):
"""Solve the Tower of Hanoi problem f(i)"""
# If only one disc remains on src, move it to tar
if i == 1:
move(src, tar)
return
# Subproblem f(i-1): move the top i-1 discs from src with the help of tar to buf
dfs(i - 1, src, tar, buf)
# Subproblem f(1): move the remaining one disc from src to tar
move(src, tar)
# Subproblem f(i-1): move the top i-1 discs from buf with the help of src to tar
dfs(i - 1, buf, src, tar)
def solve_hanota(A: list[int], B: list[int], C: list[int]):
"""Solve the Tower of Hanoi problem"""
n = len(A)
# Move the top n discs from A with the help of B to C
dfs(n, A, B, C)
"""Driver Code"""
if __name__ == "__main__":
# The tail of the list is the top of the pillar
A = [5, 4, 3, 2, 1]
B = []
C = []
print("Initial state:")
print(f"A = {A}")
print(f"B = {B}")
print(f"C = {C}")
solve_hanota(A, B, C)
print("After the discs are moved:")
print(f"A = {A}")
print(f"B = {B}")
print(f"C = {C}")

View File

@@ -0,0 +1,37 @@
"""
File: climbing_stairs_backtrack.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:
"""Backtracking"""
# When climbing to the nth step, add 1 to the number of solutions
if state == n:
res[0] += 1
# Traverse all choices
for choice in choices:
# Pruning: do not allow climbing beyond the nth step
if state + choice > n:
continue
# Attempt: make a choice, update the state
backtrack(choices, state + choice, n, res)
# Retract
def climbing_stairs_backtrack(n: int) -> int:
"""Climbing stairs: Backtracking"""
choices = [1, 2] # Can choose to climb up 1 step or 2 steps
state = 0 # Start climbing from the 0th step
res = [0] # Use res[0] to record the number of solutions
backtrack(choices, state, n, res)
return res[0]
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_backtrack(n)
print(f"Climb {n} steps, there are {res} solutions in total")

View File

@@ -0,0 +1,29 @@
"""
File: climbing_stairs_constraint_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def climbing_stairs_constraint_dp(n: int) -> int:
"""Constrained climbing stairs: Dynamic programming"""
if n == 1 or n == 2:
return 1
# Initialize dp table, used to store subproblem solutions
dp = [[0] * 3 for _ in range(n + 1)]
# Initial state: preset the smallest subproblem solution
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# State transition: gradually solve larger subproblems from smaller ones
for i in range(3, n + 1):
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
return dp[n][1] + dp[n][2]
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_constraint_dp(n)
print(f"Climb {n} steps, there are {res} solutions in total")

View File

@@ -0,0 +1,28 @@
"""
File: climbing_stairs_dfs.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def dfs(i: int) -> int:
"""Search"""
# Known dp[1] and dp[2], return them
if i == 1 or i == 2:
return i
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1) + dfs(i - 2)
return count
def climbing_stairs_dfs(n: int) -> int:
"""Climbing stairs: Search"""
return dfs(n)
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs(n)
print(f"Climb {n} steps, there are {res} solutions in total")

View File

@@ -0,0 +1,35 @@
"""
File: climbing_stairs_dfs_mem.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def dfs(i: int, mem: list[int]) -> int:
"""Memoized search"""
# Known dp[1] and dp[2], return them
if i == 1 or i == 2:
return i
# If there is a record for dp[i], return it
if mem[i] != -1:
return mem[i]
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# Record dp[i]
mem[i] = count
return count
def climbing_stairs_dfs_mem(n: int) -> int:
"""Climbing stairs: Memoized search"""
# mem[i] records the total number of solutions for climbing to the ith step, -1 means no record
mem = [-1] * (n + 1)
return dfs(n, mem)
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs_mem(n)
print(f"Climb {n} steps, there are {res} solutions in total")

View File

@@ -0,0 +1,40 @@
"""
File: climbing_stairs_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def climbing_stairs_dp(n: int) -> int:
"""Climbing stairs: Dynamic programming"""
if n == 1 or n == 2:
return n
# Initialize dp table, used to store subproblem solutions
dp = [0] * (n + 1)
# Initial state: preset the smallest subproblem solution
dp[1], dp[2] = 1, 2
# State transition: gradually solve larger subproblems from smaller ones
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
def climbing_stairs_dp_comp(n: int) -> int:
"""Climbing stairs: Space-optimized dynamic programming"""
if n == 1 or n == 2:
return n
a, b = 1, 2
for _ in range(3, n + 1):
a, b = b, a + b
return b
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dp(n)
print(f"Climb {n} steps, there are {res} solutions in total")
res = climbing_stairs_dp_comp(n)
print(f"Climb {n} steps, there are {res} solutions in total")

View File

@@ -0,0 +1,60 @@
"""
File: coin_change.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def coin_change_dp(coins: list[int], amt: int) -> int:
"""Coin change: Dynamic programming"""
n = len(coins)
MAX = amt + 1
# Initialize dp table
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# State transition: first row and first column
for a in range(1, amt + 1):
dp[0][a] = MAX
# State transition: the rest of the rows and columns
for i in range(1, n + 1):
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeding the target amount, do not choose coin i
dp[i][a] = dp[i - 1][a]
else:
# The smaller value between not choosing and choosing coin i
dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)
return dp[n][amt] if dp[n][amt] != MAX else -1
def coin_change_dp_comp(coins: list[int], amt: int) -> int:
"""Coin change: Space-optimized dynamic programming"""
n = len(coins)
MAX = amt + 1
# Initialize dp table
dp = [MAX] * (amt + 1)
dp[0] = 0
# State transition
for i in range(1, n + 1):
# Traverse in order
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeding the target amount, do not choose coin i
dp[a] = dp[a]
else:
# The smaller value between not choosing and choosing coin i
dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)
return dp[amt] if dp[amt] != MAX else -1
"""Driver Code"""
if __name__ == "__main__":
coins = [1, 2, 5]
amt = 4
# Dynamic programming
res = coin_change_dp(coins, amt)
print(f"Minimum number of coins required to reach the target amount = {res}")
# Space-optimized dynamic programming
res = coin_change_dp_comp(coins, amt)
print(f"Minimum number of coins required to reach the target amount = {res}")

View File

@@ -0,0 +1,58 @@
"""
File: coin_change_ii.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def coin_change_ii_dp(coins: list[int], amt: int) -> int:
"""Coin change II: Dynamic programming"""
n = len(coins)
# Initialize dp table
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# Initialize first column
for i in range(n + 1):
dp[i][0] = 1
# State transition
for i in range(1, n + 1):
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeding the target amount, do not choose coin i
dp[i][a] = dp[i - 1][a]
else:
# The sum of the two options of not choosing and choosing coin i
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
return dp[n][amt]
def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:
"""Coin change II: Space-optimized dynamic programming"""
n = len(coins)
# Initialize dp table
dp = [0] * (amt + 1)
dp[0] = 1
# State transition
for i in range(1, n + 1):
# Traverse in order
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeding the target amount, do not choose coin i
dp[a] = dp[a]
else:
# The sum of the two options of not choosing and choosing coin i
dp[a] = dp[a] + dp[a - coins[i - 1]]
return dp[amt]
"""Driver Code"""
if __name__ == "__main__":
coins = [1, 2, 5]
amt = 5
# Dynamic programming
res = coin_change_ii_dp(coins, amt)
print(f"The number of coin combinations to make up the target amount is {res}")
# Space-optimized dynamic programming
res = coin_change_ii_dp_comp(coins, amt)
print(f"The number of coin combinations to make up the target amount is {res}")

View File

@@ -0,0 +1,123 @@
"""
File: edit_distancde.py
Created Time: 2023-07-04
Author: krahets (krahets@163.com)
"""
def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int:
"""Edit distance: Brute force search"""
# If both s and t are empty, return 0
if i == 0 and j == 0:
return 0
# If s is empty, return the length of t
if i == 0:
return j
# If t is empty, return the length of s
if j == 0:
return i
# If the two characters are equal, skip these two characters
if s[i - 1] == t[j - 1]:
return edit_distance_dfs(s, t, i - 1, j - 1)
# The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
insert = edit_distance_dfs(s, t, i, j - 1)
delete = edit_distance_dfs(s, t, i - 1, j)
replace = edit_distance_dfs(s, t, i - 1, j - 1)
# Return the minimum number of edits
return min(insert, delete, replace) + 1
def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int:
"""Edit distance: Memoized search"""
# If both s and t are empty, return 0
if i == 0 and j == 0:
return 0
# If s is empty, return the length of t
if i == 0:
return j
# If t is empty, return the length of s
if j == 0:
return i
# If there is a record, return it
if mem[i][j] != -1:
return mem[i][j]
# If the two characters are equal, skip these two characters
if s[i - 1] == t[j - 1]:
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
insert = edit_distance_dfs_mem(s, t, mem, i, j - 1)
delete = edit_distance_dfs_mem(s, t, mem, i - 1, j)
replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# Record and return the minimum number of edits
mem[i][j] = min(insert, delete, replace) + 1
return mem[i][j]
def edit_distance_dp(s: str, t: str) -> int:
"""Edit distance: Dynamic programming"""
n, m = len(s), len(t)
dp = [[0] * (m + 1) for _ in range(n + 1)]
# State transition: first row and first column
for i in range(1, n + 1):
dp[i][0] = i
for j in range(1, m + 1):
dp[0][j] = j
# State transition: the rest of the rows and columns
for i in range(1, n + 1):
for j in range(1, m + 1):
if s[i - 1] == t[j - 1]:
# If the two characters are equal, skip these two characters
dp[i][j] = dp[i - 1][j - 1]
else:
# The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
return dp[n][m]
def edit_distance_dp_comp(s: str, t: str) -> int:
"""Edit distance: Space-optimized dynamic programming"""
n, m = len(s), len(t)
dp = [0] * (m + 1)
# State transition: first row
for j in range(1, m + 1):
dp[j] = j
# State transition: the rest of the rows
for i in range(1, n + 1):
# State transition: first column
leftup = dp[0] # Temporarily store dp[i-1, j-1]
dp[0] += 1
# State transition: the rest of the columns
for j in range(1, m + 1):
temp = dp[j]
if s[i - 1] == t[j - 1]:
# If the two characters are equal, skip these two characters
dp[j] = leftup
else:
# The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
dp[j] = min(dp[j - 1], dp[j], leftup) + 1
leftup = temp # Update for the next round of dp[i-1, j-1]
return dp[m]
"""Driver Code"""
if __name__ == "__main__":
s = "bag"
t = "pack"
n, m = len(s), len(t)
# Brute force search
res = edit_distance_dfs(s, t, n, m)
print(f"To change {s} to {t}, the minimum number of edits required is {res}")
# Memoized search
mem = [[-1] * (m + 1) for _ in range(n + 1)]
res = edit_distance_dfs_mem(s, t, mem, n, m)
print(f"To change {s} to {t}, the minimum number of edits required is {res}")
# Dynamic programming
res = edit_distance_dp(s, t)
print(f"To change {s} to {t}, the minimum number of edits required is {res}")
# Space-optimized dynamic programming
res = edit_distance_dp_comp(s, t)
print(f"To change {s} to {t}, the minimum number of edits required is {res}")

View File

@@ -0,0 +1,101 @@
"""
File: knapsack.py
Created Time: 2023-07-03
Author: krahets (krahets@163.com)
"""
def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:
"""0-1 Knapsack: Brute force search"""
# If all items have been chosen or the knapsack has no remaining capacity, return value 0
if i == 0 or c == 0:
return 0
# If exceeding the knapsack capacity, can only choose not to put it in the knapsack
if wgt[i - 1] > c:
return knapsack_dfs(wgt, val, i - 1, c)
# Calculate the maximum value of not putting in and putting in item i
no = knapsack_dfs(wgt, val, i - 1, c)
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
# Return the greater value of the two options
return max(no, yes)
def knapsack_dfs_mem(
wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int
) -> int:
"""0-1 Knapsack: Memoized search"""
# If all items have been chosen or the knapsack has no remaining capacity, return value 0
if i == 0 or c == 0:
return 0
# If there is a record, return it
if mem[i][c] != -1:
return mem[i][c]
# If exceeding the knapsack capacity, can only choose not to put it in the knapsack
if wgt[i - 1] > c:
return knapsack_dfs_mem(wgt, val, mem, i - 1, c)
# Calculate the maximum value of not putting in and putting in item i
no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)
yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]
# Record and return the greater value of the two options
mem[i][c] = max(no, yes)
return mem[i][c]
def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
"""0-1 Knapsack: Dynamic programming"""
n = len(wgt)
# Initialize dp table
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# State transition
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeding the knapsack capacity, do not choose item i
dp[i][c] = dp[i - 1][c]
else:
# The greater value between not choosing and choosing item i
dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])
return dp[n][cap]
def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""0-1 Knapsack: Space-optimized dynamic programming"""
n = len(wgt)
# Initialize dp table
dp = [0] * (cap + 1)
# State transition
for i in range(1, n + 1):
# Traverse in reverse order
for c in range(cap, 0, -1):
if wgt[i - 1] > c:
# If exceeding the knapsack capacity, do not choose item i
dp[c] = dp[c]
else:
# The greater value between not choosing and choosing item i
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""Driver Code"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = len(wgt)
# Brute force search
res = knapsack_dfs(wgt, val, n, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")
# Memoized search
mem = [[-1] * (cap + 1) for _ in range(n + 1)]
res = knapsack_dfs_mem(wgt, val, mem, n, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")
# Dynamic programming
res = knapsack_dp(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")
# Space-optimized dynamic programming
res = knapsack_dp_comp(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")

View File

@@ -0,0 +1,43 @@
"""
File: min_cost_climbing_stairs_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def min_cost_climbing_stairs_dp(cost: list[int]) -> int:
"""Climbing stairs with minimum cost: Dynamic programming"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
# Initialize dp table, used to store subproblem solutions
dp = [0] * (n + 1)
# Initial state: preset the smallest subproblem solution
dp[1], dp[2] = cost[1], cost[2]
# State transition: gradually solve larger subproblems from smaller ones
for i in range(3, n + 1):
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]
return dp[n]
def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:
"""Climbing stairs with minimum cost: Space-optimized dynamic programming"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
a, b = cost[1], cost[2]
for i in range(3, n + 1):
a, b = b, min(a, b) + cost[i]
return b
"""Driver Code"""
if __name__ == "__main__":
cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]
print(f"Enter the list of stair costs as {cost}")
res = min_cost_climbing_stairs_dp(cost)
print(f"Minimum cost to climb the stairs {res}")
res = min_cost_climbing_stairs_dp_comp(cost)
print(f"Minimum cost to climb the stairs {res}")

View File

@@ -0,0 +1,104 @@
"""
File: min_path_sum.py
Created Time: 2023-07-04
Author: krahets (krahets@163.com)
"""
from math import inf
def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:
"""Minimum path sum: Brute force search"""
# If it's the top-left cell, terminate the search
if i == 0 and j == 0:
return grid[0][0]
# If the row or column index is out of bounds, return a +∞ cost
if i < 0 or j < 0:
return inf
# Calculate the minimum path cost from the top-left to (i-1, j) and (i, j-1)
up = min_path_sum_dfs(grid, i - 1, j)
left = min_path_sum_dfs(grid, i, j - 1)
# Return the minimum path cost from the top-left to (i, j)
return min(left, up) + grid[i][j]
def min_path_sum_dfs_mem(
grid: list[list[int]], mem: list[list[int]], i: int, j: int
) -> int:
"""Minimum path sum: Memoized search"""
# If it's the top-left cell, terminate the search
if i == 0 and j == 0:
return grid[0][0]
# If the row or column index is out of bounds, return a +∞ cost
if i < 0 or j < 0:
return inf
# If there is a record, return it
if mem[i][j] != -1:
return mem[i][j]
# The minimum path cost from the left and top cells
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
# Record and return the minimum path cost from the top-left to (i, j)
mem[i][j] = min(left, up) + grid[i][j]
return mem[i][j]
def min_path_sum_dp(grid: list[list[int]]) -> int:
"""Minimum path sum: Dynamic programming"""
n, m = len(grid), len(grid[0])
# Initialize dp table
dp = [[0] * m for _ in range(n)]
dp[0][0] = grid[0][0]
# State transition: first row
for j in range(1, m):
dp[0][j] = dp[0][j - 1] + grid[0][j]
# State transition: first column
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + grid[i][0]
# State transition: the rest of the rows and columns
for i in range(1, n):
for j in range(1, m):
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]
return dp[n - 1][m - 1]
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:
"""Minimum path sum: Space-optimized dynamic programming"""
n, m = len(grid), len(grid[0])
# Initialize dp table
dp = [0] * m
# State transition: first row
dp[0] = grid[0][0]
for j in range(1, m):
dp[j] = dp[j - 1] + grid[0][j]
# State transition: the rest of the rows
for i in range(1, n):
# State transition: first column
dp[0] = dp[0] + grid[i][0]
# State transition: the rest of the columns
for j in range(1, m):
dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]
return dp[m - 1]
"""Driver Code"""
if __name__ == "__main__":
grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]]
n, m = len(grid), len(grid[0])
# Brute force search
res = min_path_sum_dfs(grid, n - 1, m - 1)
print(f"The minimum path sum from the top-left to the bottom-right corner is {res}")
# Memoized search
mem = [[-1] * m for _ in range(n)]
res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1)
print(f"The minimum path sum from the top-left to the bottom-right corner is {res}")
# Dynamic programming
res = min_path_sum_dp(grid)
print(f"The minimum path sum from the top-left to the bottom-right corner is {res}")
# Space-optimized dynamic programming
res = min_path_sum_dp_comp(grid)
print(f"The minimum path sum from the top-left to the bottom-right corner is {res}")

View File

@@ -0,0 +1,55 @@
"""
File: unbounded_knapsack.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
"""Complete knapsack: Dynamic programming"""
n = len(wgt)
# Initialize dp table
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# State transition
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeding the knapsack capacity, do not choose item i
dp[i][c] = dp[i - 1][c]
else:
# The greater value between not choosing and choosing item i
dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])
return dp[n][cap]
def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""Complete knapsack: Space-optimized dynamic programming"""
n = len(wgt)
# Initialize dp table
dp = [0] * (cap + 1)
# State transition
for i in range(1, n + 1):
# Traverse in order
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeding the knapsack capacity, do not choose item i
dp[c] = dp[c]
else:
# The greater value between not choosing and choosing item i
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""Driver Code"""
if __name__ == "__main__":
wgt = [1, 2, 3]
val = [5, 11, 15]
cap = 4
# Dynamic programming
res = unbounded_knapsack_dp(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")
# Space-optimized dynamic programming
res = unbounded_knapsack_dp_comp(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")

View File

@@ -0,0 +1,111 @@
"""
File: graph_adjacency_list.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vals_to_vets
class GraphAdjList:
"""Undirected graph class based on adjacency list"""
def __init__(self, edges: list[list[Vertex]]):
"""Constructor"""
# Adjacency list, key: vertex, value: all adjacent vertices of that vertex
self.adj_list = dict[Vertex, list[Vertex]]()
# Add all vertices and edges
for edge in edges:
self.add_vertex(edge[0])
self.add_vertex(edge[1])
self.add_edge(edge[0], edge[1])
def size(self) -> int:
"""Get the number of vertices"""
return len(self.adj_list)
def add_edge(self, vet1: Vertex, vet2: Vertex):
"""Add edge"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# Add edge vet1 - vet2
self.adj_list[vet1].append(vet2)
self.adj_list[vet2].append(vet1)
def remove_edge(self, vet1: Vertex, vet2: Vertex):
"""Remove edge"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# Remove edge vet1 - vet2
self.adj_list[vet1].remove(vet2)
self.adj_list[vet2].remove(vet1)
def add_vertex(self, vet: Vertex):
"""Add vertex"""
if vet in self.adj_list:
return
# Add a new linked list to the adjacency list
self.adj_list[vet] = []
def remove_vertex(self, vet: Vertex):
"""Remove vertex"""
if vet not in self.adj_list:
raise ValueError()
# Remove the vertex vet's corresponding linked list from the adjacency list
self.adj_list.pop(vet)
# Traverse other vertices' linked lists, removing all edges containing vet
for vertex in self.adj_list:
if vet in self.adj_list[vertex]:
self.adj_list[vertex].remove(vet)
def print(self):
"""Print the adjacency list"""
print("Adjacency list =")
for vertex in self.adj_list:
tmp = [v.val for v in self.adj_list[vertex]]
print(f"{vertex.val}: {tmp},")
"""Driver Code"""
if __name__ == "__main__":
# Initialize undirected graph
v = vals_to_vets([1, 3, 2, 5, 4])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[2], v[3]],
[v[2], v[4]],
[v[3], v[4]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
graph.print()
# Add edge
# Vertices 1, 2 i.e., v[0], v[2]
graph.add_edge(v[0], v[2])
print("\nAfter adding edge 1-2, the graph is")
graph.print()
# Remove edge
# Vertices 1, 3 i.e., v[0], v[1]
graph.remove_edge(v[0], v[1])
print("\nAfter removing edge 1-3, the graph is")
graph.print()
# Add vertex
v5 = Vertex(6)
graph.add_vertex(v5)
print("\nAfter adding vertex 6, the graph is")
graph.print()
# Remove vertex
# Vertex 3 i.e., v[1]
graph.remove_vertex(v[1])
print("\nAfter removing vertex 3, the graph is")
graph.print()

View File

@@ -0,0 +1,116 @@
"""
File: graph_adjacency_matrix.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, print_matrix
class GraphAdjMat:
"""Undirected graph class based on adjacency matrix"""
def __init__(self, vertices: list[int], edges: list[list[int]]):
"""Constructor"""
# Vertex list, elements represent "vertex value", index represents "vertex index"
self.vertices: list[int] = []
# Adjacency matrix, row and column indices correspond to "vertex index"
self.adj_mat: list[list[int]] = []
# Add vertex
for val in vertices:
self.add_vertex(val)
# Add edge
# Please note, edges elements represent vertex indices, corresponding to vertices elements indices
for e in edges:
self.add_edge(e[0], e[1])
def size(self) -> int:
"""Get the number of vertices"""
return len(self.vertices)
def add_vertex(self, val: int):
"""Add vertex"""
n = self.size()
# Add new vertex value to the vertex list
self.vertices.append(val)
# Add a row to the adjacency matrix
new_row = [0] * n
self.adj_mat.append(new_row)
# Add a column to the adjacency matrix
for row in self.adj_mat:
row.append(0)
def remove_vertex(self, index: int):
"""Remove vertex"""
if index >= self.size():
raise IndexError()
# Remove vertex at `index` from the vertex list
self.vertices.pop(index)
# Remove the row at `index` from the adjacency matrix
self.adj_mat.pop(index)
# Remove the column at `index` from the adjacency matrix
for row in self.adj_mat:
row.pop(index)
def add_edge(self, i: int, j: int):
"""Add edge"""
# Parameters i, j correspond to vertices element indices
# Handle index out of bounds and equality
if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:
raise IndexError()
# In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., satisfies (i, j) == (j, i)
self.adj_mat[i][j] = 1
self.adj_mat[j][i] = 1
def remove_edge(self, i: int, j: int):
"""Remove edge"""
# Parameters i, j correspond to vertices element indices
# Handle index out of bounds and equality
if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:
raise IndexError()
self.adj_mat[i][j] = 0
self.adj_mat[j][i] = 0
def print(self):
"""Print adjacency matrix"""
print("Vertex list =", self.vertices)
print("Adjacency matrix =")
print_matrix(self.adj_mat)
"""Driver Code"""
if __name__ == "__main__":
# Initialize undirected graph
# Please note, edges elements represent vertex indices, corresponding to vertices elements indices
vertices = [1, 3, 2, 5, 4]
edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]
graph = GraphAdjMat(vertices, edges)
print("\nAfter initialization, the graph is")
graph.print()
# Add edge
# Indices of vertices 1, 2 are 0, 2 respectively
graph.add_edge(0, 2)
print("\nAfter adding edge 1-2, the graph is")
graph.print()
# Remove edge
# Indices of vertices 1, 3 are 0, 1 respectively
graph.remove_edge(0, 1)
print("\nAfter removing edge 1-3, the graph is")
graph.print()
# Add vertex
graph.add_vertex(6)
print("\nAfter adding vertex 6, the graph is")
graph.print()
# Remove vertex
# Index of vertex 3 is 1
graph.remove_vertex(1)
print("\nAfter removing vertex 3, the graph is")
graph.print()

View File

@@ -0,0 +1,64 @@
"""
File: graph_bfs.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vals_to_vets, vets_to_vals
from collections import deque
from graph_adjacency_list import GraphAdjList
def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
"""Breadth-first traversal"""
# Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
res = []
# Hash set, used to record visited vertices
visited = set[Vertex]([start_vet])
# Queue used to implement BFS
que = deque[Vertex]([start_vet])
# Starting from vertex vet, loop until all vertices are visited
while len(que) > 0:
vet = que.popleft() # Dequeue the vertex at the head of the queue
res.append(vet) # Record visited vertex
# Traverse all adjacent vertices of that vertex
for adj_vet in graph.adj_list[vet]:
if adj_vet in visited:
continue # Skip already visited vertices
que.append(adj_vet) # Only enqueue unvisited vertices
visited.add(adj_vet) # Mark the vertex as visited
# Return the vertex traversal sequence
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize undirected graph
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[1], v[4]],
[v[2], v[5]],
[v[3], v[4]],
[v[3], v[6]],
[v[4], v[5]],
[v[4], v[7]],
[v[5], v[8]],
[v[6], v[7]],
[v[7], v[8]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
graph.print()
# Breadth-first traversal
res = graph_bfs(graph, v[0])
print("\nBreadth-first traversal (BFS) vertex sequence is")
print(vets_to_vals(res))

View File

@@ -0,0 +1,57 @@
"""
File: graph_dfs.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vets_to_vals, vals_to_vets
from graph_adjacency_list import GraphAdjList
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):
"""Depth-first traversal helper function"""
res.append(vet) # Record visited vertex
visited.add(vet) # Mark the vertex as visited
# Traverse all adjacent vertices of that vertex
for adjVet in graph.adj_list[vet]:
if adjVet in visited:
continue # Skip already visited vertices
# Recursively visit adjacent vertices
dfs(graph, visited, res, adjVet)
def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
"""Depth-first traversal"""
# Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
res = []
# Hash set, used to record visited vertices
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize undirected graph
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[2], v[5]],
[v[4], v[5]],
[v[5], v[6]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
graph.print()
# Depth-first traversal
res = graph_dfs(graph, v[0])
print("\nDepth-first traversal (DFS) vertex sequence is")
print(vets_to_vals(res))

View File

@@ -0,0 +1,48 @@
"""
File: coin_change_greedy.py
Created Time: 2023-07-18
Author: krahets (krahets@163.com)
"""
def coin_change_greedy(coins: list[int], amt: int) -> int:
"""Coin change: Greedy"""
# Assume coins list is ordered
i = len(coins) - 1
count = 0
# Loop for greedy selection until no remaining amount
while amt > 0:
# Find the smallest coin close to and less than the remaining amount
while i > 0 and coins[i] > amt:
i -= 1
# Choose coins[i]
amt -= coins[i]
count += 1
# If no feasible solution is found, return -1
return count if amt == 0 else -1
"""Driver Code"""
if __name__ == "__main__":
# Greedy: can ensure finding a global optimal solution
coins = [1, 5, 10, 20, 50, 100]
amt = 186
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"The minimum number of coins needed to make up {amt} is {res}")
# Greedy: cannot ensure finding a global optimal solution
coins = [1, 20, 50]
amt = 60
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"The minimum number of coins needed to make up {amt} is {res}")
print(f"In reality, the minimum number needed is 3, i.e., 20 + 20 + 20")
# Greedy: cannot ensure finding a global optimal solution
coins = [1, 49, 50]
amt = 98
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"The minimum number of coins needed to make up {amt} is {res}")
print(f"In reality, the minimum number needed is 2, i.e., 49 + 49")

View File

@@ -0,0 +1,46 @@
"""
File: fractional_knapsack.py
Created Time: 2023-07-19
Author: krahets (krahets@163.com)
"""
class Item:
"""Item"""
def __init__(self, w: int, v: int):
self.w = w # Item weight
self.v = v # Item value
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
"""Fractional knapsack: Greedy"""
# Create an item list, containing two properties: weight, value
items = [Item(w, v) for w, v in zip(wgt, val)]
# Sort by unit value item.v / item.w from high to low
items.sort(key=lambda item: item.v / item.w, reverse=True)
# Loop for greedy selection
res = 0
for item in items:
if item.w <= cap:
# If the remaining capacity is sufficient, put the entire item into the knapsack
res += item.v
cap -= item.w
else:
# If the remaining capacity is insufficient, put part of the item into the knapsack
res += (item.v / item.w) * cap
# No remaining capacity left, thus break the loop
break
return res
"""Driver Code"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = len(wgt)
# Greedy algorithm
res = fractional_knapsack(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")

View File

@@ -0,0 +1,33 @@
"""
File: max_capacity.py
Created Time: 2023-07-21
Author: krahets (krahets@163.com)
"""
def max_capacity(ht: list[int]) -> int:
"""Maximum capacity: Greedy"""
# Initialize i, j, making them split the array at both ends
i, j = 0, len(ht) - 1
# Initial maximum capacity is 0
res = 0
# Loop for greedy selection until the two boards meet
while i < j:
# Update maximum capacity
cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
# Move the shorter board inward
if ht[i] < ht[j]:
i += 1
else:
j -= 1
return res
"""Driver Code"""
if __name__ == "__main__":
ht = [3, 8, 5, 2, 7, 7, 3, 4]
# Greedy algorithm
res = max_capacity(ht)
print(f"Maximum capacity is {res}")

View File

@@ -0,0 +1,33 @@
"""
File: max_product_cutting.py
Created Time: 2023-07-21
Author: krahets (krahets@163.com)
"""
import math
def max_product_cutting(n: int) -> int:
"""Maximum product of cutting: Greedy"""
# When n <= 3, must cut out a 1
if n <= 3:
return 1 * (n - 1)
# Greedy cut out 3s, a is the number of 3s, b is the remainder
a, b = n // 3, n % 3
if b == 1:
# When the remainder is 1, convert a pair of 1 * 3 into 2 * 2
return int(math.pow(3, a - 1)) * 2 * 2
if b == 2:
# When the remainder is 2, do nothing
return int(math.pow(3, a)) * 2
# When the remainder is 0, do nothing
return int(math.pow(3, a))
"""Driver Code"""
if __name__ == "__main__":
n = 58
# Greedy algorithm
res = max_product_cutting(n)
print(f"Maximum product of cutting is {res}")

View File

@@ -0,0 +1,117 @@
"""
File: array_hash_map.py
Created Time: 2022-12-14
Author: msk397 (machangxinq@gmail.com)
"""
class Pair:
"""Key-value pair"""
def __init__(self, key: int, val: str):
self.key = key
self.val = val
class ArrayHashMap:
"""Hash table based on array implementation"""
def __init__(self):
"""Constructor"""
# Initialize an array, containing 100 buckets
self.buckets: list[Pair | None] = [None] * 100
def hash_func(self, key: int) -> int:
"""Hash function"""
index = key % 100
return index
def get(self, key: int) -> str:
"""Query operation"""
index: int = self.hash_func(key)
pair: Pair = self.buckets[index]
if pair is None:
return None
return pair.val
def put(self, key: int, val: str):
"""Add operation"""
pair = Pair(key, val)
index: int = self.hash_func(key)
self.buckets[index] = pair
def remove(self, key: int):
"""Remove operation"""
index: int = self.hash_func(key)
# Set to None, representing removal
self.buckets[index] = None
def entry_set(self) -> list[Pair]:
"""Get all key-value pairs"""
result: list[Pair] = []
for pair in self.buckets:
if pair is not None:
result.append(pair)
return result
def key_set(self) -> list[int]:
"""Get all keys"""
result = []
for pair in self.buckets:
if pair is not None:
result.append(pair.key)
return result
def value_set(self) -> list[str]:
"""Get all values"""
result = []
for pair in self.buckets:
if pair is not None:
result.append(pair.val)
return result
def print(self):
"""Print hash table"""
for pair in self.buckets:
if pair is not None:
print(pair.key, "->", pair.val)
"""Driver Code"""
if __name__ == "__main__":
# Initialize hash table
hmap = ArrayHashMap()
# Add operation
# Add key-value pair (key, value) to the hash table
hmap.put(12836, "Ha")
hmap.put(15937, "Luo")
hmap.put(16750, "Suan")
hmap.put(13276, "Fa")
hmap.put(10583, "Ya")
print("\nAfter adding, the hash table is\nKey -> Value")
hmap.print()
# Query operation
# Enter key to the hash table, get value
name = hmap.get(15937)
print("\nEnter student ID 15937, found name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
hmap.remove(10583)
print("\nAfter removing 10583, the hash table is\nKey -> Value")
hmap.print()
# Traverse hash table
print("\nTraverse key-value pairs Key->Value")
for pair in hmap.entry_set():
print(pair.key, "->", pair.val)
print("\nIndividually traverse keys Key")
for key in hmap.key_set():
print(key)
print("\nIndividually traverse values Value")
for val in hmap.value_set():
print(val)

View File

@@ -0,0 +1,37 @@
"""
File: built_in_hash.py
Created Time: 2023-06-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
"""Driver Code"""
if __name__ == "__main__":
num = 3
hash_num = hash(num)
print(f"Integer {num}'s hash value is {hash_num}")
bol = True
hash_bol = hash(bol)
print(f"Boolean {bol}'s hash value is {hash_bol}")
dec = 3.14159
hash_dec = hash(dec)
print(f"Decimal {dec}'s hash value is {hash_dec}")
str = "Hello algorithm"
hash_str = hash(str)
print(f"String {str}'s hash value is {hash_str}")
tup = (12836, "Ha")
hash_tup = hash(tup)
print(f"Tuple {tup}'s hash value is {hash(hash_tup)}")
obj = ListNode(0)
hash_obj = hash(obj)
print(f"Node object {obj}'s hash value is {hash_obj}")

View File

@@ -0,0 +1,50 @@
"""
File: hash_map.py
Created Time: 2022-12-14
Author: msk397 (machangxinq@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_dict
"""Driver Code"""
if __name__ == "__main__":
# Initialize hash table
hmap = dict[int, str]()
# Add operation
# Add key-value pair (key, value) to the hash table
hmap[12836] = "Ha"
hmap[15937] = "Luo"
hmap[16750] = "Suan"
hmap[13276] = "Fa"
hmap[10583] = "Ya"
print("\nAfter adding, the hash table is\nKey -> Value")
print_dict(hmap)
# Query operation
# Enter key to the hash table, get value
name: str = hmap[15937]
print("\nEnter student ID 15937, found name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
hmap.pop(10583)
print("\nAfter removing 10583, the hash table is\nKey -> Value")
print_dict(hmap)
# Traverse hash table
print("\nTraverse key-value pairs Key->Value")
for key, value in hmap.items():
print(key, "->", value)
print("\nIndividually traverse keys Key")
for key in hmap.keys():
print(key)
print("\nIndividually traverse values Value")
for val in hmap.values():
print(val)

View File

@@ -0,0 +1,118 @@
"""
File: hash_map_chaining.py
Created Time: 2023-06-13
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from chapter_hashing.array_hash_map import Pair
class HashMapChaining:
"""Chained address hash table"""
def __init__(self):
"""Constructor"""
self.size = 0 # Number of key-value pairs
self.capacity = 4 # Hash table capacity
self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion
self.extend_ratio = 2 # Expansion multiplier
self.buckets = [[] for _ in range(self.capacity)] # Bucket array
def hash_func(self, key: int) -> int:
"""Hash function"""
return key % self.capacity
def load_factor(self) -> float:
"""Load factor"""
return self.size / self.capacity
def get(self, key: int) -> str | None:
"""Query operation"""
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse the bucket, if the key is found, return the corresponding val
for pair in bucket:
if pair.key == key:
return pair.val
# If the key is not found, return None
return None
def put(self, key: int, val: str):
"""Add operation"""
# When the load factor exceeds the threshold, perform expansion
if self.load_factor() > self.load_thres:
self.extend()
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse the bucket, if the specified key is encountered, update the corresponding val and return
for pair in bucket:
if pair.key == key:
pair.val = val
return
# If the key is not found, add the key-value pair to the end
pair = Pair(key, val)
bucket.append(pair)
self.size += 1
def remove(self, key: int):
"""Remove operation"""
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse the bucket, remove the key-value pair from it
for pair in bucket:
if pair.key == key:
bucket.remove(pair)
self.size -= 1
break
def extend(self):
"""Extend hash table"""
# Temporarily store the original hash table
buckets = self.buckets
# Initialize the extended new hash table
self.capacity *= self.extend_ratio
self.buckets = [[] for _ in range(self.capacity)]
self.size = 0
# Move key-value pairs from the original hash table to the new hash table
for bucket in buckets:
for pair in bucket:
self.put(pair.key, pair.val)
def print(self):
"""Print hash table"""
for bucket in self.buckets:
res = []
for pair in bucket:
res.append(str(pair.key) + " -> " + pair.val)
print(res)
"""Driver Code"""
if __name__ == "__main__":
# Initialize hash table
hashmap = HashMapChaining()
# Add operation
# Add key-value pair (key, value) to the hash table
hashmap.put(12836, "Ha")
hashmap.put(15937, "Luo")
hashmap.put(16750, "Suan")
hashmap.put(13276, "Fa")
hashmap.put(10583, "Ya")
print("\nAfter adding, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()
# Query operation
# Enter key to the hash table, get value
name = hashmap.get(13276)
print("\nEnter student ID 13276, found name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
hashmap.remove(12836)
print("\nAfter removing 12836, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()

View File

@@ -0,0 +1,138 @@
"""
File: hash_map_open_addressing.py
Created Time: 2023-06-13
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from chapter_hashing.array_hash_map import Pair
class HashMapOpenAddressing:
"""Open addressing hash table"""
def __init__(self):
"""Constructor"""
self.size = 0 # Number of key-value pairs
self.capacity = 4 # Hash table capacity
self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion
self.extend_ratio = 2 # Expansion multiplier
self.buckets: list[Pair | None] = [None] * self.capacity # Bucket array
self.TOMBSTONE = Pair(-1, "-1") # Removal mark
def hash_func(self, key: int) -> int:
"""Hash function"""
return key % self.capacity
def load_factor(self) -> float:
"""Load factor"""
return self.size / self.capacity
def find_bucket(self, key: int) -> int:
"""Search for the bucket index corresponding to key"""
index = self.hash_func(key)
first_tombstone = -1
# Linear probing, break when encountering an empty bucket
while self.buckets[index] is not None:
# If the key is encountered, return the corresponding bucket index
if self.buckets[index].key == key:
# If a removal mark was encountered earlier, move the key-value pair to that index
if first_tombstone != -1:
self.buckets[first_tombstone] = self.buckets[index]
self.buckets[index] = self.TOMBSTONE
return first_tombstone # Return the moved bucket index
return index # Return bucket index
# Record the first encountered removal mark
if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:
first_tombstone = index
# Calculate the bucket index, return to the head if exceeding the tail
index = (index + 1) % self.capacity
# If the key does not exist, return the index of the insertion point
return index if first_tombstone == -1 else first_tombstone
def get(self, key: int) -> str:
"""Query operation"""
# Search for the bucket index corresponding to key
index = self.find_bucket(key)
# If the key-value pair is found, return the corresponding val
if self.buckets[index] not in [None, self.TOMBSTONE]:
return self.buckets[index].val
# If the key-value pair does not exist, return None
return None
def put(self, key: int, val: str):
"""Add operation"""
# When the load factor exceeds the threshold, perform expansion
if self.load_factor() > self.load_thres:
self.extend()
# Search for the bucket index corresponding to key
index = self.find_bucket(key)
# If the key-value pair is found, overwrite val and return
if self.buckets[index] not in [None, self.TOMBSTONE]:
self.buckets[index].val = val
return
# If the key-value pair does not exist, add the key-value pair
self.buckets[index] = Pair(key, val)
self.size += 1
def remove(self, key: int):
"""Remove operation"""
# Search for the bucket index corresponding to key
index = self.find_bucket(key)
# If the key-value pair is found, cover it with a removal mark
if self.buckets[index] not in [None, self.TOMBSTONE]:
self.buckets[index] = self.TOMBSTONE
self.size -= 1
def extend(self):
"""Extend hash table"""
# Temporarily store the original hash table
buckets_tmp = self.buckets
# Initialize the extended new hash table
self.capacity *= self.extend_ratio
self.buckets = [None] * self.capacity
self.size = 0
# Move key-value pairs from the original hash table to the new hash table
for pair in buckets_tmp:
if pair not in [None, self.TOMBSTONE]:
self.put(pair.key, pair.val)
def print(self):
"""Print hash table"""
for pair in self.buckets:
if pair is None:
print("None")
elif pair is self.TOMBSTONE:
print("TOMBSTONE")
else:
print(pair.key, "->", pair.val)
"""Driver Code"""
if __name__ == "__main__":
# Initialize hash table
hashmap = HashMapOpenAddressing()
# Add operation
# Add key-value pair (key, val) to the hash table
hashmap.put(12836, "Ha")
hashmap.put(15937, "Luo")
hashmap.put(16750, "Suan")
hashmap.put(13276, "Fa")
hashmap.put(10583, "Ya")
print("\nAfter adding, the hash table is\nKey -> Value")
hashmap.print()
# Query operation
# Enter key to the hash table, get value val
name = hashmap.get(13276)
print("\nEnter student ID 13276, found name " + name)
# Remove operation
# Remove key-value pair (key, val) from the hash table
hashmap.remove(16750)
print("\nAfter removing 16750, the hash table is\nKey -> Value")
hashmap.print()

View File

@@ -0,0 +1,58 @@
"""
File: simple_hash.py
Created Time: 2023-06-15
Author: krahets (krahets@163.com)
"""
def add_hash(key: str) -> int:
"""Additive hash"""
hash = 0
modulus = 1000000007
for c in key:
hash += ord(c)
return hash % modulus
def mul_hash(key: str) -> int:
"""Multiplicative hash"""
hash = 0
modulus = 1000000007
for c in key:
hash = 31 * hash + ord(c)
return hash % modulus
def xor_hash(key: str) -> int:
"""XOR hash"""
hash = 0
modulus = 1000000007
for c in key:
hash ^= ord(c)
return hash % modulus
def rot_hash(key: str) -> int:
"""Rotational hash"""
hash = 0
modulus = 1000000007
for c in key:
hash = (hash << 4) ^ (hash >> 28) ^ ord(c)
return hash % modulus
"""Driver Code"""
if __name__ == "__main__":
key = "Hello algorithm"
hash = add_hash(key)
print(f"Additive hash value is {hash}")
hash = mul_hash(key)
print(f"Multiplicative hash value is {hash}")
hash = xor_hash(key)
print(f"XOR hash value is {hash}")
hash = rot_hash(key)
print(f"Rotational hash value is {hash}")

View File

@@ -0,0 +1,71 @@
"""
File: heap.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
import heapq
def test_push(heap: list, val: int, flag: int = 1):
heapq.heappush(heap, flag * val) # Push the element into heap
print(f"\nElement {val} after pushed into heap")
print_heap([flag * val for val in heap])
def test_pop(heap: list, flag: int = 1):
val = flag * heapq.heappop(heap) # Pop the element at the heap top
print(f"\nHeap top element {val} after exiting heap")
print_heap([flag * val for val in heap])
"""Driver Code"""
if __name__ == "__main__":
# Initialize min-heap
min_heap, flag = [], 1
# Initialize max-heap
max_heap, flag = [], -1
print("\nThe following test case is for max-heap")
# Python's heapq module implements min-heap by default
# Consider "negating the elements" before entering the heap, thus reversing the comparator to implement a max-heap
# In this example, flag = 1 corresponds to min-heap, flag = -1 corresponds to max-heap
# Push the element into heap
test_push(max_heap, 1, flag)
test_push(max_heap, 3, flag)
test_push(max_heap, 2, flag)
test_push(max_heap, 5, flag)
test_push(max_heap, 4, flag)
# Access heap top element
peek: int = flag * max_heap[0]
print(f"\nHeap top element is {peek}")
# Pop the element at the heap top
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
# Get heap size
size: int = len(max_heap)
print(f"\nNumber of heap elements is {size}")
# Determine if heap is empty
is_empty: bool = not max_heap
print(f"\nIs the heap empty {is_empty}")
# Enter list and build heap
# Time complexity is O(n), not O(nlogn)
min_heap = [1, 3, 2, 5, 4]
heapq.heapify(min_heap)
print("\nEnter list and build min-heap")
print_heap(min_heap)

View File

@@ -0,0 +1,137 @@
"""
File: my_heap.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
class MaxHeap:
"""Max-heap"""
def __init__(self, nums: list[int]):
"""Constructor, build heap based on input list"""
# Add all list elements into the heap
self.max_heap = nums
# Heapify all nodes except leaves
for i in range(self.parent(self.size() - 1), -1, -1):
self.sift_down(i)
def left(self, i: int) -> int:
"""Get index of left child node"""
return 2 * i + 1
def right(self, i: int) -> int:
"""Get index of right child node"""
return 2 * i + 2
def parent(self, i: int) -> int:
"""Get index of parent node"""
return (i - 1) // 2 # Integer division down
def swap(self, i: int, j: int):
"""Swap elements"""
self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i]
def size(self) -> int:
"""Get heap size"""
return len(self.max_heap)
def is_empty(self) -> bool:
"""Determine if heap is empty"""
return self.size() == 0
def peek(self) -> int:
"""Access heap top element"""
return self.max_heap[0]
def push(self, val: int):
"""Push the element into heap"""
# Add node
self.max_heap.append(val)
# Heapify from bottom to top
self.sift_up(self.size() - 1)
def sift_up(self, i: int):
"""Start heapifying node i, from bottom to top"""
while True:
# Get parent node of node i
p = self.parent(i)
# When "crossing the root node" or "node does not need repair", end heapification
if p < 0 or self.max_heap[i] <= self.max_heap[p]:
break
# Swap two nodes
self.swap(i, p)
# Loop upwards heapification
i = p
def pop(self) -> int:
"""Element exits heap"""
# Empty handling
if self.is_empty():
raise IndexError("Heap is empty")
# Swap the root node with the rightmost leaf node (swap the first element with the last element)
self.swap(0, self.size() - 1)
# Remove node
val = self.max_heap.pop()
# Heapify from top to bottom
self.sift_down(0)
# Return heap top element
return val
def sift_down(self, i: int):
"""Start heapifying node i, from top to bottom"""
while True:
# Determine the largest node among i, l, r, noted as ma
l, r, ma = self.left(i), self.right(i), i
if l < self.size() and self.max_heap[l] > self.max_heap[ma]:
ma = l
if r < self.size() and self.max_heap[r] > self.max_heap[ma]:
ma = r
# If node i is the largest or indices l, r are out of bounds, no further heapification needed, break
if ma == i:
break
# Swap two nodes
self.swap(i, ma)
# Loop downwards heapification
i = ma
def print(self):
"""Print heap (binary tree)"""
print_heap(self.max_heap)
"""Driver Code"""
if __name__ == "__main__":
# Initialize max-heap
max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2])
print("\nEnter list and build heap")
max_heap.print()
# Access heap top element
peek = max_heap.peek()
print(f"\nHeap top element is {peek}")
# Push the element into heap
val = 7
max_heap.push(val)
print(f"\nElement {val} after pushed into heap")
max_heap.print()
# Pop the element at the heap top
peek = max_heap.pop()
print(f"\nHeap top element {peek} after exiting heap")
max_heap.print()
# Get heap size
size = max_heap.size()
print(f"\nNumber of heap elements is {size}")
# Determine if heap is empty
is_empty = max_heap.is_empty()
print(f"\nIs the heap empty {is_empty}")

View File

@@ -0,0 +1,39 @@
"""
File: top_k.py
Created Time: 2023-06-10
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
import heapq
def top_k_heap(nums: list[int], k: int) -> list[int]:
"""Using heap to find the largest k elements in an array"""
# Initialize min-heap
heap = []
# Enter the first k elements of the array into the heap
for i in range(k):
heapq.heappush(heap, nums[i])
# From the k+1th element, keep the heap length as k
for i in range(k, len(nums)):
# If the current element is larger than the heap top element, remove the heap top element and enter the current element into the heap
if nums[i] > heap[0]:
heapq.heappop(heap)
heapq.heappush(heap, nums[i])
return heap
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 7, 6, 3, 2]
k = 3
res = top_k_heap(nums, k)
print(f"The largest {k} elements are")
print_heap(res)

View File

@@ -0,0 +1,52 @@
"""
File: binary_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
def binary_search(nums: list[int], target: int) -> int:
"""Binary search (double closed interval)"""
# Initialize double closed interval [0, n-1], i.e., i, j point to the first element and last element of the array respectively
i, j = 0, len(nums) - 1
# Loop until the search interval is empty (when i > j, it is empty)
while i <= j:
# Theoretically, Python's numbers can be infinitely large (depending on memory size), so there is no need to consider large number overflow
m = (i + j) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # This situation indicates that target is in the interval [m+1, j]
elif nums[m] > target:
j = m - 1 # This situation indicates that target is in the interval [i, m-1]
else:
return m # Found the target element, thus return its index
return -1 # Did not find the target element, thus return -1
def binary_search_lcro(nums: list[int], target: int) -> int:
"""Binary search (left closed right open interval)"""
# Initialize left closed right open interval [0, n), i.e., i, j point to the first element and the last element +1 of the array respectively
i, j = 0, len(nums)
# Loop until the search interval is empty (when i = j, it is empty)
while i < j:
m = (i + j) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # This situation indicates that target is in the interval [m+1, j)
elif nums[m] > target:
j = m # This situation indicates that target is in the interval [i, m)
else:
return m # Found the target element, thus return its index
return -1 # Did not find the target element, thus return -1
"""Driver Code"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (double closed interval)
index = binary_search(nums, target)
print("Index of target element 6 =", index)
# Binary search (left closed right open interval)
index = binary_search_lcro(nums, target)
print("Index of target element 6 =", index)

View File

@@ -0,0 +1,49 @@
"""
File: binary_search_edge.py
Created Time: 2023-08-04
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from binary_search_insertion import binary_search_insertion
def binary_search_left_edge(nums: list[int], target: int) -> int:
"""Binary search for the leftmost target"""
# Equivalent to finding the insertion point of target
i = binary_search_insertion(nums, target)
# Did not find target, thus return -1
if i == len(nums) or nums[i] != target:
return -1
# Found target, return index i
return i
def binary_search_right_edge(nums: list[int], target: int) -> int:
"""Binary search for the rightmost target"""
# Convert to finding the leftmost target + 1
i = binary_search_insertion(nums, target + 1)
# j points to the rightmost target, i points to the first element greater than target
j = i - 1
# Did not find target, thus return -1
if j == -1 or nums[j] != target:
return -1
# Found target, return index j
return j
"""Driver Code"""
if __name__ == "__main__":
# Array with duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\nArray nums = {nums}")
# Binary search for left and right boundaries
for target in [6, 7]:
index = binary_search_left_edge(nums, target)
print(f"The index of the leftmost element {target} is {index}")
index = binary_search_right_edge(nums, target)
print(f"The index of the rightmost element {target} is {index}")

View File

@@ -0,0 +1,54 @@
"""
File: binary_search_insertion.py
Created Time: 2023-08-04
Author: krahets (krahets@163.com)
"""
def binary_search_insertion_simple(nums: list[int], target: int) -> int:
"""Binary search for insertion point (no duplicate elements)"""
i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # Target is in interval [m+1, j]
elif nums[m] > target:
j = m - 1 # Target is in interval [i, m-1]
else:
return m # Found target, return insertion point m
# Did not find target, return insertion point i
return i
def binary_search_insertion(nums: list[int], target: int) -> int:
"""Binary search for insertion point (with duplicate elements)"""
i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # Target is in interval [m+1, j]
elif nums[m] > target:
j = m - 1 # Target is in interval [i, m-1]
else:
j = m - 1 # First element less than target is in interval [i, m-1]
# Return insertion point i
return i
"""Driver Code"""
if __name__ == "__main__":
# Array without duplicate elements
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
print(f"\nArray nums = {nums}")
# Binary search for insertion point
for target in [6, 9]:
index = binary_search_insertion_simple(nums, target)
print(f"Element {target}'s insertion point index is {index}")
# Array with duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\nArray nums = {nums}")
# Binary search for insertion point
for target in [2, 6, 20]:
index = binary_search_insertion(nums, target)
print(f"Element {target}'s insertion point index is {index}")

View File

@@ -0,0 +1,51 @@
"""
File: hashing_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, list_to_linked_list
def hashing_search_array(hmap: dict[int, int], target: int) -> int:
"""Hash search (array)"""
# Hash table's key: target element, value: index
# If the hash table does not contain this key, return -1
return hmap.get(target, -1)
def hashing_search_linkedlist(
hmap: dict[int, ListNode], target: int
) -> ListNode | None:
"""Hash search (linked list)"""
# Hash table's key: target element, value: node object
# If the hash table does not contain this key, return None
return hmap.get(target, None)
"""Driver Code"""
if __name__ == "__main__":
target = 3
# Hash search (array)
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
# Initialize hash table
map0 = dict[int, int]()
for i in range(len(nums)):
map0[nums[i]] = i # key: element, value: index
index: int = hashing_search_array(map0, target)
print("Index of target element 3 =", index)
# Hash search (linked list)
head: ListNode = list_to_linked_list(nums)
# Initialize hash table
map1 = dict[int, ListNode]()
while head:
map1[head.val] = head # key: node value, value: node
head = head.next
node: ListNode = hashing_search_linkedlist(map1, target)
print("Target node value 3's corresponding node object is", node)

View File

@@ -0,0 +1,45 @@
"""
File: linear_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, list_to_linked_list
def linear_search_array(nums: list[int], target: int) -> int:
"""Linear search (array)"""
# Traverse array
for i in range(len(nums)):
if nums[i] == target: # Found the target element, thus return its index
return i
return -1 # Did not find the target element, thus return -1
def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None:
"""Linear search (linked list)"""
# Traverse the list
while head:
if head.val == target: # Found the target node, return it
return head
head = head.next
return None # Did not find the target node, thus return None
"""Driver Code"""
if __name__ == "__main__":
target = 3
# Perform linear search in array
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
index: int = linear_search_array(nums, target)
print("Index of target element 3 =", index)
# Perform linear search in linked list
head: ListNode = list_to_linked_list(nums)
node: ListNode | None = linear_search_linkedlist(head, target)
print("Target node value 3's corresponding node object is", node)

View File

@@ -0,0 +1,42 @@
"""
File: two_sum.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
"""Method one: Brute force enumeration"""
# Two-layer loop, time complexity is O(n^2)
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
"""Method two: Auxiliary hash table"""
# Auxiliary hash table, space complexity is O(n)
dic = {}
# Single-layer loop, time complexity is O(n)
for i in range(len(nums)):
if target - nums[i] in dic:
return [dic[target - nums[i]], i]
dic[nums[i]] = i
return []
"""Driver Code"""
if __name__ == "__main__":
# ======= Test Case =======
nums = [2, 7, 11, 15]
target = 13
# ====== Driver Code ======
# Method one
res: list[int] = two_sum_brute_force(nums, target)
print("Method one res =", res)
# Method two
res: list[int] = two_sum_hash_table(nums, target)
print("Method two res =", res)

View File

@@ -0,0 +1,44 @@
"""
File: bubble_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
def bubble_sort(nums: list[int]):
"""Bubble sort"""
n = len(nums)
# Outer loop: unsorted range is [0, i]
for i in range(n - 1, 0, -1):
# Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
for j in range(i):
if nums[j] > nums[j + 1]:
# Swap nums[j] and nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
def bubble_sort_with_flag(nums: list[int]):
"""Bubble sort (optimized with flag)"""
n = len(nums)
# Outer loop: unsorted range is [0, i]
for i in range(n - 1, 0, -1):
flag = False # Initialize flag
# Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
for j in range(i):
if nums[j] > nums[j + 1]:
# Swap nums[j] and nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True # Record swapped elements
if not flag:
break # If no elements were swapped in this round of "bubbling", exit
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
bubble_sort(nums)
print("Bubble sort completed nums =", nums)
nums1 = [4, 1, 3, 1, 5, 2]
bubble_sort_with_flag(nums1)
print("Bubble sort completed nums =", nums1)

View File

@@ -0,0 +1,35 @@
"""
File: bucket_sort.py
Created Time: 2023-03-30
Author: krahets (krahets@163.com)
"""
def bucket_sort(nums: list[float]):
"""Bucket sort"""
# Initialize k = n/2 buckets, expected to allocate 2 elements per bucket
k = len(nums) // 2
buckets = [[] for _ in range(k)]
# 1. Distribute array elements into various buckets
for num in nums:
# Input data range is [0, 1), use num * k to map to index range [0, k-1]
i = int(num * k)
# Add num to bucket i
buckets[i].append(num)
# 2. Sort each bucket
for bucket in buckets:
# Use built-in sorting function, can also replace with other sorting algorithms
bucket.sort()
# 3. Traverse buckets to merge results
i = 0
for bucket in buckets:
for num in bucket:
nums[i] = num
i += 1
if __name__ == "__main__":
# Assume input data is floating point, range [0, 1)
nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]
bucket_sort(nums)
print("Bucket sort completed nums =", nums)

View File

@@ -0,0 +1,64 @@
"""
File: counting_sort.py
Created Time: 2023-03-21
Author: krahets (krahets@163.com)
"""
def counting_sort_naive(nums: list[int]):
"""Counting sort"""
# Simple implementation, cannot be used for sorting objects
# 1. Count the maximum element m in the array
m = 0
for num in nums:
m = max(m, num)
# 2. Count the occurrence of each digit
# counter[num] represents the occurrence of num
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. Traverse counter, filling each element back into the original array nums
i = 0
for num in range(m + 1):
for _ in range(counter[num]):
nums[i] = num
i += 1
def counting_sort(nums: list[int]):
"""Counting sort"""
# Complete implementation, can sort objects and is a stable sort
# 1. Count the maximum element m in the array
m = max(nums)
# 2. Count the occurrence of each digit
# counter[num] represents the occurrence of num
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index"
# counter[num]-1 is the last index where num appears in res
for i in range(m):
counter[i + 1] += counter[i]
# 4. Traverse nums in reverse order, placing each element into the result array res
# Initialize the array res to record results
n = len(nums)
res = [0] * n
for i in range(n - 1, -1, -1):
num = nums[i]
res[counter[num] - 1] = num # Place num at the corresponding index
counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num
# Use result array res to overwrite the original array nums
for i in range(n):
nums[i] = res[i]
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort_naive(nums)
print(f"Counting sort (unable to sort objects) completed nums = {nums}")
nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort(nums1)
print(f"Counting sort completed nums1 = {nums1}")

View File

@@ -0,0 +1,45 @@
"""
File: heap_sort.py
Created Time: 2023-05-24
Author: krahets (krahets@163.com)
"""
def sift_down(nums: list[int], n: int, i: int):
"""Heap length is n, start heapifying node i, from top to bottom"""
while True:
# Determine the largest node among i, l, r, noted as ma
l = 2 * i + 1
r = 2 * i + 2
ma = i
if l < n and nums[l] > nums[ma]:
ma = l
if r < n and nums[r] > nums[ma]:
ma = r
# If node i is the largest or indices l, r are out of bounds, no further heapification needed, break
if ma == i:
break
# Swap two nodes
nums[i], nums[ma] = nums[ma], nums[i]
# Loop downwards heapification
i = ma
def heap_sort(nums: list[int]):
"""Heap sort"""
# Build heap operation: heapify all nodes except leaves
for i in range(len(nums) // 2 - 1, -1, -1):
sift_down(nums, len(nums), i)
# Extract the largest element from the heap and repeat for n-1 rounds
for i in range(len(nums) - 1, 0, -1):
# Swap the root node with the rightmost leaf node (swap the first element with the last element)
nums[0], nums[i] = nums[i], nums[0]
# Start heapifying the root node, from top to bottom
sift_down(nums, i, 0)
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)
print("Heap sort completed nums =", nums)

View File

@@ -0,0 +1,25 @@
"""
File: insertion_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
def insertion_sort(nums: list[int]):
"""Insertion sort"""
# Outer loop: sorted range is [0, i-1]
for i in range(1, len(nums)):
base = nums[i]
j = i - 1
# Inner loop: insert base into the correct position within the sorted range [0, i-1]
while j >= 0 and nums[j] > base:
nums[j + 1] = nums[j] # Move nums[j] to the right by one position
j -= 1
nums[j + 1] = base # Assign base to the correct position
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)
print("Insertion sort completed nums =", nums)

View File

@@ -0,0 +1,55 @@
"""
File: merge_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com), krahets (krahets@163.com)
"""
def merge(nums: list[int], left: int, mid: int, right: int):
"""Merge left subarray and right subarray"""
# Left subarray interval is [left, mid], right subarray interval is [mid+1, right]
# Create a temporary array tmp to store the merged results
tmp = [0] * (right - left + 1)
# Initialize the start indices of the left and right subarrays
i, j, k = left, mid + 1, 0
# While both subarrays still have elements, compare and copy the smaller element into the temporary array
while i <= mid and j <= right:
if nums[i] <= nums[j]:
tmp[k] = nums[i]
i += 1
else:
tmp[k] = nums[j]
j += 1
k += 1
# Copy the remaining elements of the left and right subarrays into the temporary array
while i <= mid:
tmp[k] = nums[i]
i += 1
k += 1
while j <= right:
tmp[k] = nums[j]
j += 1
k += 1
# Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval
for k in range(0, len(tmp)):
nums[left + k] = tmp[k]
def merge_sort(nums: list[int], left: int, right: int):
"""Merge sort"""
# Termination condition
if left >= right:
return # Terminate recursion when subarray length is 1
# Partition stage
mid = (left + right) // 2 # Calculate midpoint
merge_sort(nums, left, mid) # Recursively process the left subarray
merge_sort(nums, mid + 1, right) # Recursively process the right subarray
# Merge stage
merge(nums, left, mid, right)
"""Driver Code"""
if __name__ == "__main__":
nums = [7, 3, 2, 6, 0, 1, 5, 4]
merge_sort(nums, 0, len(nums) - 1)
print("Merge sort completed nums =", nums)

View File

@@ -0,0 +1,129 @@
"""
File: quick_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
class QuickSort:
"""Quick sort class"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Partition"""
# Use nums[left] as the pivot
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # Search from right to left for the first element smaller than the pivot
while i < j and nums[i] <= nums[left]:
i += 1 # Search from left to right for the first element greater than the pivot
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort"""
# Terminate recursion when subarray length is 1
if left >= right:
return
# Partition
pivot = self.partition(nums, left, right)
# Recursively process the left subarray and right subarray
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
class QuickSortMedian:
"""Quick sort class (median pivot optimization)"""
def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:
"""Select the median of three candidate elements"""
l, m, r = nums[left], nums[mid], nums[right]
if (l <= m <= r) or (r <= m <= l):
return mid # m is between l and r
if (m <= l <= r) or (r <= l <= m):
return left # l is between m and r
return right
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Partition (median of three)"""
# Use nums[left] as the pivot
med = self.median_three(nums, left, (left + right) // 2, right)
# Swap the median to the array's leftmost position
nums[left], nums[med] = nums[med], nums[left]
# Use nums[left] as the pivot
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # Search from right to left for the first element smaller than the pivot
while i < j and nums[i] <= nums[left]:
i += 1 # Search from left to right for the first element greater than the pivot
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort"""
# Terminate recursion when subarray length is 1
if left >= right:
return
# Partition
pivot = self.partition(nums, left, right)
# Recursively process the left subarray and right subarray
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
class QuickSortTailCall:
"""Quick sort class (tail recursion optimization)"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Partition"""
# Use nums[left] as the pivot
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # Search from right to left for the first element smaller than the pivot
while i < j and nums[i] <= nums[left]:
i += 1 # Search from left to right for the first element greater than the pivot
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort (tail recursion optimization)"""
# Terminate when subarray length is 1
while left < right:
# Partition operation
pivot = self.partition(nums, left, right)
# Perform quick sort on the shorter of the two subarrays
if pivot - left < right - pivot:
self.quick_sort(nums, left, pivot - 1) # Recursively sort the left subarray
left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right]
else:
self.quick_sort(nums, pivot + 1, right) # Recursively sort the right subarray
right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1]
"""Driver Code"""
if __name__ == "__main__":
# Quick sort
nums = [2, 4, 1, 0, 3, 5]
QuickSort().quick_sort(nums, 0, len(nums) - 1)
print("Quick sort completed nums =", nums)
# Quick sort (median pivot optimization)
nums1 = [2, 4, 1, 0, 3, 5]
QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1)
print("Quick sort (median pivot optimization) completed nums =", nums1)
# Quick sort (tail recursion optimization)
nums2 = [2, 4, 1, 0, 3, 5]
QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1)
print("Quick sort (tail recursion optimization) completed nums =", nums2)

View File

@@ -0,0 +1,69 @@
"""
File: radix_sort.py
Created Time: 2023-03-26
Author: krahets (krahets@163.com)
"""
def digit(num: int, exp: int) -> int:
"""Get the k-th digit of element num, where exp = 10^(k-1)"""
# Passing exp instead of k can avoid repeated expensive exponentiation here
return (num // exp) % 10
def counting_sort_digit(nums: list[int], exp: int):
"""Counting sort (based on nums k-th digit)"""
# Decimal digit range is 0~9, therefore need a bucket array of length 10
counter = [0] * 10
n = len(nums)
# Count the occurrence of digits 0~9
for i in range(n):
d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d
counter[d] += 1 # Count the occurrence of digit d
# Calculate prefix sum, converting "occurrence count" into "array index"
for i in range(1, 10):
counter[i] += counter[i - 1]
# Traverse in reverse, based on bucket statistics, place each element into res
res = [0] * n
for i in range(n - 1, -1, -1):
d = digit(nums[i], exp)
j = counter[d] - 1 # Get the index j for d in the array
res[j] = nums[i] # Place the current element at index j
counter[d] -= 1 # Decrease the count of d by 1
# Use result to overwrite the original array nums
for i in range(n):
nums[i] = res[i]
def radix_sort(nums: list[int]):
"""Radix sort"""
# Get the maximum element of the array, used to determine the maximum number of digits
m = max(nums)
# Traverse from the lowest to the highest digit
exp = 1
while exp <= m:
# Perform counting sort on the k-th digit of array elements
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# i.e., exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
"""Driver Code"""
if __name__ == "__main__":
# Radix sort
nums = [
10546151,
35663510,
42865989,
34862445,
81883077,
88906420,
72429244,
30524779,
82060337,
63832996,
]
radix_sort(nums)
print("Radix sort completed nums =", nums)

View File

@@ -0,0 +1,26 @@
"""
File: selection_sort.py
Created Time: 2023-05-22
Author: krahets (krahets@163.com)
"""
def selection_sort(nums: list[int]):
"""Selection sort"""
n = len(nums)
# Outer loop: unsorted range is [i, n-1]
for i in range(n - 1):
# Inner loop: find the smallest element within the unsorted range
k = i
for j in range(i + 1, n):
if nums[j] < nums[k]:
k = j # Record the index of the smallest element
# Swap the smallest element with the first element of the unsorted range
nums[i], nums[k] = nums[k], nums[i]
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
selection_sort(nums)
print("Selection sort completed nums =", nums)

View File

@@ -0,0 +1,129 @@
"""
File: array_deque.py
Created Time: 2023-03-01
Author: krahets (krahets@163.com)
"""
class ArrayDeque:
"""Double-ended queue class based on circular array"""
def __init__(self, capacity: int):
"""Constructor"""
self._nums: list[int] = [0] * capacity
self._front: int = 0
self._size: int = 0
def capacity(self) -> int:
"""Get the capacity of the double-ended queue"""
return len(self._nums)
def size(self) -> int:
"""Get the length of the double-ended queue"""
return self._size
def is_empty(self) -> bool:
"""Determine if the double-ended queue is empty"""
return self._size == 0
def index(self, i: int) -> int:
"""Calculate circular array index"""
# Implement circular array by modulo operation
# When i exceeds the tail of the array, return to the head
# When i exceeds the head of the array, return to the tail
return (i + self.capacity()) % self.capacity()
def push_first(self, num: int):
"""Front enqueue"""
if self._size == self.capacity():
print("Double-ended queue is full")
return
# Move the front pointer one position to the left
# Implement front crossing the head of the array to return to the tail by modulo operation
self._front = self.index(self._front - 1)
# Add num to the front
self._nums[self._front] = num
self._size += 1
def push_last(self, num: int):
"""Rear enqueue"""
if self._size == self.capacity():
print("Double-ended queue is full")
return
# Calculate rear pointer, pointing to rear index + 1
rear = self.index(self._front + self._size)
# Add num to the rear
self._nums[rear] = num
self._size += 1
def pop_first(self) -> int:
"""Front dequeue"""
num = self.peek_first()
# Move front pointer one position backward
self._front = self.index(self._front + 1)
self._size -= 1
return num
def pop_last(self) -> int:
"""Rear dequeue"""
num = self.peek_last()
self._size -= 1
return num
def peek_first(self) -> int:
"""Access front element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._nums[self._front]
def peek_last(self) -> int:
"""Access rear element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# Calculate rear element index
last = self.index(self._front + self._size - 1)
return self._nums[last]
def to_array(self) -> list[int]:
"""Return array for printing"""
# Only convert elements within valid length range
res = []
for i in range(self._size):
res.append(self._nums[self.index(self._front + i)])
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize double-ended queue
deque = ArrayDeque(10)
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("Double-ended queue deque =", deque.to_array())
# Access element
peek_first: int = deque.peek_first()
print("Front element peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("Rear element peek_last =", peek_last)
# Element enqueue
deque.push_last(4)
print("Element 4 rear enqueued, deque =", deque.to_array())
deque.push_first(1)
print("Element 1 front enqueued, deque =", deque.to_array())
# Element dequeue
pop_last: int = deque.pop_last()
print("Rear dequeued element =", pop_last, ", deque after rear dequeue =", deque.to_array())
pop_first: int = deque.pop_first()
print("Front dequeued element =", pop_first, ", deque after front dequeue =", deque.to_array())
# Get the length of the double-ended queue
size: int = deque.size()
print("Double-ended queue length size =", size)
# Determine if the double-ended queue is empty
is_empty: bool = deque.is_empty()
print("Is the double-ended queue empty =", is_empty)

View File

@@ -0,0 +1,98 @@
"""
File: array_queue.py
Created Time: 2022-12-01
Author: Peng Chen (pengchzn@gmail.com)
"""
class ArrayQueue:
"""Queue class based on circular array"""
def __init__(self, size: int):
"""Constructor"""
self._nums: list[int] = [0] * size # Array for storing queue elements
self._front: int = 0 # Front pointer, pointing to the front element
self._size: int = 0 # Queue length
def capacity(self) -> int:
"""Get the capacity of the queue"""
return len(self._nums)
def size(self) -> int:
"""Get the length of the queue"""
return self._size
def is_empty(self) -> bool:
"""Determine if the queue is empty"""
return self._size == 0
def push(self, num: int):
"""Enqueue"""
if self._size == self.capacity():
raise IndexError("Queue is full")
# Calculate rear pointer, pointing to rear index + 1
# Use modulo operation to wrap the rear pointer from the end of the array back to the start
rear: int = (self._front + self._size) % self.capacity()
# Add num to the rear
self._nums[rear] = num
self._size += 1
def pop(self) -> int:
"""Dequeue"""
num: int = self.peek()
# Move front pointer one position backward, returning to the head of the array if it exceeds the tail
self._front = (self._front + 1) % self.capacity()
self._size -= 1
return num
def peek(self) -> int:
"""Access front element"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._nums[self._front]
def to_list(self) -> list[int]:
"""Return array for printing"""
res = [0] * self.size()
j: int = self._front
for i in range(self.size()):
res[i] = self._nums[(j % self.capacity())]
j += 1
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize queue
queue = ArrayQueue(10)
# Element enqueue
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("Queue queue =", queue.to_list())
# Access front element
peek: int = queue.peek()
print("Front element peek =", peek)
# Element dequeue
pop: int = queue.pop()
print("Dequeued element pop =", pop)
print("Queue after dequeue =", queue.to_list())
# Get the length of the queue
size: int = queue.size()
print("Queue length size =", size)
# Determine if the queue is empty
is_empty: bool = queue.is_empty()
print("Is the queue empty =", is_empty)
# Test circular array
for i in range(10):
queue.push(i)
queue.pop()
print("In the ", i, "th round of enqueue + dequeue, queue = ", queue.to_list())

View File

@@ -0,0 +1,72 @@
"""
File: array_stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
class ArrayStack:
"""Stack class based on array"""
def __init__(self):
"""Constructor"""
self._stack: list[int] = []
def size(self) -> int:
"""Get the length of the stack"""
return len(self._stack)
def is_empty(self) -> bool:
"""Determine if the stack is empty"""
return self.size() == 0
def push(self, item: int):
"""Push"""
self._stack.append(item)
def pop(self) -> int:
"""Pop"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._stack.pop()
def peek(self) -> int:
"""Access stack top element"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._stack[-1]
def to_list(self) -> list[int]:
"""Return array for printing"""
return self._stack
"""Driver Code"""
if __name__ == "__main__":
# Initialize stack
stack = ArrayStack()
# Element push
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("Stack stack =", stack.to_list())
# Access stack top element
peek: int = stack.peek()
print("Stack top element peek =", peek)
# Element pop
pop: int = stack.pop()
print("Popped element pop =", pop)
print("Stack after pop =", stack.to_list())
# Get the length of the stack
size: int = stack.size()
print("Stack length size =", size)
# Determine if it's empty
is_empty: bool = stack.is_empty()
print("Is the stack empty =", is_empty)

View File

@@ -0,0 +1,42 @@
"""
File: deque.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
from collections import deque
"""Driver Code"""
if __name__ == "__main__":
# Initialize double-ended queue
deq: deque[int] = deque()
# Element enqueue
deq.append(2) # Add to rear
deq.append(5)
deq.append(4)
deq.appendleft(3) # Add to front
deq.appendleft(1)
print("Double-ended queue deque =", deq)
# Access element
front: int = deq[0] # Front element
print("Front element front =", front)
rear: int = deq[-1] # Rear element
print("Rear element rear =", rear)
# Element dequeue
pop_front: int = deq.popleft() # Front element dequeue
print("Front dequeued element pop_front =", pop_front)
print("Deque after front dequeue =", deq)
pop_rear: int = deq.pop() # Rear element dequeue
print("Rear dequeued element pop_rear =", pop_rear)
print("Deque after rear dequeue =", deq)
# Get the length of the double-ended queue
size: int = len(deq)
print("Double-ended queue length size =", size)
# Determine if the double-ended queue is empty
is_empty: bool = len(deq) == 0
print("Is the double-ended queue empty =", is_empty)

View File

@@ -0,0 +1,151 @@
"""
File: linkedlist_deque.py
Created Time: 2023-03-01
Author: krahets (krahets@163.com)
"""
class ListNode:
"""Double-linked list node"""
def __init__(self, val: int):
"""Constructor"""
self.val: int = val
self.next: ListNode | None = None # Reference to the next node
self.prev: ListNode | None = None # Reference to predecessor node
class LinkedListDeque:
"""Double-ended queue class based on double-linked list"""
def __init__(self):
"""Constructor"""
self._front: ListNode | None = None # Head node front
self._rear: ListNode | None = None # Tail node rear
self._size: int = 0 # Length of the double-ended queue
def size(self) -> int:
"""Get the length of the double-ended queue"""
return self._size
def is_empty(self) -> bool:
"""Determine if the double-ended queue is empty"""
return self._size == 0
def push(self, num: int, is_front: bool):
"""Enqueue operation"""
node = ListNode(num)
# If the list is empty, make front and rear both point to node
if self.is_empty():
self._front = self._rear = node
# Front enqueue operation
elif is_front:
# Add node to the head of the list
self._front.prev = node
node.next = self._front
self._front = node # Update head node
# Rear enqueue operation
else:
# Add node to the tail of the list
self._rear.next = node
node.prev = self._rear
self._rear = node # Update tail node
self._size += 1 # Update queue length
def push_first(self, num: int):
"""Front enqueue"""
self.push(num, True)
def push_last(self, num: int):
"""Rear enqueue"""
self.push(num, False)
def pop(self, is_front: bool) -> int:
"""Dequeue operation"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# Front dequeue operation
if is_front:
val: int = self._front.val # Temporarily store the head node value
# Remove head node
fnext: ListNode | None = self._front.next
if fnext != None:
fnext.prev = None
self._front.next = None
self._front = fnext # Update head node
# Rear dequeue operation
else:
val: int = self._rear.val # Temporarily store the tail node value
# Remove tail node
rprev: ListNode | None = self._rear.prev
if rprev != None:
rprev.next = None
self._rear.prev = None
self._rear = rprev # Update tail node
self._size -= 1 # Update queue length
return val
def pop_first(self) -> int:
"""Front dequeue"""
return self.pop(True)
def pop_last(self) -> int:
"""Rear dequeue"""
return self.pop(False)
def peek_first(self) -> int:
"""Access front element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._front.val
def peek_last(self) -> int:
"""Access rear element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._rear.val
def to_array(self) -> list[int]:
"""Return array for printing"""
node = self._front
res = [0] * self.size()
for i in range(self.size()):
res[i] = node.val
node = node.next
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize double-ended queue
deque = LinkedListDeque()
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("Double-ended queue deque =", deque.to_array())
# Access element
peek_first: int = deque.peek_first()
print("Front element peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("Rear element peek_last =", peek_last)
# Element enqueue
deque.push_last(4)
print("Element 4 rear enqueued, deque =", deque.to_array())
deque.push_first(1)
print("Element 1 front enqueued, deque =", deque.to_array())
# Element dequeue
pop_last: int = deque.pop_last()
print("Rear dequeued element =", pop_last, ", deque after rear dequeue =", deque.to_array())
pop_first: int = deque.pop_first()
print("Front dequeued element =", pop_first, ", deque after front dequeue =", deque.to_array())
# Get the length of the double-ended queue
size: int = deque.size()
print("Double-ended queue length size =", size)
# Determine if the double-ended queue is empty
is_empty: bool = deque.is_empty()
print("Is the double-ended queue empty =", is_empty)

View File

@@ -0,0 +1,97 @@
"""
File: linkedlist_queue.py
Created Time: 2022-12-01
Author: Peng Chen (pengchzn@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
class LinkedListQueue:
"""Queue class based on linked list"""
def __init__(self):
"""Constructor"""
self._front: ListNode | None = None # Head node front
self._rear: ListNode | None = None # Tail node rear
self._size: int = 0
def size(self) -> int:
"""Get the length of the queue"""
return self._size
def is_empty(self) -> bool:
"""Determine if the queue is empty"""
return self._size == 0
def push(self, num: int):
"""Enqueue"""
# Add num behind the tail node
node = ListNode(num)
# If the queue is empty, make the head and tail nodes both point to that node
if self._front is None:
self._front = node
self._rear = node
# If the queue is not empty, add that node behind the tail node
else:
self._rear.next = node
self._rear = node
self._size += 1
def pop(self) -> int:
"""Dequeue"""
num = self.peek()
# Remove head node
self._front = self._front.next
self._size -= 1
return num
def peek(self) -> int:
"""Access front element"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._front.val
def to_list(self) -> list[int]:
"""Convert to a list for printing"""
queue = []
temp = self._front
while temp:
queue.append(temp.val)
temp = temp.next
return queue
"""Driver Code"""
if __name__ == "__main__":
# Initialize queue
queue = LinkedListQueue()
# Element enqueue
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("Queue queue =", queue.to_list())
# Access front element
peek: int = queue.peek()
print("Front element front =", peek)
# Element dequeue
pop_front: int = queue.pop()
print("Dequeued element pop =", pop_front)
print("Queue after dequeue =", queue.to_list())
# Get the length of the queue
size: int = queue.size()
print("Queue length size =", size)
# Determine if the queue is empty
is_empty: bool = queue.is_empty()
print("Is the queue empty =", is_empty)

View File

@@ -0,0 +1,89 @@
"""
File: linkedlist_stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
class LinkedListStack:
"""Stack class based on linked list"""
def __init__(self):
"""Constructor"""
self._peek: ListNode | None = None
self._size: int = 0
def size(self) -> int:
"""Get the length of the stack"""
return self._size
def is_empty(self) -> bool:
"""Determine if the stack is empty"""
return self._size == 0
def push(self, val: int):
"""Push"""
node = ListNode(val)
node.next = self._peek
self._peek = node
self._size += 1
def pop(self) -> int:
"""Pop"""
num = self.peek()
self._peek = self._peek.next
self._size -= 1
return num
def peek(self) -> int:
"""Access stack top element"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._peek.val
def to_list(self) -> list[int]:
"""Convert to a list for printing"""
arr = []
node = self._peek
while node:
arr.append(node.val)
node = node.next
arr.reverse()
return arr
"""Driver Code"""
if __name__ == "__main__":
# Initialize stack
stack = LinkedListStack()
# Element push
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("Stack stack =", stack.to_list())
# Access stack top element
peek: int = stack.peek()
print("Stack top element peek =", peek)
# Element pop
pop: int = stack.pop()
print("Popped element pop =", pop)
print("Stack after pop =", stack.to_list())
# Get the length of the stack
size: int = stack.size()
print("Stack length size =", size)
# Determine if it's empty
is_empty: bool = stack.is_empty()
print("Is the stack empty =", is_empty)

View File

@@ -0,0 +1,39 @@
"""
File: queue.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
from collections import deque
"""Driver Code"""
if __name__ == "__main__":
# Initialize queue
# In Python, we generally consider the deque class as a queue
# Although queue.Queue() is a pure queue class, it's not very user-friendly
que: deque[int] = deque()
# Element enqueue
que.append(1)
que.append(3)
que.append(2)
que.append(5)
que.append(4)
print("Queue que =", que)
# Access front element
front: int = que[0]
print("Front element front =", front)
# Element dequeue
pop: int = que.popleft()
print("Dequeued element pop =", pop)
print("Queue after dequeue =", que)
# Get the length of the queue
size: int = len(que)
print("Queue length size =", size)
# Determine if the queue is empty
is_empty: bool = len(que) == 0
print("Is the queue empty =", is_empty)

View File

@@ -0,0 +1,36 @@
"""
File: stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
"""Driver Code"""
if __name__ == "__main__":
# Initialize stack
# Python does not have a built-in stack class, but you can use a list as a stack
stack: list[int] = []
# Element push
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
print("Stack stack =", stack)
# Access stack top element
peek: int = stack[-1]
print("Stack top element peek =", peek)
# Element pop
pop: int = stack.pop()
print("Popped element pop =", pop)
print("Stack after pop =", stack)
# Get the length of the stack
size: int = len(stack)
print("Stack length size =", size)
# Determine if it's empty
is_empty: bool = len(stack) == 0
print("Is the stack empty =", is_empty)

View File

@@ -0,0 +1,119 @@
"""
File: array_binary_tree.py
Created Time: 2023-07-19
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
class ArrayBinaryTree:
"""Array-based binary tree class"""
def __init__(self, arr: list[int | None]):
"""Constructor"""
self._tree = list(arr)
def size(self):
"""List capacity"""
return len(self._tree)
def val(self, i: int) -> int | None:
"""Get the value of the node at index i"""
# If the index is out of bounds, return None, representing a vacancy
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
"""Get the index of the left child of the node at index i"""
return 2 * i + 1
def right(self, i: int) -> int | None:
"""Get the index of the right child of the node at index i"""
return 2 * i + 2
def parent(self, i: int) -> int | None:
"""Get the index of the parent of the node at index i"""
return (i - 1) // 2
def level_order(self) -> list[int]:
"""Level-order traversal"""
self.res = []
# Traverse array
for i in range(self.size()):
if self.val(i) is not None:
self.res.append(self.val(i))
return self.res
def dfs(self, i: int, order: str):
"""Depth-first traversal"""
if self.val(i) is None:
return
# Pre-order traversal
if order == "pre":
self.res.append(self.val(i))
self.dfs(self.left(i), order)
# In-order traversal
if order == "in":
self.res.append(self.val(i))
self.dfs(self.right(i), order)
# Post-order traversal
if order == "post":
self.res.append(self.val(i))
def pre_order(self) -> list[int]:
"""Pre-order traversal"""
self.res = []
self.dfs(0, order="pre")
return self.res
def in_order(self) -> list[int]:
"""In-order traversal"""
self.res = []
self.dfs(0, order="in")
return self.res
def post_order(self) -> list[int]:
"""Post-order traversal"""
self.res = []
self.dfs(0, order="post")
return self.res
"""Driver Code"""
if __name__ == "__main__":
# Initialize binary tree
# Use a specific function to convert an array into a binary tree
arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
root = list_to_tree(arr)
print("\nInitialize binary tree\n")
print("Array representation of the binary tree:")
print(arr)
print("Linked list representation of the binary tree:")
print_tree(root)
# Array-based binary tree class
abt = ArrayBinaryTree(arr)
# Access node
i = 1
l, r, p = abt.left(i), abt.right(i), abt.parent(i)
print(f"\nCurrent node index is {i}, value is {abt.val(i)}")
print(f"Its left child node index is {l}, value is {abt.val(l)}")
print(f"Its right child node index is {r}, value is {abt.val(r)}")
print(f"Its parent node index is {p}, value is {abt.val(p)}")
# Traverse tree
res = abt.level_order()
print("\nLevel-order traversal is:", res)
res = abt.pre_order()
print("Pre-order traversal is:", res)
res = abt.in_order()
print("In-order traversal is:", res)
res = abt.post_order()
print("Post-order traversal is:", res)

View File

@@ -0,0 +1,200 @@
"""
File: avl_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class AVLTree:
"""AVL tree"""
def __init__(self):
"""Constructor"""
self._root = None
def get_root(self) -> TreeNode | None:
"""Get binary tree root node"""
return self._root
def height(self, node: TreeNode | None) -> int:
"""Get node height"""
# Empty node height is -1, leaf node height is 0
if node is not None:
return node.height
return -1
def update_height(self, node: TreeNode | None):
"""Update node height"""
# Node height equals the height of the tallest subtree + 1
node.height = max([self.height(node.left), self.height(node.right)]) + 1
def balance_factor(self, node: TreeNode | None) -> int:
"""Get balance factor"""
# Empty node balance factor is 0
if node is None:
return 0
# Node balance factor = left subtree height - right subtree height
return self.height(node.left) - self.height(node.right)
def right_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""Right rotation operation"""
child = node.left
grand_child = child.right
# Rotate node to the right around child
child.right = node
node.left = grand_child
# Update node height
self.update_height(node)
self.update_height(child)
# Return the root of the subtree after rotation
return child
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""Left rotation operation"""
child = node.right
grand_child = child.left
# Rotate node to the left around child
child.left = node
node.right = grand_child
# Update node height
self.update_height(node)
self.update_height(child)
# Return the root of the subtree after rotation
return child
def rotate(self, node: TreeNode | None) -> TreeNode | None:
"""Perform rotation operation to restore balance to the subtree"""
# Get the balance factor of node
balance_factor = self.balance_factor(node)
# Left-leaning tree
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
# Right rotation
return self.right_rotate(node)
else:
# First left rotation then right rotation
node.left = self.left_rotate(node.left)
return self.right_rotate(node)
# Right-leaning tree
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
# Left rotation
return self.left_rotate(node)
else:
# First right rotation then left rotation
node.right = self.right_rotate(node.right)
return self.left_rotate(node)
# Balanced tree, no rotation needed, return
return node
def insert(self, val):
"""Insert node"""
self._root = self.insert_helper(self._root, val)
def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:
"""Recursively insert node (helper method)"""
if node is None:
return TreeNode(val)
# 1. Find insertion position and insert node
if val < node.val:
node.left = self.insert_helper(node.left, val)
elif val > node.val:
node.right = self.insert_helper(node.right, val)
else:
# Do not insert duplicate nodes, return
return node
# Update node height
self.update_height(node)
# 2. Perform rotation operation to restore balance to the subtree
return self.rotate(node)
def remove(self, val: int):
"""Remove node"""
self._root = self.remove_helper(self._root, val)
def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:
"""Recursively remove node (helper method)"""
if node is None:
return None
# 1. Find and remove the node
if val < node.val:
node.left = self.remove_helper(node.left, val)
elif val > node.val:
node.right = self.remove_helper(node.right, val)
else:
if node.left is None or node.right is None:
child = node.left or node.right
# Number of child nodes = 0, remove node and return
if child is None:
return None
# Number of child nodes = 1, remove node
else:
node = child
else:
# Number of child nodes = 2, remove the next node in in-order traversal and replace the current node with it
temp = node.right
while temp.left is not None:
temp = temp.left
node.right = self.remove_helper(node.right, temp.val)
node.val = temp.val
# Update node height
self.update_height(node)
# 2. Perform rotation operation to restore balance to the subtree
return self.rotate(node)
def search(self, val: int) -> TreeNode | None:
"""Search node"""
cur = self._root
# Loop find, break after passing leaf nodes
while cur is not None:
# Target node is in cur's right subtree
if cur.val < val:
cur = cur.right
# Target node is in cur's left subtree
elif cur.val > val:
cur = cur.left
# Found target node, break loop
else:
break
# Return target node
return cur
"""Driver Code"""
if __name__ == "__main__":
def test_insert(tree: AVLTree, val: int):
tree.insert(val)
print("\nInsert node {} after, AVL tree is".format(val))
print_tree(tree.get_root())
def test_remove(tree: AVLTree, val: int):
tree.remove(val)
print("\nRemove node {} after, AVL tree is".format(val))
print_tree(tree.get_root())
# Initialize empty AVL tree
avl_tree = AVLTree()
# Insert node
# Notice how the AVL tree maintains balance after inserting nodes
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]:
test_insert(avl_tree, val)
# Insert duplicate node
test_insert(avl_tree, 7)
# Remove node
# Notice how the AVL tree maintains balance after removing nodes
test_remove(avl_tree, 8) # Remove node with degree 0
test_remove(avl_tree, 5) # Remove node with degree 1
test_remove(avl_tree, 4) # Remove node with degree 2
result_node = avl_tree.search(7)
print("\nFound node object is {}, node value = {}".format(result_node, result_node.val))

View File

@@ -0,0 +1,146 @@
"""
File: binary_search_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class BinarySearchTree:
"""Binary search tree"""
def __init__(self):
"""Constructor"""
# Initialize empty tree
self._root = None
def get_root(self) -> TreeNode | None:
"""Get binary tree root node"""
return self._root
def search(self, num: int) -> TreeNode | None:
"""Search node"""
cur = self._root
# Loop find, break after passing leaf nodes
while cur is not None:
# Target node is in cur's right subtree
if cur.val < num:
cur = cur.right
# Target node is in cur's left subtree
elif cur.val > num:
cur = cur.left
# Found target node, break loop
else:
break
return cur
def insert(self, num: int):
"""Insert node"""
# If tree is empty, initialize root node
if self._root is None:
self._root = TreeNode(num)
return
# Loop find, break after passing leaf nodes
cur, pre = self._root, None
while cur is not None:
# Found duplicate node, thus return
if cur.val == num:
return
pre = cur
# Insertion position is in cur's right subtree
if cur.val < num:
cur = cur.right
# Insertion position is in cur's left subtree
else:
cur = cur.left
# Insert node
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
def remove(self, num: int):
"""Remove node"""
# If tree is empty, return
if self._root is None:
return
# Loop find, break after passing leaf nodes
cur, pre = self._root, None
while cur is not None:
# Found node to be removed, break loop
if cur.val == num:
break
pre = cur
# Node to be removed is in cur's right subtree
if cur.val < num:
cur = cur.right
# Node to be removed is in cur's left subtree
else:
cur = cur.left
# If no node to be removed, return
if cur is None:
return
# Number of child nodes = 0 or 1
if cur.left is None or cur.right is None:
# When the number of child nodes = 0/1, child = null/that child node
child = cur.left or cur.right
# Remove node cur
if cur != self._root:
if pre.left == cur:
pre.left = child
else:
pre.right = child
else:
# If the removed node is the root, reassign the root
self._root = child
# Number of child nodes = 2
else:
# Get the next node in in-order traversal of cur
tmp: TreeNode = cur.right
while tmp.left is not None:
tmp = tmp.left
# Recursively remove node tmp
self.remove(tmp.val)
# Replace cur with tmp
cur.val = tmp.val
"""Driver Code"""
if __name__ == "__main__":
# Initialize binary search tree
bst = BinarySearchTree()
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# Note that different insertion orders can result in various tree structures. This particular sequence creates a perfect binary tree
for num in nums:
bst.insert(num)
print("\nInitialized binary tree is\n")
print_tree(bst.get_root())
# Search node
node = bst.search(7)
print("\nFound node object is: {}, node value = {}".format(node, node.val))
# Insert node
bst.insert(16)
print("\nAfter inserting node 16, the binary tree is\n")
print_tree(bst.get_root())
# Remove node
bst.remove(1)
print("\nAfter removing node 1, the binary tree is\n")
print_tree(bst.get_root())
bst.remove(2)
print("\nAfter removing node 2, the binary tree is\n")
print_tree(bst.get_root())
bst.remove(4)
print("\nAfter removing node 4, the binary tree is\n")
print_tree(bst.get_root())

View File

@@ -0,0 +1,41 @@
"""
File: binary_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
"""Driver Code"""
if __name__ == "__main__":
# Initialize binary tree
# Initialize node
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# Construct node references (pointers)
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
print("\nInitialize binary tree\n")
print_tree(n1)
# Insert and remove nodes
P = TreeNode(0)
# Insert node P between n1 -> n2
n1.left = P
P.left = n2
print("\nAfter inserting node P\n")
print_tree(n1)
# Remove node
n1.left = n2
print("\nAfter removing node P\n")
print_tree(n1)

View File

@@ -0,0 +1,42 @@
"""
File: binary_tree_bfs.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
from collections import deque
def level_order(root: TreeNode | None) -> list[int]:
"""Level-order traversal"""
# Initialize queue, add root node
queue: deque[TreeNode] = deque()
queue.append(root)
# Initialize a list to store the traversal sequence
res = []
while queue:
node: TreeNode = queue.popleft() # Queue dequeues
res.append(node.val) # Save node value
if node.left is not None:
queue.append(node.left) # Left child node enqueues
if node.right is not None:
queue.append(node.right) # Right child node enqueues
return res
"""Driver Code"""
if __name__ == "__main__":
# Initialize binary tree
# Use a specific function to convert an array into a binary tree
root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\nInitialize binary tree\n")
print_tree(root)
# Level-order traversal
res: list[int] = level_order(root)
print("\nPrint sequence of nodes from level-order traversal = ", res)

View File

@@ -0,0 +1,65 @@
"""
File: binary_tree_dfs.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
def pre_order(root: TreeNode | None):
"""Pre-order traversal"""
if root is None:
return
# Visit priority: root node -> left subtree -> right subtree
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
def in_order(root: TreeNode | None):
"""In-order traversal"""
if root is None:
return
# Visit priority: left subtree -> root node -> right subtree
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
def post_order(root: TreeNode | None):
"""Post-order traversal"""
if root is None:
return
# Visit priority: left subtree -> right subtree -> root node
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
"""Driver Code"""
if __name__ == "__main__":
# Initialize binary tree
# Use a specific function to convert an array into a binary tree
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\nInitialize binary tree\n")
print_tree(root)
# Pre-order traversal
res = []
pre_order(root)
print("\nPrint sequence of nodes from pre-order traversal = ", res)
# In-order traversal
res.clear()
in_order(root)
print("\nPrint sequence of nodes from in-order traversal = ", res)
# Post-order traversal
res.clear()
post_order(root)
print("\nPrint sequence of nodes from post-order traversal = ", res)

View File

@@ -0,0 +1,19 @@
# Follow the PEP 585 - Type Hinting Generics In Standard Collections
# https://peps.python.org/pep-0585/
from __future__ import annotations
# Import common libs here to simplify the code by `from module import *`
from .list_node import (
ListNode,
list_to_linked_list,
linked_list_to_list,
)
from .tree_node import TreeNode, list_to_tree, tree_to_list
from .vertex import Vertex, vals_to_vets, vets_to_vals
from .print_util import (
print_matrix,
print_linked_list,
print_tree,
print_dict,
print_heap,
)

View File

@@ -0,0 +1,32 @@
"""
File: list_node.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com)
"""
class ListNode:
"""LinkedList node class"""
def __init__(self, val: int):
self.val: int = val # Node value
self.next: ListNode | None = None # Reference to the next node
def list_to_linked_list(arr: list[int]) -> ListNode | None:
"""Deserialize a list into a linked list"""
dum = head = ListNode(0)
for a in arr:
node = ListNode(a)
head.next = node
head = head.next
return dum.next
def linked_list_to_list(head: ListNode | None) -> list[int]:
"""Serialize a linked list into a list"""
arr: list[int] = []
while head:
arr.append(head.val)
head = head.next
return arr

View File

@@ -0,0 +1,81 @@
"""
File: print_util.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com)
"""
from .tree_node import TreeNode, list_to_tree
from .list_node import ListNode, linked_list_to_list
def print_matrix(mat: list[list[int]]):
"""Print matrix"""
s = []
for arr in mat:
s.append(" " + str(arr))
print("[\n" + ",\n".join(s) + "\n]")
def print_linked_list(head: ListNode | None):
"""Print linked list"""
arr: list[int] = linked_list_to_list(head)
print(" -> ".join([str(a) for a in arr]))
class Trunk:
def __init__(self, prev, string: str | None = None):
self.prev = prev
self.str = string
def show_trunks(p: Trunk | None):
if p is None:
return
show_trunks(p.prev)
print(p.str, end="")
def print_tree(
root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False
):
"""
Print binary tree
This tree printer is borrowed from TECHIE DELIGHT
https://www.techiedelight.com/c-program-print-binary-tree/
"""
if root is None:
return
prev_str = " "
trunk = Trunk(prev, prev_str)
print_tree(root.right, trunk, True)
if prev is None:
trunk.str = "———"
elif is_right:
trunk.str = "/———"
prev_str = " |"
else:
trunk.str = "\———"
prev.str = prev_str
show_trunks(trunk)
print(" " + str(root.val))
if prev:
prev.str = prev_str
trunk.str = " |"
print_tree(root.left, trunk, False)
def print_dict(hmap: dict):
"""Print dictionary"""
for key, value in hmap.items():
print(key, "->", value)
def print_heap(heap: list[int]):
"""Print heap"""
print("Array representation of the heap:", heap)
print("Tree representation of the heap:")
root: TreeNode | None = list_to_tree(heap)
print_tree(root)

View File

@@ -0,0 +1,69 @@
"""
File: tree_node.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com)
"""
from collections import deque
class TreeNode:
"""Binary tree node class"""
def __init__(self, val: int = 0):
self.val: int = val # Node value
self.height: int = 0 # Node height
self.left: TreeNode | None = None # Reference to the left child node
self.right: TreeNode | None = None # Reference to the right child node
# For serialization encoding rules, refer to:
# https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
# Array representation of the binary tree:
# [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
# Linked list representation of the binary tree:
# /——— 15
# /——— 7
# /——— 3
# | \——— 6
# | \——— 12
# ——— 1
# \——— 2
# | /——— 9
# \——— 4
# \——— 8
def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None:
"""Deserialize a list into a binary tree: Recursively"""
# If the index is out of array bounds, or the corresponding element is None, return None
if i < 0 or i >= len(arr) or arr[i] is None:
return None
# Construct the current node
root = TreeNode(arr[i])
# Recursively construct left and right subtrees
root.left = list_to_tree_dfs(arr, 2 * i + 1)
root.right = list_to_tree_dfs(arr, 2 * i + 2)
return root
def list_to_tree(arr: list[int]) -> TreeNode | None:
"""Deserialize a list into a binary tree"""
return list_to_tree_dfs(arr, 0)
def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]:
"""Serialize a binary tree into a list: Recursively"""
if root is None:
return
if i >= len(res):
res += [None] * (i - len(res) + 1)
res[i] = root.val
tree_to_list_dfs(root.left, 2 * i + 1, res)
tree_to_list_dfs(root.right, 2 * i + 2, res)
def tree_to_list(root: TreeNode | None) -> list[int]:
"""Serialize a binary tree into a list"""
res = []
tree_to_list_dfs(root, 0, res)
return res

View File

@@ -0,0 +1,20 @@
# File: vertex.py
# Created Time: 2023-02-23
# Author: krahets (krahets@163.com)
class Vertex:
"""Vertex class"""
def __init__(self, val: int):
self.val = val
def vals_to_vets(vals: list[int]) -> list["Vertex"]:
"""Input a list of values vals, return a list of vertices vets"""
return [Vertex(val) for val in vals]
def vets_to_vals(vets: list["Vertex"]) -> list[int]:
"""Input a list of vertices vets, return a list of values vals"""
return [vet.val for vet in vets]

View File

@@ -0,0 +1,33 @@
import os
import glob
import subprocess
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
if __name__ == "__main__":
# find source code files
src_paths = sorted(glob.glob("en/codes/python/chapter_*/*.py"))
errors = []
# run python code
for src_path in src_paths:
process = subprocess.Popen(
["python", src_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=env,
encoding='utf-8'
)
# Wait for the process to complete, and get the output and error messages
stdout, stderr = process.communicate()
# Check the exit status
exit_status = process.returncode
if exit_status != 0:
errors.append(stderr)
print(f"Tested {len(src_paths)} files")
print(f"Found exception in {len(errors)} files")
if len(errors) > 0:
raise RuntimeError("\n\n".join(errors))