Retranslate the complete English edition from Simplified Chinese (#2010)

* Retranslate the complete English edition from Simplified Chinese

* Polish three English explanations after reader-focused review
This commit is contained in:
Yudong Jin
2026-10-31 03:35:51 +08:00
committed by GitHub
parent ef46e2ed95
commit 14c314c08c
1502 changed files with 29459 additions and 23517 deletions

View File

@@ -8,69 +8,69 @@ import random
def random_access(nums: list[int]) -> int:
"""Random access to element"""
# Randomly select a number from the interval [0, len(nums)-1]
"""Access an element at a random index"""
# Choose a random number in the range [0, len(nums)-1]
random_index = random.randint(0, len(nums) - 1)
# Retrieve and return the random element
# Get and return the randomly selected element
random_num = nums[random_index]
return random_num
# Please note that Python's list is a dynamic array and can be extended directly
# For learning purposes, this function treats the list as an array with immutable length
# Note that a Python list is a dynamic array and can be extended directly
# For learning purposes, this function treats a list as a fixed-length array
def extend(nums: list[int], enlarge: int) -> list[int]:
"""Extend array length"""
# Initialize an array with extended length
"""Extend the array length"""
# Initialize an array with the extended length
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 extended new array
# Return the new, extended array
return res
def insert(nums: list[int], num: int, index: int):
"""Insert element num at index index in the array"""
# Move all elements at and after index index backward by one position
"""Insert element num at index `index` in the array"""
# Shift all elements at and after index `index` one position to the right
for i in range(len(nums) - 1, index, -1):
nums[i] = nums[i - 1]
# Assign num to the element at index index
# Assign num to the element at index `index`
nums[index] = num
def remove(nums: list[int], index: int):
"""Remove the element at index index"""
# Move all elements after index index forward by one position
"""Remove the element at index `index`"""
# Shift all elements after index `index` one position to the left
for i in range(index, len(nums) - 1):
nums[i] = nums[i + 1]
def traverse(nums: list[int]):
"""Traverse array"""
"""Traverse the array"""
count = 0
# Traverse array by index
# Traverse the array by index
for i in range(len(nums)):
count += nums[i]
# Direct traversal of array elements
# Iterate directly over the array elements
for num in nums:
count += num
# Traverse simultaneously data index and elements
# Iterate over both the indices and the elements
for i, num in enumerate(nums):
count += nums[i]
count += num
def find(nums: list[int], target: int) -> int:
"""Find the specified element in the array"""
"""Find a specified element in the array"""
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize array
# Initialize the array
arr = [0] * 5
print("Array arr =", arr)
nums = [1, 3, 2, 5, 4]
@@ -78,23 +78,23 @@ if __name__ == "__main__":
# Random access
random_num: int = random_access(nums)
print("Get random element from nums", random_num)
print("Randomly selected element from nums:", random_num)
# Length extension
# Extend the length
nums: list[int] = extend(nums, 3)
print("Extend the array length to 8, get nums =", nums)
print("Extend the array length to 8, giving nums =", nums)
# Insert element
# Insert an element
insert(nums, 6, 3)
print("Insert number 6 at index 3, get nums =", nums)
print("Insert the number 6 at index 3, giving nums =", nums)
# Remove element
# Remove an element
remove(nums, 2)
print("Remove the element at index 2, get nums =", nums)
print("Remove the element at index 2, giving nums =", nums)
# Traverse array
# Traverse the array
traverse(nums)
# Find element
# Find an element
index: int = find(nums, 3)
print("Search for element 3 in nums, get index =", index)
print("Find element 3 in nums, giving index =", index)

View File

@@ -29,7 +29,7 @@ def remove(n0: ListNode):
def access(head: ListNode, index: int) -> ListNode | None:
"""Access the node at index index in the linked list"""
"""Access the node at index `index` in the linked list"""
for _ in range(index):
if not head:
return None
@@ -48,16 +48,16 @@ def find(head: ListNode, target: int) -> int:
return -1
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize linked list
# Initialize each node
# Initialize the linked list
# Initialize the individual nodes
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# Build references between nodes
# Set up references between the nodes
n0.next = n1
n1.next = n2
n2.next = n3
@@ -65,21 +65,21 @@ if __name__ == "__main__":
print("The initialized linked list is")
print_linked_list(n0)
# Insert node
# Insert a node
p = ListNode(0)
insert(n0, p)
print("The linked list after inserting a node is")
print_linked_list(n0)
# Remove node
# Remove a node
remove(n0)
print("The linked list after removing a node is")
print_linked_list(n0)
# Access node
# Access a node
node: ListNode = access(n0, 3)
print("The value of the node at index 3 in the linked list = {}".format(node.val))
print("Value of the node at index 3 in the linked list = {}".format(node.val))
# Find node
# Find a node
index: int = find(n0, 2)
print("The index of the node with value 2 in the linked list = {}".format(index))
print("Index of the node with value 2 in the linked list = {}".format(index))

View File

@@ -4,53 +4,53 @@ Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize list
# Initialize the list
nums: list[int] = [1, 3, 2, 5, 4]
print("\nList nums =", nums)
# Access element
# Access an element
x: int = nums[1]
print("\nAccess the element at index 1, get x =", x)
print("\nAccess the element at index 1, giving x =", x)
# Update element
# Update an element
nums[1] = 0
print("\nUpdate the element at index 1 to 0, get nums =", nums)
print("\nUpdate the element at index 1 to 0, giving nums =", nums)
# Clear list
# Clear the list
nums.clear()
print("\nAfter clearing the list nums =", nums)
print("\nAfter clearing the list, nums =", nums)
# Add elements at the end
# Append an element at the end
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
print("\nAfter adding elements nums =", nums)
print("\nAfter adding elements, nums =", nums)
# Insert element in the middle
# Insert an element in the middle
nums.insert(3, 6)
print("\nInsert number 6 at index 3, get nums =", nums)
print("\nInsert the number 6 at index 3, giving nums =", nums)
# Remove element
# Remove an element
nums.pop(3)
print("\nRemove the element at index 3, get nums =", nums)
print("\nRemove the element at index 3, giving nums =", nums)
# Traverse list by index
# Traverse the list by index
count = 0
for i in range(len(nums)):
count += nums[i]
# Direct traversal of list elements
# Iterate directly over 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, get nums =", nums)
print("\nAppend list nums1 to nums, giving nums =", nums)
# Sort list
# Sort the list
nums.sort()
print("\nAfter sorting the list nums =", nums)
print("\nAfter sorting the list, nums =", nums)

View File

@@ -11,47 +11,47 @@ class MyList:
def __init__(self):
"""Constructor"""
self._capacity: int = 10 # List capacity
self._arr: list[int] = [0] * self._capacity # Array (stores list elements)
self._arr: list[int] = [0] * self._capacity # Array (stores the list elements)
self._size: int = 0 # List length (current number of elements)
self._extend_ratio: int = 2 # Multiple by which the list capacity is extended each time
self._extend_ratio: int = 2 # Factor by which the list capacity grows each time
def size(self) -> int:
"""Get list length (current number of elements)"""
"""Get the list length (current number of elements)"""
return self._size
def capacity(self) -> int:
"""Get list capacity"""
"""Get the 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
"""Access an element"""
# Raise an exception if the index is out of bounds; the same applies 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"""
"""Update an 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 extension mechanism
"""Append an element at the end"""
# Grow the capacity when adding an element would exceed it
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"""
"""Insert an 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 extension mechanism
# Grow the capacity when adding an element would exceed it
if self._size == self.capacity():
self.extend_capacity()
# Move all elements at and after index index backward by one position
# Shift all elements at and after index `index` one position to the right
for j in range(self._size - 1, index - 1, -1):
self._arr[j + 1] = self._arr[j]
self._arr[index] = num
@@ -59,11 +59,11 @@ class MyList:
self._size += 1
def remove(self, index: int) -> int:
"""Remove element"""
"""Remove an element"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
num = self._arr[index]
# Move all elements after index index forward by one position
# Shift all elements after index `index` one position to the left
for j in range(index, self._size - 1):
self._arr[j] = self._arr[j + 1]
# Update the number of elements
@@ -72,22 +72,22 @@ class MyList:
return num
def extend_capacity(self):
"""Extend list capacity"""
# Create a new array with length _extend_ratio times the original array, and copy the original array to the new array
"""Grow the list capacity"""
# Create a new array with _extend_ratio times the original length and copy the original array into it
self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)
# Update list capacity
# Update the list capacity
self._capacity = len(self._arr)
def to_array(self) -> list[int]:
"""Return list with valid length"""
"""Return the portion of the list that contains elements"""
return self._arr[: self._size]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize list
# Initialize the list
nums = MyList()
# Add elements at the end
# Append an element at the end
nums.add(1)
nums.add(3)
nums.add(2)
@@ -95,24 +95,24 @@ if __name__ == "__main__":
nums.add(4)
print(f"List nums = {nums.to_array()}, capacity = {nums.capacity()}, length = {nums.size()}")
# Insert element in the middle
# Insert an element in the middle
nums.insert(6, index=3)
print("Insert number 6 at index 3, get nums =", nums.to_array())
print("Insert the number 6 at index 3, giving nums =", nums.to_array())
# Remove element
# Remove an element
nums.remove(3)
print("Remove the element at index 3, get nums =", nums.to_array())
print("Remove the element at index 3, giving nums =", nums.to_array())
# Access element
# Access an element
num = nums.get(1)
print("Access the element at index 1, get num =", num)
print("Access the element at index 1, giving num =", num)
# Update element
# Update an element
nums.set(0, 1)
print("Update the element at index 1 to 0, get nums =", nums.to_array())
print("Update the element at index 1 to 0, giving nums =", nums.to_array())
# Test extension mechanism
# Test capacity growth
for i in range(10):
# At i = 5, the list length will exceed the list capacity, triggering the extension mechanism at this point
# When i = 5, the list length would exceed its capacity, triggering capacity growth
nums.add(i)
print(f"List after extension {nums.to_array()}, capacity = {nums.capacity()}, length = {nums.size()}")
print(f"List after growing its capacity: {nums.to_array()}, capacity = {nums.capacity()}, length = {nums.size()}")

View File

@@ -14,48 +14,48 @@ def backtrack(
diags1: list[bool],
diags2: list[bool],
):
"""Backtracking algorithm: N queens"""
# When all rows are placed, record the solution
"""Backtracking: n-queens"""
# Record a solution when a queen has been placed in every row
if row == n:
res.append([list(row) for row in state])
return
# Traverse all columns
# Iterate over all columns
for col in range(n):
# Calculate the main diagonal and anti-diagonal corresponding to this cell
# Calculate the main-diagonal and anti-diagonal indices for this square
diag1 = row - col + n - 1
diag2 = row + col
# Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell
# Prune: the square must have no queen in its column, main diagonal, or anti-diagonal
if not cols[col] and not diags1[diag1] and not diags2[diag2]:
# Attempt: place the queen in this cell
# Try: place a queen on this square
state[row][col] = "Q"
cols[col] = diags1[diag1] = diags2[diag2] = True
# Place the next row
# Place a queen in the next row
backtrack(row + 1, n, state, res, cols, diags1, diags2)
# Backtrack: restore this cell to an empty cell
# Backtrack: restore this square to empty
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 chessboard, where 'Q' represents a queen and '#' represents an empty cell
"""Solve the n-queens problem"""
# Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square
state = [["#" for _ in range(n)] for _ in range(n)]
cols = [False] * n # Record whether there is a queen in the column
diags1 = [False] * (2 * n - 1) # Record whether there is a queen on the main diagonal
diags2 = [False] * (2 * n - 1) # Record whether there is a queen on the anti-diagonal
cols = [False] * n # Track whether a column contains a queen
diags1 = [False] * (2 * n - 1) # Track whether a main diagonal contains a queen
diags2 = [False] * (2 * n - 1) # Track whether an anti-diagonal contains a queen
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 4
res = n_queens(n)
print(f"Input chessboard size is {n}")
print(f"There are {len(res)} queen placement solutions")
print(f"The input board has width and height {n}")
print(f"Number of ways to place the queens: {len(res)}")
for state in res:
print("--------------------")
for row in state:

View File

@@ -8,21 +8,21 @@ Author: krahets (krahets@163.com)
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""Backtracking algorithm: Permutations I"""
# When the state length equals the number of elements, record the solution
"""Backtracking: permutations I"""
# Record a solution when the state length equals the number of elements
if len(state) == len(choices):
res.append(list(state))
return
# Traverse all choices
# Iterate over all choices
for i, choice in enumerate(choices):
# Pruning: do not allow repeated selection of elements
# Prune: do not choose an element more than once
if not selected[i]:
# Attempt: make choice, update state
# Try: make a choice and update the state
selected[i] = True
state.append(choice)
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, choices, selected, res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore the previous state
selected[i] = False
state.pop()
@@ -34,11 +34,11 @@ def permutations_i(nums: list[int]) -> list[list[int]]:
return res
"""Driver Code"""
"""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}")
print(f"All permutations: res = {res}")

View File

@@ -8,23 +8,23 @@ Author: krahets (krahets@163.com)
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""Backtracking algorithm: Permutations II"""
# When the state length equals the number of elements, record the solution
"""Backtracking: permutations II"""
# Record a solution when the state length equals the number of elements
if len(state) == len(choices):
res.append(list(state))
return
# Traverse all choices
# Iterate over 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
# Prune: do not reuse an element or choose the same value again at this depth
if not selected[i] and choice not in duplicated:
# Attempt: make choice, update state
duplicated.add(choice) # Record the selected element value
# Try: make a choice and update the state
duplicated.add(choice) # Record the chosen value
selected[i] = True
state.append(choice)
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, choices, selected, res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore the previous state
selected[i] = False
state.pop()
@@ -36,11 +36,11 @@ def permutations_ii(nums: list[int]) -> list[list[int]]:
return res
"""Driver Code"""
"""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}")
print(f"All permutations: res = {res}")

View File

@@ -12,20 +12,20 @@ from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Preorder traversal: Example 1"""
"""Preorder traversal: example 1"""
if root is None:
return
if root.val == 7:
# Record solution
# Record the solution
res.append(root)
pre_order(root.left)
pre_order(root.right)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print("\nInitialize the binary tree")
print_tree(root)
# Preorder traversal

View File

@@ -12,13 +12,13 @@ from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Preorder traversal: Example 2"""
"""Preorder traversal: example 2"""
if root is None:
return
# Attempt
# Try
path.append(root)
if root.val == 7:
# Record solution
# Record the solution
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
@@ -26,10 +26,10 @@ def pre_order(root: TreeNode):
path.pop()
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print("\nInitialize the binary tree")
print_tree(root)
# Preorder traversal
@@ -37,6 +37,6 @@ if __name__ == "__main__":
res = list[list[TreeNode]]()
pre_order(root)
print("\nOutput all paths from root node to node 7")
print("\nOutput all paths from the root to nodes with value 7")
for path in res:
print([node.val for node in path])

View File

@@ -12,14 +12,14 @@ from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""Preorder traversal: Example 3"""
# Pruning
"""Preorder traversal: example 3"""
# Prune
if root is None or root.val == 3:
return
# Attempt
# Try
path.append(root)
if root.val == 7:
# Record solution
# Record the solution
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
@@ -27,10 +27,10 @@ def pre_order(root: TreeNode):
path.pop()
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print("\nInitialize the binary tree")
print_tree(root)
# Preorder traversal
@@ -38,6 +38,6 @@ if __name__ == "__main__":
res = list[list[TreeNode]]()
pre_order(root)
print("\nOutput all paths from root node to node 7, excluding paths with nodes of value 3")
print("\nOutput all paths from the root to nodes with value 7 that contain no node with value 3")
for path in res:
print([node.val for node in path])

View File

@@ -12,60 +12,60 @@ from modules import TreeNode, print_tree, list_to_tree
def is_solution(state: list[TreeNode]) -> bool:
"""Check if the current state is a solution"""
"""Check whether 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"""
"""Record the solution"""
res.append(list(state))
def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
"""Check if the choice is valid under the current state"""
"""Check whether the choice is valid in the current state"""
return choice is not None and choice.val != 3
def make_choice(state: list[TreeNode], choice: TreeNode):
"""Update state"""
"""Update the state"""
state.append(choice)
def undo_choice(state: list[TreeNode], choice: TreeNode):
"""Restore state"""
"""Restore the state"""
state.pop()
def backtrack(
state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]
):
"""Backtracking algorithm: Example 3"""
# Check if it is a solution
"""Backtracking: example 3"""
# Check whether this is a solution
if is_solution(state):
# Record solution
# Record the solution
record_solution(state, res)
# Traverse all choices
# Iterate over all choices
for choice in choices:
# Pruning: check if the choice is valid
# Prune: check whether the choice is valid
if is_valid(state, choice):
# Attempt: make choice, update state
# Try: make a choice and update the state
make_choice(state, choice)
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, [choice.left, choice.right], res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore the previous state
undo_choice(state, choice)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\nInitialize binary tree")
print("\nInitialize the binary tree")
print_tree(root)
# Backtracking algorithm
res = []
backtrack(state=[], choices=[root], res=res)
print("\nOutput all paths from root node to node 7, excluding paths with nodes of value 3")
print("\nOutput all paths from the root to nodes with value 7, requiring that no node on the path has value 3")
for path in res:
print([node.val for node in path])

View File

@@ -8,23 +8,23 @@ 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
"""Backtracking: subset sum I"""
# Record a solution when the remaining target is zero
if target == 0:
res.append(list(state))
return
# Traverse all choices
# Pruning 2: start traversing from start to avoid generating duplicate subsets
# Iterate over all choices
# Pruning 2: iterate from start to avoid generating duplicate subsets
for i in range(start, len(choices)):
# Pruning 1: if the subset sum exceeds target, end the loop directly
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
# Pruning 1: exit the loop if this choice exceeds the remaining target
# The array is sorted, so later elements are at least as large and also exceed the remaining target
if target - choices[i] < 0:
break
# Attempt: make choice, update target, start
# Try: make a choice and update target and start
state.append(choices[i])
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, target - choices[i], choices, i, res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore the previous state
state.pop()
@@ -32,17 +32,17 @@ 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)
start = 0 # Starting index for iteration
res = [] # Result list (list of subsets)
backtrack(state, target, nums, start, res)
return res
"""Driver Code"""
"""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 with sum equal to {target} res = {res}")
print(f"All subsets with sum {target}: res = {res}")

View File

@@ -12,39 +12,39 @@ def backtrack(
choices: list[int],
res: list[list[int]],
):
"""Backtracking algorithm: Subset sum I"""
# When the subset sum equals target, record the solution
"""Backtracking: subset sum I"""
# Record a solution when the subset sum equals target
if total == target:
res.append(list(state))
return
# Traverse all choices
# Iterate over all choices
for i in range(len(choices)):
# Pruning: if the subset sum exceeds target, skip this choice
# Prune: skip this choice if the subset sum would exceed target
if total + choices[i] > target:
continue
# Attempt: make choice, update element sum total
# Try: make a choice and update the sum total
state.append(choices[i])
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, target, total + choices[i], choices, res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore 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)"""
"""Solve subset sum I (with duplicate subsets)"""
state = [] # State (subset)
total = 0 # Subset sum
res = [] # Result list (subset list)
res = [] # Result list (list of subsets)
backtrack(state, target, total, nums, res)
return res
"""Driver Code"""
"""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 with sum equal to {target} res = {res}")
print(f"Please note that the result output by this method contains duplicate sets")
print(f"All subsets with sum {target}: res = {res}")
print(f"Note that this method produces duplicate subsets")

View File

@@ -8,27 +8,27 @@ 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
"""Backtracking: subset sum II"""
# Record a solution when the remaining target is zero
if target == 0:
res.append(list(state))
return
# Traverse all choices
# Pruning 2: start traversing from start to avoid generating duplicate subsets
# Pruning 3: start traversing from start to avoid repeatedly selecting the same element
# Iterate over all choices
# Pruning 2: iterate from start to avoid generating duplicate subsets
# Pruning 3: start after the previously chosen index to avoid choosing the same element again
for i in range(start, len(choices)):
# Pruning 1: if the subset sum exceeds target, end the loop directly
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
# Pruning 1: exit the loop if this choice exceeds the remaining target
# The array is sorted, so later elements are at least as large and also exceed the remaining target
if target - choices[i] < 0:
break
# Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly
# Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it
if i > start and choices[i] == choices[i - 1]:
continue
# Attempt: make choice, update target, start
# Try: make a choice and update target and start
state.append(choices[i])
# Proceed to the next round of selection
# Proceed to the next choice
backtrack(state, target - choices[i], choices, i + 1, res)
# Backtrack: undo choice, restore to previous state
# Backtrack: undo the choice and restore the previous state
state.pop()
@@ -36,17 +36,17 @@ 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)
start = 0 # Starting index for iteration
res = [] # Result list (list of subsets)
backtrack(state, target, nums, start, res)
return res
"""Driver Code"""
"""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 with sum equal to {target} res = {res}")
print(f"All subsets with sum {target}: res = {res}")

View File

@@ -6,7 +6,7 @@ Author: Hello Algo Team
def sum_iter(n: int) -> int:
"""Iterative summation"""
"""Sum using iteration"""
res = 0
for i in range(1, n + 1):
res += i
@@ -14,14 +14,14 @@ def sum_iter(n: int) -> int:
def sum_recur(n: int) -> int:
"""Recursive summation"""
"""Sum using recursion"""
if n == 1:
return 1
return n + sum_recur(n - 1)
def linear_loop(n: int) -> int:
"""Linear loop"""
"""Linear-time loop"""
res = 0
for i in range(n):
res += i
@@ -29,7 +29,7 @@ def linear_loop(n: int) -> int:
def quadratic_loop(n: int) -> int:
"""Quadratic loop"""
"""Quadratic-time loop"""
res = 0
for i in range(n):
for j in range(i, n):
@@ -38,7 +38,7 @@ def quadratic_loop(n: int) -> int:
def logarithmic_loop(n: int) -> int:
"""Logarithmic loop"""
"""Logarithmic-time loop"""
while n > 1:
n //= 2
return n

View File

@@ -8,7 +8,7 @@ Author: krahets (krahets@163.com)
def for_loop(n: int) -> int:
"""for loop"""
res = 0
# Sum 1, 2, ..., n-1, n
# Sum 1, 2, ..., n-1, n in a loop
for i in range(1, n + 1):
res += i
return res
@@ -17,49 +17,49 @@ def for_loop(n: int) -> int:
def while_loop(n: int) -> int:
"""while loop"""
res = 0
i = 1 # Initialize condition variable
# Sum 1, 2, ..., n-1, n
i = 1 # Initialize the loop control variable
# Sum 1, 2, ..., n-1, n in a loop
while i <= n:
res += i
i += 1 # Update condition variable
i += 1 # Update the loop control variable
return res
def while_loop_ii(n: int) -> int:
"""while loop (two updates)"""
res = 0
i = 1 # Initialize condition variable
# Sum 1, 4, 10, ...
i = 1 # Initialize the loop control variable
# Sum 1, 4, 10, ... in a loop
while i <= n:
res += i
# Update condition variable
# Update the loop control variable
i += 1
i *= 2
return res
def nested_for_loop(n: int) -> str:
"""Nested for loop"""
"""Nested for loops"""
res = ""
# Loop i = 1, 2, ..., n-1, n
# Loop over i = 1, 2, ..., n-1, n
for i in range(1, n + 1):
# Loop j = 1, 2, ..., n-1, n
# Loop over j = 1, 2, ..., n-1, n
for j in range(1, n + 1):
res += f"({i}, {j}), "
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 5
res = for_loop(n)
print(f"\nSum result of for loop res = {res}")
print(f"\nSum from the for loop: res = {res}")
res = while_loop(n)
print(f"\nSum result of while loop res = {res}")
print(f"\nSum from the while loop: res = {res}")
res = while_loop_ii(n)
print(f"\nSum result of while loop (two updates) res = {res}")
print(f"\nSum from the while loop (two updates): res = {res}")
res = nested_for_loop(n)
print(f"\nTraversal result of nested for loop {res}")
print(f"\nTraversal from the nested for loops: {res}")

View File

@@ -7,12 +7,12 @@ Author: krahets (krahets@163.com)
def recur(n: int) -> int:
"""Recursion"""
# Termination condition
# Base case
if n == 1:
return 1
# Recurse: recursive call
# Recurse: make a recursive call
res = recur(n - 1)
# Return: return result
# Unwind: return the result
return n + res
@@ -21,13 +21,13 @@ def for_loop_recur(n: int) -> int:
# Use an explicit stack to simulate the system call stack
stack = []
res = 0
# Recurse: recursive call
# Simulate the recursive-call phase
for i in range(n, 0, -1):
# Simulate "recurse" with "push"
# Simulate recursive calls by pushing onto the stack
stack.append(i)
# Return: return result
# Simulate unwinding and accumulating the result
while stack:
# Simulate "return" with "pop"
# Simulate unwinding by popping from the stack
res += stack.pop()
# res = 1+2+3+...+n
return res
@@ -35,35 +35,35 @@ def for_loop_recur(n: int) -> int:
def tail_recur(n, res):
"""Tail recursion"""
# Termination condition
# Base case
if n == 0:
return res
# Tail recursive call
# 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
# Base cases: 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)
# Recursive calls: f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# Return result f(n)
# Return the result f(n)
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 5
res = recur(n)
print(f"\nSum result of recursive function res = {res}")
print(f"\nSum from the recursive function: res = {res}")
res = for_loop_recur(n)
print(f"\nSum result of simulating recursion using iteration res = {res}")
print(f"\nSum from simulating recursion using iteration: res = {res}")
res = tail_recur(n, 0)
print(f"\nSum result of tail recursive function res = {res}")
print(f"\nSum from the tail-recursive function: res = {res}")
res = fib(n)
print(f"\nThe {n}th term of the Fibonacci sequence is {res}")
print(f"\nTerm {n} of the Fibonacci sequence is {res}")

View File

@@ -18,54 +18,54 @@ def function() -> int:
def constant(n: int):
"""Constant order"""
# Constants, variables, objects occupy O(1) space
"""Constant complexity"""
# Constants, variables, and objects occupy O(1) space
a = 0
nums = [0] * 10000
node = ListNode(0)
# Variables in the loop occupy O(1) space
for _ in range(n):
c = 0
# Functions in the loop occupy O(1) space
# Function calls in the loop occupy O(1) space
for _ in range(n):
function()
def linear(n: int):
"""Linear order"""
"""Linear complexity"""
# A list of length n occupies O(n) space
nums = [0] * n
# A hash table of length n occupies O(n) space
# A hash table with n entries occupies O(n) space
hmap = dict[int, str]()
for i in range(n):
hmap[i] = str(i)
def linear_recur(n: int):
"""Linear order (recursive implementation)"""
print("Recursion n =", n)
"""Linear complexity (recursive implementation)"""
print("Recursion: n =", n)
if n == 1:
return
linear_recur(n - 1)
def quadratic(n: int):
"""Quadratic order"""
# A 2D list occupies O(n^2) space
"""Quadratic complexity"""
# The two-dimensional list occupies O(n^2) space
num_matrix = [[0] * n for _ in range(n)]
def quadratic_recur(n: int) -> int:
"""Quadratic order (recursive implementation)"""
"""Quadratic complexity (recursive implementation)"""
if n <= 0:
return 0
# Array nums length is n, n-1, ..., 2, 1
# The lengths of array nums are n, n-1, ..., 2, 1
nums = [0] * n
return quadratic_recur(n - 1)
def build_tree(n: int) -> TreeNode | None:
"""Exponential order (build full binary tree)"""
"""Exponential complexity (building a perfect binary tree)"""
if n == 0:
return None
root = TreeNode(0)
@@ -74,17 +74,17 @@ def build_tree(n: int) -> TreeNode | None:
return root
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 5
# Constant order
# Constant complexity
constant(n)
# Linear order
# Linear complexity
linear(n)
linear_recur(n)
# Quadratic order
# Quadratic complexity
quadratic(n)
quadratic_recur(n)
# Exponential order
# Exponential complexity
root = build_tree(n)
print_tree(root)

View File

@@ -6,7 +6,7 @@ Author: krahets (krahets@163.com)
def constant(n: int) -> int:
"""Constant order"""
"""Constant complexity"""
count = 0
size = 100000
for _ in range(size):
@@ -15,7 +15,7 @@ def constant(n: int) -> int:
def linear(n: int) -> int:
"""Linear order"""
"""Linear complexity"""
count = 0
for _ in range(n):
count += 1
@@ -23,18 +23,18 @@ def linear(n: int) -> int:
def array_traversal(nums: list[int]) -> int:
"""Linear order (traversing array)"""
"""Linear complexity (array traversal)"""
count = 0
# Number of iterations is proportional to the array length
# The number of iterations is proportional to the array length
for num in nums:
count += 1
return count
def quadratic(n: int) -> int:
"""Quadratic order"""
"""Quadratic complexity"""
count = 0
# Number of iterations is quadratically related to the data size n
# The number of iterations is proportional to the square of the input size n
for i in range(n):
for j in range(n):
count += 1
@@ -42,26 +42,26 @@ def quadratic(n: int) -> int:
def bubble_sort(nums: list[int]) -> int:
"""Quadratic order (bubble sort)"""
"""Quadratic complexity (bubble sort)"""
count = 0 # Counter
# Outer loop: unsorted range is [0, i]
# Outer loop: the 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 rightmost end of that range
# Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps
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 unit operations
count += 3 # Swapping two elements takes 3 elementary operations
return count
def exponential(n: int) -> int:
"""Exponential order (loop implementation)"""
"""Exponential complexity (iterative implementation)"""
count = 0
base = 1
# Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)
# Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)
for _ in range(n):
for _ in range(base):
count += 1
@@ -71,14 +71,14 @@ def exponential(n: int) -> int:
def exp_recur(n: int) -> int:
"""Exponential order (recursive implementation)"""
"""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 order (loop implementation)"""
"""Logarithmic complexity (iterative implementation)"""
count = 0
while n > 1:
n = n / 2
@@ -87,67 +87,67 @@ def logarithmic(n: int) -> int:
def log_recur(n: int) -> int:
"""Logarithmic order (recursive implementation)"""
"""Logarithmic complexity (recursive implementation)"""
if n <= 1:
return 0
return log_recur(n / 2) + 1
def linear_log_recur(n: int) -> int:
"""Linearithmic order"""
"""Linearithmic complexity"""
if n <= 1:
return 1
# Divide into two, the scale of subproblems is reduced by half
# Split into two subproblems, each half the original size
count = linear_log_recur(n // 2) + linear_log_recur(n // 2)
# Current subproblem contains n operations
# The current subproblem includes n operations
for _ in range(n):
count += 1
return count
def factorial_recur(n: int) -> int:
"""Factorial order (recursive implementation)"""
"""Factorial complexity (recursive implementation)"""
if n == 0:
return 1
count = 0
# Split from 1 into n
# Split one problem into n subproblems
for _ in range(n):
count += factorial_recur(n - 1)
return count
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# You can modify n to run and observe the trend of the number of operations for various complexities
# Try different values of n to see how the operation count grows for each complexity
n = 8
print("Input data size n =", n)
print("Input size n =", n)
count = constant(n)
print("Number of operations of constant order =", count)
print("Operation count for constant complexity =", count)
count = linear(n)
print("Number of operations of linear order =", count)
print("Operation count for linear complexity =", count)
count = array_traversal([0] * n)
print("Number of operations of linear order (traversing array) =", count)
print("Operation count for linear complexity (array traversal) =", count)
count = quadratic(n)
print("Number of operations of quadratic order =", count)
print("Operation count for quadratic complexity =", count)
nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1]
count = bubble_sort(nums)
print("Number of operations of quadratic order (bubble sort) =", count)
print("Operation count for quadratic complexity (bubble sort) =", count)
count = exponential(n)
print("Number of operations of exponential order (loop implementation) =", count)
print("Operation count for exponential complexity (iterative implementation) =", count)
count = exp_recur(n)
print("Number of operations of exponential order (recursive implementation) =", count)
print("Operation count for exponential complexity (recursive implementation) =", count)
count = logarithmic(n)
print("Number of operations of logarithmic order (loop implementation) =", count)
print("Operation count for logarithmic complexity (iterative implementation) =", count)
count = log_recur(n)
print("Number of operations of logarithmic order (recursive implementation) =", count)
print("Operation count for logarithmic complexity (recursive implementation) =", count)
count = linear_log_recur(n)
print("Number of operations of linearithmic order (recursive implementation) =", count)
print("Operation count for linearithmic complexity (recursive implementation) =", count)
count = factorial_recur(n)
print("Number of operations of factorial order (recursive implementation) =", count)
print("Operation count for factorial complexity (recursive implementation) =", count)

View File

@@ -8,29 +8,29 @@ import random
def random_numbers(n: int) -> list[int]:
"""Generate an array with elements: 1, 2, ..., n, shuffled in order"""
# Generate array nums =: 1, 2, 3, ..., n
"""Generate an array containing 1, 2, ..., n in shuffled order"""
# Generate the array nums = 1, 2, 3, ..., n
nums = [i for i in range(1, n + 1)]
# Randomly shuffle array elements
# Randomly shuffle the array elements
random.shuffle(nums)
return nums
def find_one(nums: list[int]) -> int:
"""Find the index of number 1 in array nums"""
"""Find the index of the number 1 in array nums"""
for i in range(len(nums)):
# When element 1 is at the head of the array, best time complexity O(1) is achieved
# When element 1 is at the tail of the array, worst time complexity O(n) is achieved
# When 1 is at the start of the array, the best-case time complexity is O(1)
# When 1 is at the end of the array, the worst-case time complexity is O(n)
if nums[i] == 1:
return i
return -1
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
for i in range(10):
n = 100
nums: list[int] = random_numbers(n)
index: int = find_one(nums)
print("\nArray [ 1, 2, ..., n ] after being shuffled =", nums)
print("The index of number 1 is", index)
print("\nArray [ 1, 2, ..., n ] after shuffling =", nums)
print("The index of the number 1 is", index)

View File

@@ -7,34 +7,34 @@ 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, it means there is no target element, return -1
# If the interval is empty, the target is absent; return -1
if i > j:
return -1
# Calculate the midpoint index m
m = (i + j) // 2
if nums[m] < target:
# Recursion subproblem f(m+1, j)
# Recurse on subproblem f(m+1, j)
return dfs(nums, target, m + 1, j)
elif nums[m] > target:
# Recursion subproblem f(i, m-1)
# Recurse on subproblem f(i, m-1)
return dfs(nums, target, i, m - 1)
else:
# Found the target element, return its index
# The target element was found; return its index
return m
def binary_search(nums: list[int], target: int) -> int:
"""Binary search"""
n = len(nums)
# Solve the problem f(0, n-1)
# Solve problem f(0, n-1)
return dfs(nums, target, 0, n - 1)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (closed interval on both sides)
# Binary search (closed interval)
index = binary_search(nums, target)
print("Index of target element 6 = ", index)

View File

@@ -18,13 +18,13 @@ def dfs(
l: int,
r: int,
) -> TreeNode | None:
"""Build binary tree: divide and conquer"""
# Terminate when the subtree interval is empty
"""Build a binary tree: divide and conquer"""
# Stop when the subtree interval is empty
if r - l < 0:
return None
# Initialize the root node
root = TreeNode(preorder[i])
# Query m to divide the left and right subtrees
# Look up m to separate the left and right subtrees
m = inorder_map[preorder[i]]
# Subproblem: build the left subtree
root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)
@@ -35,14 +35,14 @@ def dfs(
def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:
"""Build binary tree"""
# Initialize hash map, storing the mapping from inorder elements to indices
"""Build a binary tree"""
# Initialize a hash table mapping each element in inorder to its index
inorder_map = {val: i for i, val in enumerate(inorder)}
root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)
return root
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
preorder = [3, 9, 2, 1, 7]
inorder = [9, 3, 1, 2, 7]
@@ -50,5 +50,5 @@ if __name__ == "__main__":
print(f"Inorder traversal = {inorder}")
root = build_tree(preorder, inorder)
print("The built binary tree is:")
print("The constructed binary tree is:")
print_tree(root)

View File

@@ -6,7 +6,7 @@ Author: Hello Algo Team
def fast_pow(x: int, n: int) -> int:
"""Exponentiation by squaring"""
"""Fast exponentiation"""
if n == 0:
return 1
half = fast_pow(x, n // 2)

View File

@@ -6,8 +6,8 @@ Author: krahets (krahets@163.com)
def move(src: list[int], tar: list[int]):
"""Move a disk"""
# Take out a disk from the top of src
"""Move one disk"""
# Take a disk from the top of src
pan = src.pop()
# Place the disk on top of tar
tar.append(pan)
@@ -15,13 +15,13 @@ def move(src: list[int], tar: list[int]):
def dfs(i: int, src: list[int], buf: list[int], tar: list[int]):
"""Solve the Tower of Hanoi problem f(i)"""
# If there is only one disk left in src, move it directly to tar
# If this subproblem has only one disk to move, move it directly from src to tar
if i == 1:
move(src, tar)
return
# Subproblem f(i-1): move the top i-1 disks from src to buf using tar
dfs(i - 1, src, tar, buf)
# Subproblem f(1): move the remaining disk from src to tar
# Subproblem f(1): move the remaining disk of this subproblem from src to tar
move(src, tar)
# Subproblem f(i-1): move the top i-1 disks from buf to tar using src
dfs(i - 1, buf, src, tar)
@@ -34,9 +34,9 @@ def solve_hanota(A: list[int], B: list[int], C: list[int]):
dfs(n, A, B, C)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# The tail of the list is the top of the rod
# The end of each list represents the top of the peg
A = [5, 4, 3, 2, 1]
B = []
C = []

View File

@@ -7,31 +7,31 @@ Author: krahets (krahets@163.com)
def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:
"""Backtracking"""
# When climbing to the n-th stair, add 1 to the solution count
# When step n is reached, increase the number of ways by 1
if state == n:
res[0] += 1
# Traverse all choices
# Iterate over all choices
for choice in choices:
# Pruning: not allowed to go beyond the n-th stair
# Prune: do not go beyond step n
if state + choice > n:
continue
# Attempt: make a choice, update state
# Try: make a choice and update the state
backtrack(choices, state + choice, n, res)
# Backtrack
def climbing_stairs_backtrack(n: int) -> int:
"""Climbing stairs: Backtracking"""
choices = [1, 2] # Can choose to climb up 1 or 2 stairs
state = 0 # Start climbing from the 0-th stair
res = [0] # Use res[0] to record the solution count
"""Climbing stairs: backtracking"""
choices = [1, 2] # Choose to climb 1 or 2 steps
state = 0 # Start climbing from step 0
res = [0] # Use res[0] to record the number of ways
backtrack(choices, state, n, res)
return res[0]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_backtrack(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")

View File

@@ -6,24 +6,24 @@ Author: krahets (krahets@163.com)
def climbing_stairs_constraint_dp(n: int) -> int:
"""Climbing stairs with constraint: Dynamic programming"""
"""Climbing stairs with constraints: dynamic programming"""
if n == 1 or n == 2:
return 1
# Initialize dp table, used to store solutions to subproblems
# Initialize the dp table to store subproblem solutions
dp = [[0] * 3 for _ in range(n + 1)]
# Initial state: preset the solution to the smallest subproblem
# Initial states: set the solutions to the smallest subproblems
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
# State transitions: solve progressively larger subproblems using 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"""
"""Driver code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_constraint_dp(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")

View File

@@ -7,7 +7,7 @@ Author: krahets (krahets@163.com)
def dfs(i: int) -> int:
"""Search"""
# Known dp[1] and dp[2], return them
# The values of dp[1] and dp[2] are known; return the corresponding value
if i == 1 or i == 2:
return i
# dp[i] = dp[i-1] + dp[i-2]
@@ -16,13 +16,13 @@ def dfs(i: int) -> int:
def climbing_stairs_dfs(n: int) -> int:
"""Climbing stairs: Search"""
"""Climbing stairs: search"""
return dfs(n)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")

View File

@@ -6,11 +6,11 @@ Author: krahets (krahets@163.com)
def dfs(i: int, mem: list[int]) -> int:
"""Memoization search"""
# Known dp[1] and dp[2], return them
"""Memoized search"""
# The values of dp[1] and dp[2] are known; return the corresponding value
if i == 1 or i == 2:
return i
# If record dp[i] exists, return it directly
# If dp[i] has been recorded, return it directly
if mem[i] != -1:
return mem[i]
# dp[i] = dp[i-1] + dp[i-2]
@@ -21,15 +21,15 @@ def dfs(i: int, mem: list[int]) -> int:
def climbing_stairs_dfs_mem(n: int) -> int:
"""Climbing stairs: Memoization search"""
# mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record
"""Climbing stairs: memoized search"""
# mem[i] stores the number of ways to reach step i; -1 means no result has been recorded
mem = [-1] * (n + 1)
return dfs(n, mem)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs_mem(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")

View File

@@ -6,21 +6,21 @@ Author: krahets (krahets@163.com)
def climbing_stairs_dp(n: int) -> int:
"""Climbing stairs: Dynamic programming"""
"""Climbing stairs: dynamic programming"""
if n == 1 or n == 2:
return n
# Initialize dp table, used to store solutions to subproblems
# Initialize the dp table to store subproblem solutions
dp = [0] * (n + 1)
# Initial state: preset the solution to the smallest subproblem
# Initial states: set the solutions to the smallest subproblems
dp[1], dp[2] = 1, 2
# State transition: gradually solve larger subproblems from smaller ones
# State transitions: solve progressively larger subproblems using 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"""
"""Climbing stairs: space-optimized dynamic programming"""
if n == 1 or n == 2:
return n
a, b = 1, 2
@@ -29,12 +29,12 @@ def climbing_stairs_dp_comp(n: int) -> int:
return b
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dp(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")
res = climbing_stairs_dp_comp(n)
print(f"Climbing {n} stairs has {res} solutions")
print(f"Number of ways to climb {n} stairs: {res}")

View File

@@ -6,55 +6,55 @@ Author: krahets (krahets@163.com)
def coin_change_dp(coins: list[int], amt: int) -> int:
"""Coin change: Dynamic programming"""
"""Coin change: dynamic programming"""
n = len(coins)
MAX = amt + 1
# Initialize dp table
# Initialize the dp table
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# State transition: first row and first column
# State transitions: first row and first column
for a in range(1, amt + 1):
dp[0][a] = MAX
# State transition: rest of the rows and columns
# State transitions: remaining rows and columns
for i in range(1, n + 1):
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeds target amount, don't select coin i
# If the coin value exceeds the current amount a, do not choose coin i
dp[i][a] = dp[i - 1][a]
else:
# The smaller value between not selecting and selecting coin i
# Take the smaller result of 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"""
"""Coin change: space-optimized dynamic programming"""
n = len(coins)
MAX = amt + 1
# Initialize dp table
# Initialize the dp table
dp = [MAX] * (amt + 1)
dp[0] = 0
# State transition
# State transitions
for i in range(1, n + 1):
# Traverse in forward order
# Traverse in increasing order
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeds target amount, don't select coin i
# If the coin value exceeds the current amount a, do not choose coin i
dp[a] = dp[a]
else:
# The smaller value between not selecting and selecting coin i
# Take the smaller result of 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"""
"""Driver code"""
if __name__ == "__main__":
coins = [1, 2, 5]
amt = 4
# Dynamic programming
res = coin_change_dp(coins, amt)
print(f"The minimum number of coins needed to make up the target amount is {res}")
print(f"Minimum number of coins needed to make the target amount: {res}")
# Space-optimized dynamic programming
res = coin_change_dp_comp(coins, amt)
print(f"The minimum number of coins needed to make up the target amount is {res}")
print(f"Minimum number of coins needed to make the target amount: {res}")

View File

@@ -6,53 +6,53 @@ Author: krahets (krahets@163.com)
def coin_change_ii_dp(coins: list[int], amt: int) -> int:
"""Coin change II: Dynamic programming"""
"""Coin change II: dynamic programming"""
n = len(coins)
# Initialize dp table
# Initialize the dp table
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# Initialize first column
# Initialize the first column
for i in range(n + 1):
dp[i][0] = 1
# State transition
# State transitions
for i in range(1, n + 1):
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeds target amount, don't select coin i
# If the coin value exceeds the current amount a, do not choose coin i
dp[i][a] = dp[i - 1][a]
else:
# Sum of the two options: not selecting and selecting coin i
# Add the numbers of ways without and with 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"""
"""Coin change II: space-optimized dynamic programming"""
n = len(coins)
# Initialize dp table
# Initialize the dp table
dp = [0] * (amt + 1)
dp[0] = 1
# State transition
# State transitions
for i in range(1, n + 1):
# Traverse in forward order
# Traverse in increasing order
for a in range(1, amt + 1):
if coins[i - 1] > a:
# If exceeds target amount, don't select coin i
# If the coin value exceeds the current amount a, do not choose coin i
dp[a] = dp[a]
else:
# Sum of the two options: not selecting and selecting coin i
# Add the numbers of ways without and with coin i
dp[a] = dp[a] + dp[a - coins[i - 1]]
return dp[amt]
"""Driver Code"""
"""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}")
print(f"Number of coin combinations that make the target amount: {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}")
print(f"Number of coin combinations that make the target amount: {res}")

View File

@@ -6,100 +6,100 @@ 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
"""Edit distance: brute-force search"""
# If both prefixes s[:i] and t[:j] are empty, return 0
if i == 0 and j == 0:
return 0
# If s is empty, return length of t
# If prefix s[:i] is empty, return the length j of prefix t[:j]
if i == 0:
return j
# If t is empty, return length of s
# If prefix t[:j] is empty, return the length i of prefix s[:i]
if j == 0:
return i
# If two characters are equal, skip both characters
# If the two characters are equal, skip both of them
if s[i - 1] == t[j - 1]:
return edit_distance_dfs(s, t, i - 1, j - 1)
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
# Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 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 minimum edit steps
# 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: Memoization search"""
# If both s and t are empty, return 0
"""Edit distance: memoized search"""
# If both prefixes s[:i] and t[:j] are empty, return 0
if i == 0 and j == 0:
return 0
# If s is empty, return length of t
# If prefix s[:i] is empty, return the length j of prefix t[:j]
if i == 0:
return j
# If t is empty, return length of s
# If prefix t[:j] is empty, return the length i of prefix s[:i]
if j == 0:
return i
# If there's a record, return it directly
# If a result has already been recorded, return it directly
if mem[i][j] != -1:
return mem[i][j]
# If two characters are equal, skip both characters
# If the two characters are equal, skip both of them
if s[i - 1] == t[j - 1]:
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
# Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 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 minimum edit steps
# 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"""
"""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
# State transitions: 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: rest of the rows and columns
# State transitions: remaining 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 two characters are equal, skip both characters
# If the two characters are equal, skip both of them
dp[i][j] = dp[i - 1][j - 1]
else:
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
# Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 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"""
"""Edit distance: space-optimized dynamic programming"""
n, m = len(s), len(t)
dp = [0] * (m + 1)
# State transition: first row
# State transitions: first row
for j in range(1, m + 1):
dp[j] = j
# State transition: rest of the rows
# State transitions: remaining rows
for i in range(1, n + 1):
# State transition: first column
# State transitions: first column
leftup = dp[0] # Temporarily store dp[i-1, j-1]
dp[0] += 1
# State transition: rest of the columns
# State transitions: remaining columns
for j in range(1, m + 1):
temp = dp[j]
if s[i - 1] == t[j - 1]:
# If two characters are equal, skip both characters
# If the two characters are equal, skip both of them
dp[j] = leftup
else:
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
# Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1
dp[j] = min(dp[j - 1], dp[j], leftup) + 1
leftup = temp # Update for next round's dp[i-1, j-1]
leftup = temp # Update to dp[i-1, j-1] for the next iteration
return dp[m]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
s = "bag"
t = "pack"
@@ -107,17 +107,17 @@ if __name__ == "__main__":
# Brute-force search
res = edit_distance_dfs(s, t, n, m)
print(f"Changing {s} to {t} requires a minimum of {res} edits")
print(f"Changing {s} to {t} requires at least {res} edits")
# Memoization search
# Memoized search
mem = [[-1] * (m + 1) for _ in range(n + 1)]
res = edit_distance_dfs_mem(s, t, mem, n, m)
print(f"Changing {s} to {t} requires a minimum of {res} edits")
print(f"Changing {s} to {t} requires at least {res} edits")
# Dynamic programming
res = edit_distance_dp(s, t)
print(f"Changing {s} to {t} requires a minimum of {res} edits")
print(f"Changing {s} to {t} requires at least {res} edits")
# Space-optimized dynamic programming
res = edit_distance_dp_comp(s, t)
print(f"Changing {s} to {t} requires a minimum of {res} edits")
print(f"Changing {s} to {t} requires at least {res} edits")

View File

@@ -6,77 +6,77 @@ 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 selected or knapsack has no remaining capacity, return value 0
"""0-1 knapsack: brute-force search"""
# If all items have been considered or no capacity remains, return value 0
if i == 0 or c == 0:
return 0
# If exceeds knapsack capacity, can only choose not to put it in
# If the item exceeds the remaining capacity, it must be excluded
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
# Calculate the maximum values obtained by excluding and including 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 larger value of the two options
# Return the larger value from the two choices
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: Memoization search"""
# If all items have been selected or knapsack has no remaining capacity, return value 0
"""0-1 knapsack: memoized search"""
# If all items have been considered or no capacity remains, return value 0
if i == 0 or c == 0:
return 0
# If there's a record, return it directly
# If a result has already been recorded, return it directly
if mem[i][c] != -1:
return mem[i][c]
# If exceeds knapsack capacity, can only choose not to put it in
# If the item exceeds the remaining capacity, it must be excluded
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
# Calculate the maximum values obtained by excluding and including 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 larger value of the two options
# Record and return the larger value from the two choices
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"""
"""0-1 knapsack: dynamic programming"""
n = len(wgt)
# Initialize dp table
# Initialize the dp table
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# State transition
# State transitions
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeds knapsack capacity, don't select item i
# If item i exceeds the remaining capacity, do not choose it
dp[i][c] = dp[i - 1][c]
else:
# The larger value between not selecting and selecting item i
# Take the larger value obtained by excluding and including 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"""
"""0-1 knapsack: space-optimized dynamic programming"""
n = len(wgt)
# Initialize dp table
# Initialize the dp table
dp = [0] * (cap + 1)
# State transition
# State transitions
for i in range(1, n + 1):
# Traverse in reverse order
# Traverse in decreasing order
for c in range(cap, 0, -1):
if wgt[i - 1] > c:
# If exceeds knapsack capacity, don't select item i
# If item i exceeds the remaining capacity, do not choose it
dp[c] = dp[c]
else:
# The larger value between not selecting and selecting item i
# Take the larger value obtained by excluding and including item i
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
@@ -85,17 +85,17 @@ if __name__ == "__main__":
# Brute-force search
res = knapsack_dfs(wgt, val, n, cap)
print(f"The maximum item value not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")
# Memoization search
# 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 not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")
# Dynamic programming
res = knapsack_dp(wgt, val, cap)
print(f"The maximum item value not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")
# Space-optimized dynamic programming
res = knapsack_dp_comp(wgt, val, cap)
print(f"The maximum item value not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")

View File

@@ -6,22 +6,22 @@ Author: krahets (krahets@163.com)
def min_cost_climbing_stairs_dp(cost: list[int]) -> int:
"""Minimum cost climbing stairs: Dynamic programming"""
"""Minimum cost to climb stairs: dynamic programming"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
# Initialize dp table, used to store solutions to subproblems
# Initialize the dp table to store subproblem solutions
dp = [0] * (n + 1)
# Initial state: preset the solution to the smallest subproblem
# Initial states: set the solutions to the smallest subproblems
dp[1], dp[2] = cost[1], cost[2]
# State transition: gradually solve larger subproblems from smaller ones
# State transitions: solve progressively larger subproblems using 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:
"""Minimum cost climbing stairs: Space-optimized dynamic programming"""
"""Minimum cost to climb stairs: space-optimized dynamic programming"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
@@ -31,13 +31,13 @@ def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:
return b
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]
print(f"Input stair cost list is {cost}")
print(f"Input list of stair costs: {cost}")
res = min_cost_climbing_stairs_dp(cost)
print(f"The minimum cost to finish climbing the stairs is {res}")
print(f"Minimum cost to finish climbing the stairs: {res}")
res = min_cost_climbing_stairs_dp_comp(cost)
print(f"The minimum cost to finish climbing the stairs is {res}")
print(f"Minimum cost to finish climbing the stairs: {res}")

View File

@@ -8,54 +8,54 @@ 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
"""Minimum path sum: brute-force search"""
# Stop searching at the top-left cell
if i == 0 and j == 0:
return grid[0][0]
# If row or column index is out of bounds, return +∞ cost
# If a row or column index is out of bounds, return a cost of +∞
if i < 0 or j < 0:
return inf
# Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)
# Calculate the minimum path costs from the top-left corner 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 top-left to (i, j)
# Return the minimum path cost from the top-left corner 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: Memoization search"""
# If it's the top-left cell, terminate the search
"""Minimum path sum: memoized search"""
# Stop searching at the top-left cell
if i == 0 and j == 0:
return grid[0][0]
# If row or column index is out of bounds, return +∞ cost
# If a row or column index is out of bounds, return a cost of +∞
if i < 0 or j < 0:
return inf
# If there's a record, return it directly
# If a result has already been recorded, return it directly
if mem[i][j] != -1:
return mem[i][j]
# Minimum path cost for left and upper cells
# Minimum path costs to the cells on the left and above
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 top-left to (i, j)
# Record and return the minimum path cost from the top-left corner 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"""
"""Minimum path sum: dynamic programming"""
n, m = len(grid), len(grid[0])
# Initialize dp table
# Initialize the dp table
dp = [[0] * m for _ in range(n)]
dp[0][0] = grid[0][0]
# State transition: first row
# State transitions: first row
for j in range(1, m):
dp[0][j] = dp[0][j - 1] + grid[0][j]
# State transition: first column
# State transitions: first column
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + grid[i][0]
# State transition: rest of the rows and columns
# State transitions: remaining 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]
@@ -63,42 +63,42 @@ def min_path_sum_dp(grid: list[list[int]]) -> int:
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:
"""Minimum path sum: Space-optimized dynamic programming"""
"""Minimum path sum: space-optimized dynamic programming"""
n, m = len(grid), len(grid[0])
# Initialize dp table
# Initialize the dp table
dp = [0] * m
# State transition: first row
# State transitions: first row
dp[0] = grid[0][0]
for j in range(1, m):
dp[j] = dp[j - 1] + grid[0][j]
# State transition: rest of the rows
# State transitions: remaining rows
for i in range(1, n):
# State transition: first column
# State transitions: first column
dp[0] = dp[0] + grid[i][0]
# State transition: rest of the columns
# State transitions: remaining columns
for j in range(1, m):
dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]
return dp[m - 1]
"""Driver Code"""
"""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 top-left to bottom-right is {res}")
print(f"Minimum path sum from the top-left to the bottom-right corner: {res}")
# Memoization search
# 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 top-left to bottom-right is {res}")
print(f"Minimum path sum from the top-left to the bottom-right corner: {res}")
# Dynamic programming
res = min_path_sum_dp(grid)
print(f"The minimum path sum from top-left to bottom-right is {res}")
print(f"Minimum path sum from the top-left to the bottom-right corner: {res}")
# Space-optimized dynamic programming
res = min_path_sum_dp_comp(grid)
print(f"The minimum path sum from top-left to bottom-right is {res}")
print(f"Minimum path sum from the top-left to the bottom-right corner: {res}")

View File

@@ -6,41 +6,41 @@ Author: krahets (krahets@163.com)
def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
"""Unbounded knapsack: Dynamic programming"""
"""Unbounded knapsack: dynamic programming"""
n = len(wgt)
# Initialize dp table
# Initialize the dp table
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# State transition
# State transitions
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeds knapsack capacity, don't select item i
# If item i exceeds the remaining capacity, do not choose it
dp[i][c] = dp[i - 1][c]
else:
# The larger value between not selecting and selecting item i
# Take the larger value obtained by excluding and including 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:
"""Unbounded knapsack: Space-optimized dynamic programming"""
"""Unbounded knapsack: space-optimized dynamic programming"""
n = len(wgt)
# Initialize dp table
# Initialize the dp table
dp = [0] * (cap + 1)
# State transition
# State transitions
for i in range(1, n + 1):
# Traverse in forward order
# Traverse in increasing order
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# If exceeds knapsack capacity, don't select item i
# If item i exceeds the remaining capacity, do not choose it
dp[c] = dp[c]
else:
# The larger value between not selecting and selecting item i
# Take the larger value obtained by excluding and including item i
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
wgt = [1, 2, 3]
val = [5, 11, 15]
@@ -48,8 +48,8 @@ if __name__ == "__main__":
# Dynamic programming
res = unbounded_knapsack_dp(wgt, val, cap)
print(f"The maximum item value not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")
# Space-optimized dynamic programming
res = unbounded_knapsack_dp_comp(wgt, val, cap)
print(f"The maximum item value not exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")

View File

@@ -12,11 +12,11 @@ from modules import Vertex, vals_to_vets
class GraphAdjList:
"""Undirected graph class based on adjacency list"""
"""Undirected graph class implemented with an adjacency list"""
def __init__(self, edges: list[list[Vertex]]):
"""Constructor"""
# Adjacency list, key: vertex, value: all adjacent vertices of that vertex
# Adjacency list: key is a vertex, value is the list of all its neighbors
self.adj_list = dict[Vertex, list[Vertex]]()
# Add all vertices and edges
for edge in edges:
@@ -29,7 +29,7 @@ class GraphAdjList:
return len(self.adj_list)
def add_edge(self, vet1: Vertex, vet2: Vertex):
"""Add edge"""
"""Add an edge"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# Add edge vet1 - vet2
@@ -37,7 +37,7 @@ class GraphAdjList:
self.adj_list[vet2].append(vet1)
def remove_edge(self, vet1: Vertex, vet2: Vertex):
"""Remove edge"""
"""Remove an edge"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# Remove edge vet1 - vet2
@@ -45,34 +45,34 @@ class GraphAdjList:
self.adj_list[vet2].remove(vet1)
def add_vertex(self, vet: Vertex):
"""Add vertex"""
"""Add a vertex"""
if vet in self.adj_list:
return
# Add a new linked list in the adjacency list
# Add a new list to the adjacency list
self.adj_list[vet] = []
def remove_vertex(self, vet: Vertex):
"""Remove vertex"""
"""Remove a vertex"""
if vet not in self.adj_list:
raise ValueError()
# Remove the linked list corresponding to vertex vet in the adjacency list
# Remove the list corresponding to vertex vet from the adjacency list
self.adj_list.pop(vet)
# Traverse the linked lists of other vertices and remove all edges containing vet
# Traverse the lists of the other vertices and remove all edges incident to vet
for vertex in self.adj_list:
if vet in self.adj_list[vertex]:
self.adj_list[vertex].remove(vet)
def print(self):
"""Print adjacency list"""
"""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"""
"""Driver code"""
if __name__ == "__main__":
# Initialize undirected graph
# Initialize the undirected graph
v = vals_to_vets([1, 3, 2, 5, 4])
edges = [
[v[0], v[1]],
@@ -83,29 +83,29 @@ if __name__ == "__main__":
[v[3], v[4]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
print("\nGraph after initialization:")
graph.print()
# Add edge
# Vertices 1, 2 are v[0], v[2]
# Add an edge
# Vertices 1 and 2 are v[0] and v[2]
graph.add_edge(v[0], v[2])
print("\nAfter adding edge 1-2, the graph is")
print("\nGraph after adding edge 1-2:")
graph.print()
# Remove edge
# Vertices 1, 3 are v[0], v[1]
# Remove an edge
# Vertices 1 and 3 are v[0] and v[1]
graph.remove_edge(v[0], v[1])
print("\nAfter removing edge 1-3, the graph is")
print("\nGraph after removing edge 1-3:")
graph.print()
# Add vertex
# Add a vertex
v5 = Vertex(6)
graph.add_vertex(v5)
print("\nAfter adding vertex 6, the graph is")
print("\nGraph after adding vertex 6:")
graph.print()
# Remove vertex
# Remove a vertex
# Vertex 3 is v[1]
graph.remove_vertex(v[1])
print("\nAfter removing vertex 3, the graph is")
print("\nGraph after removing vertex 3:")
graph.print()

View File

@@ -12,19 +12,19 @@ from modules import Vertex, print_matrix
class GraphAdjMat:
"""Undirected graph class based on adjacency matrix"""
"""Undirected graph class implemented with an adjacency matrix"""
def __init__(self, vertices: list[int], edges: list[list[int]]):
"""Constructor"""
# Vertex list, where the element represents the "vertex value" and the index represents the "vertex index"
# Vertex list: elements are vertex values, and indices are vertex indices
self.vertices: list[int] = []
# Adjacency matrix, where the row and column indices correspond to the "vertex index"
# Adjacency matrix: row and column indices correspond to vertex indices
self.adj_mat: list[list[int]] = []
# Add vertices
# Add a vertex
for val in vertices:
self.add_vertex(val)
# Add edges
# Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices
# Add an edge
# Note that the entries in edges are vertex indices, corresponding to element indices in vertices
for e in edges:
self.add_edge(e[0], e[1])
@@ -33,9 +33,9 @@ class GraphAdjMat:
return len(self.vertices)
def add_vertex(self, val: int):
"""Add vertex"""
"""Add a vertex"""
n = self.size()
# Add the value of the new vertex to the vertex list
# Append the new vertex value to the vertex list
self.vertices.append(val)
# Add a row to the adjacency matrix
new_row = [0] * n
@@ -45,72 +45,72 @@ class GraphAdjMat:
row.append(0)
def remove_vertex(self, index: int):
"""Remove vertex"""
"""Remove a vertex"""
if index >= self.size():
raise IndexError()
# Remove the vertex at index from the vertex list
# Remove the vertex at index `index` from the vertex list
self.vertices.pop(index)
# Remove the row at index from the adjacency matrix
# Remove row `index` from the adjacency matrix
self.adj_mat.pop(index)
# Remove the column at index from the adjacency matrix
# Remove column `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 the vertices element indices
# Handle index out of bounds and equality
"""Add an edge"""
# Parameters i and j are element indices in vertices
# Handle out-of-bounds or equal indices
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., (i, j) == (j, i)
# For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal
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 the vertices element indices
# Handle index out of bounds and equality
"""Remove an edge"""
# Parameters i and j are element indices in vertices
# Handle out-of-bounds or equal indices
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 the adjacency matrix"""
print("Vertex list =", self.vertices)
print("Adjacency matrix =")
print_matrix(self.adj_mat)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize undirected graph
# Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices
# Initialize the undirected graph
# Note that the entries in edges are vertex indices, corresponding to element indices in vertices
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")
print("\nGraph after initialization:")
graph.print()
# Add edge
# Vertices 1, 2 have indices 0, 2 respectively
# Add an edge
# Vertices 1 and 2 have indices 0 and 2, respectively
graph.add_edge(0, 2)
print("\nAfter adding edge 1-2, the graph is")
print("\nGraph after adding edge 1-2:")
graph.print()
# Remove edge
# Vertices 1, 3 have indices 0, 1 respectively
# Remove an edge
# Vertices 1 and 3 have indices 0 and 1, respectively
graph.remove_edge(0, 1)
print("\nAfter removing edge 1-3, the graph is")
print("\nGraph after removing edge 1-3:")
graph.print()
# Add vertex
# Add a vertex
graph.add_vertex(6)
print("\nAfter adding vertex 6, the graph is")
print("\nGraph after adding vertex 6:")
graph.print()
# Remove vertex
# Remove a vertex
# Vertex 3 has index 1
graph.remove_vertex(1)
print("\nAfter removing vertex 3, the graph is")
print("\nGraph after removing vertex 3:")
graph.print()

View File

@@ -15,30 +15,30 @@ 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, in order to obtain all adjacent vertices of a specified vertex
# Represent the graph with an adjacency list to access all neighbors of a given vertex
# Vertex traversal sequence
res = []
# Hash set for recording vertices that have been visited
# Hash set recording visited vertices
visited = set[Vertex]([start_vet])
# Queue used to implement BFS
# Use a queue to implement BFS
que = deque[Vertex]([start_vet])
# Starting from vertex vet, loop until all vertices are visited
# Starting from start_vet, loop until all reachable vertices have been visited
while len(que) > 0:
vet = que.popleft() # Dequeue the front vertex
res.append(vet) # Record visited vertex
# Traverse all adjacent vertices of this vertex
res.append(vet) # Record the visited vertex
# Traverse all neighbors of this vertex
for adj_vet in graph.adj_list[vet]:
if adj_vet in visited:
continue # Skip vertices that have been visited
que.append(adj_vet) # Only enqueue unvisited vertices
continue # Skip vertices that have already been visited
que.append(adj_vet) # Enqueue only unvisited vertices
visited.add(adj_vet) # Mark this vertex as visited
# Return vertex traversal sequence
# Return the vertex traversal sequence
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize undirected graph
# Initialize the undirected graph
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
edges = [
[v[0], v[1]],
@@ -55,10 +55,10 @@ if __name__ == "__main__":
[v[7], v[8]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
print("\nGraph after initialization:")
graph.print()
# Breadth-first traversal
res = graph_bfs(graph, v[0])
print("\nBreadth-first traversal (BFS) vertex sequence is")
print("\nVertex sequence from breadth-first traversal (BFS):")
print(vets_to_vals(res))

View File

@@ -14,30 +14,30 @@ 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
res.append(vet) # Record the visited vertex
visited.add(vet) # Mark this vertex as visited
# Traverse all adjacent vertices of this vertex
# Traverse all neighbors of this vertex
for adjVet in graph.adj_list[vet]:
if adjVet in visited:
continue # Skip vertices that have been visited
# Recursively visit adjacent vertices
continue # Skip vertices that have already been visited
# Recursively visit the neighboring vertex
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, in order to obtain all adjacent vertices of a specified vertex
# Represent the graph with an adjacency list to access all neighbors of a given vertex
# Vertex traversal sequence
res = []
# Hash set for recording vertices that have been visited
# Hash set recording visited vertices
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize undirected graph
# Initialize the undirected graph
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6])
edges = [
[v[0], v[1]],
@@ -48,10 +48,10 @@ if __name__ == "__main__":
[v[5], v[6]],
]
graph = GraphAdjList(edges)
print("\nAfter initialization, the graph is")
print("\nGraph after initialization:")
graph.print()
# Depth-first traversal
res = graph_dfs(graph, v[0])
print("\nDepth-first traversal (DFS) vertex sequence is")
print("\nVertex sequence from depth-first traversal (DFS):")
print(vets_to_vals(res))

View File

@@ -6,43 +6,43 @@ Author: krahets (krahets@163.com)
def coin_change_greedy(coins: list[int], amt: int) -> int:
"""Coin change: Greedy algorithm"""
# Assume coins list is sorted
"""Coin change: greedy algorithm"""
# Assume the coins list is sorted
i = len(coins) - 1
count = 0
# Loop to make greedy choices until no remaining amount
# Repeatedly make greedy choices until no amount remains
while amt > 0:
# Find the coin that is less than and closest to the remaining amount
# Find the largest coin no greater 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 -1 if no feasible solution was found
return count if amt == 0 else -1
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Greedy algorithm: Can guarantee finding the global optimal solution
# Greedy algorithm: guaranteed to find a globally 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 {amt} is {res}")
print(f"Number of coins chosen by the greedy algorithm for amount {amt}: {res}")
# Greedy algorithm: Cannot guarantee finding the global optimal solution
# Greedy algorithm: not guaranteed to find a globally 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 {amt} is {res}")
print(f"Actually the minimum number needed is 3, i.e., 20 + 20 + 20")
print(f"Number of coins chosen by the greedy algorithm for amount {amt}: {res}")
print(f"The actual minimum is 3 coins: 20 + 20 + 20")
# Greedy algorithm: Cannot guarantee finding the global optimal solution
# Greedy algorithm: not guaranteed to find a globally 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 {amt} is {res}")
print(f"Actually the minimum number needed is 2, i.e., 49 + 49")
print(f"Number of coins chosen by the greedy algorithm for amount {amt}: {res}")
print(f"The actual minimum is 2 coins: 49 + 49")

View File

@@ -14,27 +14,27 @@ class Item:
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
"""Fractional knapsack: Greedy algorithm"""
# Create item list with two attributes: weight, value
"""Fractional knapsack: greedy algorithm"""
# Create a list of items, each with two attributes: weight and value
items = [Item(w, v) for w, v in zip(wgt, val)]
# Sort by unit value item.v / item.w from high to low
# Sort by value per unit weight, item.v / item.w, in descending order
items.sort(key=lambda item: item.v / item.w, reverse=True)
# Loop for greedy selection
# Repeatedly make greedy choices
res = 0
for item in items:
if item.w <= cap:
# If remaining capacity is sufficient, put the entire current item into the knapsack
# If enough capacity remains, put the entire current item in the knapsack
res += item.v
cap -= item.w
else:
# If remaining capacity is insufficient, put part of the current item into the knapsack
# If the remaining capacity is insufficient, put part of the current item in the knapsack
res += (item.v / item.w) * cap
# No remaining capacity, so break out of the loop
# No capacity remains, so exit the loop
break
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
@@ -43,4 +43,4 @@ if __name__ == "__main__":
# Greedy algorithm
res = fractional_knapsack(wgt, val, cap)
print(f"The maximum item value without exceeding knapsack capacity is {res}")
print(f"Maximum total value within the knapsack capacity: {res}")

View File

@@ -6,17 +6,17 @@ Author: krahets (krahets@163.com)
def max_capacity(ht: list[int]) -> int:
"""Max capacity: Greedy algorithm"""
# Initialize i, j to be at both ends of the array
"""Maximum container capacity: greedy algorithm"""
# Initialize i and j at opposite ends of the array
i, j = 0, len(ht) - 1
# Initial max capacity is 0
# The initial maximum capacity is 0
res = 0
# Loop for greedy selection until the two boards meet
# Repeatedly make greedy choices until the two sides meet
while i < j:
# Update max capacity
# Update the maximum capacity
cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
# Move the shorter board inward
# Move the shorter side inward
if ht[i] < ht[j]:
i += 1
else:
@@ -24,10 +24,10 @@ def max_capacity(ht: list[int]) -> int:
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
ht = [3, 8, 5, 2, 7, 7, 3, 4]
# Greedy algorithm
res = max_capacity(ht)
print(f"Max capacity is {res}")
print(f"Maximum capacity: {res}")

View File

@@ -8,26 +8,26 @@ import math
def max_product_cutting(n: int) -> int:
"""Max product cutting: Greedy algorithm"""
# When n <= 3, must cut out a 1
"""Maximum product after splitting: greedy algorithm"""
# When n <= 3, one part must be 1
if n <= 3:
return 1 * (n - 1)
# Greedily cut out 3, a is the number of 3s, b is the remainder
# Greedily split off parts of size 3; a is the number of 3s and b is the remainder
a, b = n // 3, n % 3
if b == 1:
# When the remainder is 1, convert a pair of 1 * 3 to 2 * 2
# When the remainder is 1, replace one pair 1 * 3 with 2 * 2
return int(math.pow(3, a - 1)) * 2 * 2
if b == 2:
# When the remainder is 2, do nothing
# When the remainder is 2, no adjustment is needed
return int(math.pow(3, a)) * 2
# When the remainder is 0, do nothing
# When the remainder is 0, no adjustment is needed
return int(math.pow(3, a))
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
n = 58
# Greedy algorithm
res = max_product_cutting(n)
print(f"Max product cutting is {res}")
print(f"Maximum product after splitting: {res}")

View File

@@ -14,11 +14,11 @@ class Pair:
class ArrayHashMap:
"""Hash table based on array implementation"""
"""Hash table implemented with an array"""
def __init__(self):
"""Constructor"""
# Initialize array with 100 buckets
# Initialize an array containing 100 buckets
self.buckets: list[Pair | None] = [None] * 100
def hash_func(self, key: int) -> int:
@@ -27,7 +27,7 @@ class ArrayHashMap:
return index
def get(self, key: int) -> str | None:
"""Query operation"""
"""Lookup operation"""
index: int = self.hash_func(key)
pair: Pair = self.buckets[index]
if pair is None:
@@ -35,7 +35,7 @@ class ArrayHashMap:
return pair.val
def put(self, key: int, val: str):
"""Add and update operation"""
"""Insert and update operations"""
pair = Pair(key, val)
index: int = self.hash_func(key)
self.buckets[index] = pair
@@ -43,7 +43,7 @@ class ArrayHashMap:
def remove(self, key: int):
"""Remove operation"""
index: int = self.hash_func(key)
# Set to None to represent removal
# Set to None to mark removal
self.buckets[index] = None
def entry_set(self) -> list[Pair]:
@@ -71,47 +71,47 @@ class ArrayHashMap:
return result
def print(self):
"""Print hash table"""
"""Print the hash table"""
for pair in self.buckets:
if pair is not None:
print(pair.key, "->", pair.val)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize hash table
# Initialize the hash table
hmap = ArrayHashMap()
# Add operation
# Add key-value pair (key, value) to the hash table
hmap.put(12836, "Xiao Ha")
hmap.put(15937, "Xiao Luo")
hmap.put(16750, "Xiao Suan")
hmap.put(13276, "Xiao Fa")
hmap.put(10583, "Xiao Ya")
print("\nAfter adding, the hash table is\nKey -> Value")
# Insert operation
# Insert the key-value pair (key, value) into the hash table
hmap.put(12836, "小哈")
hmap.put(15937, "小啰")
hmap.put(16750, "小算")
hmap.put(13276, "小法")
hmap.put(10583, "小鸭")
print("\nHash table after insertion:\nKey -> Value")
hmap.print()
# Query operation
# Input key into the hash table to get value
# Lookup operation
# Look up key in the hash table to obtain value
name = hmap.get(15937)
print("\nInput student ID 15937, found name " + name)
print("\nLook up student ID 15937 to obtain the name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
# Remove the key-value pair (key, value) from the hash table
hmap.remove(10583)
print("\nAfter removing 10583, the hash table is\nKey -> Value")
print("\nHash table after removing 10583:\nKey -> Value")
hmap.print()
# Traverse hash table
print("\nTraverse key-value pairs Key->Value")
# Traverse the hash table
print("\nTraverse key-value pairs: Key->Value")
for pair in hmap.entry_set():
print(pair.key, "->", pair.val)
print("\nTraverse keys only Key")
print("\nTraverse keys only: Key")
for key in hmap.key_set():
print(key)
print("\nTraverse values only Value")
print("\nTraverse values only: Value")
for val in hmap.value_set():
print(val)

View File

@@ -10,28 +10,28 @@ from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
num = 3
hash_num = hash(num)
print(f"Hash value of integer {num} is {hash_num}")
print(f"Hash value of integer {num}: {hash_num}")
bol = True
hash_bol = hash(bol)
print(f"Hash value of boolean {bol} is {hash_bol}")
print(f"Hash value of Boolean {bol}: {hash_bol}")
dec = 3.14159
hash_dec = hash(dec)
print(f"Hash value of decimal {dec} is {hash_dec}")
print(f"Hash value of floating-point number {dec}: {hash_dec}")
str = "Hello algo"
str = "Hello 算法"
hash_str = hash(str)
print(f"Hash value of string {str} is {hash_str}")
print(f"Hash value of string {str}: {hash_str}")
tup = (12836, "Xiao Ha")
tup = (12836, "小哈")
hash_tup = hash(tup)
print(f"Hash value of tuple {tup} is {hash(hash_tup)}")
print(f"Hash of the hash value of tuple {tup}: {hash(hash_tup)}")
obj = ListNode(0)
hash_obj = hash(obj)
print(f"Hash value of node object {obj} is {hash_obj}")
print(f"Hash value of node object {obj}: {hash_obj}")

View File

@@ -10,41 +10,41 @@ from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_dict
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize hash table
# Initialize the hash table
hmap = dict[int, str]()
# Add operation
# Add key-value pair (key, value) to the hash table
hmap[12836] = "Xiao Ha"
hmap[15937] = "Xiao Luo"
hmap[16750] = "Xiao Suan"
hmap[13276] = "Xiao Fa"
hmap[10583] = "Xiao Ya"
print("\nAfter adding, the hash table is\nKey -> Value")
# Insert operation
# Insert the key-value pair (key, value) into the hash table
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"
print("\nHash table after insertion:\nKey -> Value")
print_dict(hmap)
# Query operation
# Input key into the hash table to get value
# Lookup operation
# Look up key in the hash table to obtain value
name: str = hmap[15937]
print("\nInput student ID 15937, found name " + name)
print("\nLook up student ID 15937 to obtain the name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
# Remove the key-value pair (key, value) from the hash table
hmap.pop(10583)
print("\nAfter removing 10583, the hash table is\nKey -> Value")
print("\nHash table after removing 10583:\nKey -> Value")
print_dict(hmap)
# Traverse hash table
print("\nTraverse key-value pairs Key->Value")
# Traverse the hash table
print("\nTraverse key-value pairs: Key->Value")
for key, value in hmap.items():
print(key, "->", value)
print("\nTraverse keys only Key")
print("\nTraverse keys only: Key")
for key in hmap.keys():
print(key)
print("\nTraverse values only Value")
print("\nTraverse values only: Value")
for val in hmap.values():
print(val)

View File

@@ -18,8 +18,8 @@ class HashMapChaining:
"""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.load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth
self.extend_ratio = 2 # Capacity growth factor
self.buckets = [[] for _ in range(self.capacity)] # Bucket array
def hash_func(self, key: int) -> int:
@@ -31,29 +31,29 @@ class HashMapChaining:
return self.size / self.capacity
def get(self, key: int) -> str | None:
"""Query operation"""
"""Lookup operation"""
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse bucket, if key is found, return corresponding val
# Traverse the bucket; if key is found, return its corresponding val
for pair in bucket:
if pair.key == key:
return pair.val
# If key is not found, return None
# Return None if key is not found
return None
def put(self, key: int, val: str):
"""Add operation"""
# When load factor exceeds threshold, perform expansion
"""Insert operation"""
# Grow the capacity when the load factor exceeds the threshold
if self.load_factor() > self.load_thres:
self.extend()
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse bucket, if specified key is encountered, update corresponding val and return
# Traverse the bucket; if the specified key is found, update its val and return
for pair in bucket:
if pair.key == key:
pair.val = val
return
# If key does not exist, append key-value pair to the end
# If key is absent, append the key-value pair at the end
pair = Pair(key, val)
bucket.append(pair)
self.size += 1
@@ -62,7 +62,7 @@ class HashMapChaining:
"""Remove operation"""
index = self.hash_func(key)
bucket = self.buckets[index]
# Traverse bucket and remove key-value pair from it
# Traverse the bucket and remove the key-value pair
for pair in bucket:
if pair.key == key:
bucket.remove(pair)
@@ -70,20 +70,20 @@ class HashMapChaining:
break
def extend(self):
"""Expand hash table"""
# Temporarily store the original hash table
"""Grow the hash table capacity"""
# Keep a reference to the original hash table
buckets = self.buckets
# Initialize expanded new hash table
# Initialize a new hash table with the expanded capacity
self.capacity *= self.extend_ratio
self.buckets = [[] for _ in range(self.capacity)]
self.size = 0
# Move key-value pairs from original hash table to new hash table
# Move the key-value pairs from the original hash table to the new one
for bucket in buckets:
for pair in bucket:
self.put(pair.key, pair.val)
def print(self):
"""Print hash table"""
"""Print the hash table"""
for bucket in self.buckets:
res = []
for pair in bucket:
@@ -91,28 +91,28 @@ class HashMapChaining:
print(res)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize hash table
# Initialize the hash table
hashmap = HashMapChaining()
# Add operation
# Add key-value pair (key, value) to the hash table
hashmap.put(12836, "Xiao Ha")
hashmap.put(15937, "Xiao Luo")
hashmap.put(16750, "Xiao Suan")
hashmap.put(13276, "Xiao Fa")
hashmap.put(10583, "Xiao Ya")
print("\nAfter adding, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]")
# Insert operation
# Insert the key-value pair (key, value) into the hash table
hashmap.put(12836, "小哈")
hashmap.put(15937, "小啰")
hashmap.put(16750, "小算")
hashmap.put(13276, "小法")
hashmap.put(10583, "小鸭")
print("\nHash table after insertion:\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()
# Query operation
# Input key into the hash table to get value
# Lookup operation
# Look up key in the hash table to obtain value
name = hashmap.get(13276)
print("\nInput student ID 13276, found name " + name)
print("\nLook up student ID 13276 to obtain the name " + name)
# Remove operation
# Remove key-value pair (key, value) from the hash table
# Remove the 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, ...]")
print("\nHash table after removing 12836:\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()

View File

@@ -18,10 +18,10 @@ class HashMapOpenAddressing:
"""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.load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth
self.extend_ratio = 2 # Capacity growth factor
self.buckets: list[Pair | None] = [None] * self.capacity # Bucket array
self.TOMBSTONE = Pair(-1, "-1") # Removal marker
self.TOMBSTONE = Pair(-1, "-1") # Tombstone marker
def hash_func(self, key: int) -> int:
"""Hash function"""
@@ -32,76 +32,76 @@ class HashMapOpenAddressing:
return self.size / self.capacity
def find_bucket(self, key: int) -> int:
"""Search for bucket index corresponding to key"""
"""Find the bucket index for key"""
index = self.hash_func(key)
first_tombstone = -1
# Linear probing, break when encountering an empty bucket
# Probe linearly until an empty bucket is reached
while self.buckets[index] is not None:
# If key is encountered, return the corresponding bucket index
# If key is found, return its bucket index
if self.buckets[index].key == key:
# If a removal marker was encountered before, move the key-value pair to that index
# If a tombstone 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 removal marker encountered
return first_tombstone # Return the bucket index after the move
return index # Return the bucket index
# Record the first tombstone encountered
if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:
first_tombstone = index
# Calculate bucket index, wrap around to the head if past the tail
# Calculate the bucket index, wrapping to the beginning after the end
index = (index + 1) % self.capacity
# If key does not exist, return the index for insertion
# If key is absent, return the index for insertion
return index if first_tombstone == -1 else first_tombstone
def get(self, key: int) -> str:
"""Query operation"""
# Search for bucket index corresponding to key
"""Lookup operation"""
# Find the bucket index for key
index = self.find_bucket(key)
# If key-value pair is found, return corresponding val
# If the key-value pair is found, return its val
if self.buckets[index] not in [None, self.TOMBSTONE]:
return self.buckets[index].val
# If key-value pair does not exist, return None
# Return None if the key-value pair is absent
return None
def put(self, key: int, val: str):
"""Add operation"""
# When load factor exceeds threshold, perform expansion
"""Insert operation"""
# Grow the capacity when the load factor exceeds the threshold
if self.load_factor() > self.load_thres:
self.extend()
# Search for bucket index corresponding to key
# Find the bucket index for key
index = self.find_bucket(key)
# If key-value pair is found, overwrite val and return
# 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 key-value pair does not exist, add the key-value pair
# If the key-value pair is absent, insert it
self.buckets[index] = Pair(key, val)
self.size += 1
def remove(self, key: int):
"""Remove operation"""
# Search for bucket index corresponding to key
# Find the bucket index for key
index = self.find_bucket(key)
# If key-value pair is found, overwrite it with removal marker
# If the key-value pair is found, replace it with a tombstone
if self.buckets[index] not in [None, self.TOMBSTONE]:
self.buckets[index] = self.TOMBSTONE
self.size -= 1
def extend(self):
"""Expand hash table"""
# Temporarily store the original hash table
"""Grow the hash table capacity"""
# Keep a reference to the original hash table
buckets_tmp = self.buckets
# Initialize expanded new hash table
# Initialize a new hash table with the expanded capacity
self.capacity *= self.extend_ratio
self.buckets = [None] * self.capacity
self.size = 0
# Move key-value pairs from original hash table to new hash table
# Move the key-value pairs from the original hash table to the new one
for pair in buckets_tmp:
if pair not in [None, self.TOMBSTONE]:
self.put(pair.key, pair.val)
def print(self):
"""Print hash table"""
"""Print the hash table"""
for pair in self.buckets:
if pair is None:
print("None")
@@ -111,28 +111,28 @@ class HashMapOpenAddressing:
print(pair.key, "->", pair.val)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize hash table
# Initialize the hash table
hashmap = HashMapOpenAddressing()
# Add operation
# Add key-value pair (key, val) to the hash table
hashmap.put(12836, "Xiao Ha")
hashmap.put(15937, "Xiao Luo")
hashmap.put(16750, "Xiao Suan")
hashmap.put(13276, "Xiao Fa")
hashmap.put(10583, "Xiao Ya")
print("\nAfter adding, the hash table is\nKey -> Value")
# Insert operation
# Insert the key-value pair (key, val) into the hash table
hashmap.put(12836, "小哈")
hashmap.put(15937, "小啰")
hashmap.put(16750, "小算")
hashmap.put(13276, "小法")
hashmap.put(10583, "小鸭")
print("\nHash table after insertion:\nKey -> Value")
hashmap.print()
# Query operation
# Input key into the hash table to get val
# Lookup operation
# Look up key in the hash table to obtain val
name = hashmap.get(13276)
print("\nInput student ID 13276, found name " + name)
print("\nLook up student ID 13276 to obtain the name " + name)
# Remove operation
# Remove key-value pair (key, val) from the hash table
# Remove the key-value pair (key, val) from the hash table
hashmap.remove(16750)
print("\nAfter removing 16750, the hash table is\nKey -> Value")
print("\nHash table after removing 16750:\nKey -> Value")
hashmap.print()

View File

@@ -33,7 +33,7 @@ def xor_hash(key: str) -> int:
def rot_hash(key: str) -> int:
"""Rotational hash"""
"""Rotating hash"""
hash = 0
modulus = 1000000007
for c in key:
@@ -41,18 +41,18 @@ def rot_hash(key: str) -> int:
return hash % modulus
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
key = "Hello algo"
key = "Hello 算法"
hash = add_hash(key)
print(f"Additive hash value is {hash}")
print(f"Additive hash value: {hash}")
hash = mul_hash(key)
print(f"Multiplicative hash value is {hash}")
print(f"Multiplicative hash value: {hash}")
hash = xor_hash(key)
print(f"XOR hash value is {hash}")
print(f"XOR hash value: {hash}")
hash = rot_hash(key)
print(f"Rotational hash value is {hash}")
print(f"Rotating hash value: {hash}")

View File

@@ -14,58 +14,58 @@ import heapq
def test_push(heap: list, val: int, flag: int = 1):
heapq.heappush(heap, flag * val) # Element enters heap
print(f"\nAfter element {val} enters heap")
heapq.heappush(heap, flag * val) # Push an element onto the heap
print(f"\nAfter pushing element {val} onto the heap:")
print_heap([flag * val for val in heap])
def test_pop(heap: list, flag: int = 1):
val = flag * heapq.heappop(heap) # Top element exits heap
print(f"\nAfter top element {val} exits heap")
val = flag * heapq.heappop(heap) # Pop the top element from the heap
print(f"\nAfter popping top element {val} from the heap:")
print_heap([flag * val for val in heap])
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize min heap
# Initialize a min heap
min_heap, flag = [], 1
# Initialize max heap
# Initialize a max heap
max_heap, flag = [], -1
print("\nThe following test cases are for max heap")
# Python's heapq module implements min heap by default
# Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap
# In this example, flag = 1 corresponds to min heap, flag = -1 corresponds to max heap
print("\nThe following example uses a max heap")
# Python's heapq module implements a min heap by default
# Negate each element before pushing it onto the heap to reverse the ordering and implement a max heap
# In this example, flag = 1 represents a min heap and flag = -1 represents a max heap
# Elements enter heap
# Push an element onto the 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)
# Get top element
# Get the top element of the heap
peek: int = flag * max_heap[0]
print(f"\nTop element is {peek}")
print(f"\nTop element of the heap: {peek}")
# Top element exits heap
# Pop the top element from the heap
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
# Get the heap size
size: int = len(max_heap)
print(f"\nNumber of heap elements is {size}")
print(f"\nNumber of elements in the heap: {size}")
# Check if heap is empty
# Check whether the heap is empty
is_empty: bool = not max_heap
print(f"\nIs heap empty {is_empty}")
print(f"\nIs the heap empty: {is_empty}")
# Input list and build heap
# Time complexity is O(n), not O(nlogn)
# Build a heap from an input list
# The time complexity is O(n), not O(nlogn)
min_heap = [1, 3, 2, 5, 4]
heapq.heapify(min_heap)
print("\nAfter inputting list and building min heap")
print("\nAfter building a min heap from the input list:")
print_heap(min_heap)

View File

@@ -15,23 +15,23 @@ class MaxHeap:
"""Max heap"""
def __init__(self, nums: list[int]):
"""Constructor, build heap based on input list"""
# Add list elements to heap as is
"""Constructor: build a heap from the input list"""
# Place the list elements in the heap without changing their order
self.max_heap = nums
# Heapify all nodes except leaf nodes
# Sift down from every non-leaf node
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"""
"""Get the left child index"""
return 2 * i + 1
def right(self, i: int) -> int:
"""Get index of right child node"""
"""Get the right child index"""
return 2 * i + 2
def parent(self, i: int) -> int:
"""Get index of parent node"""
"""Get the parent index"""
return (i - 1) // 2 # Floor division
def swap(self, i: int, j: int):
@@ -39,99 +39,99 @@ class MaxHeap:
self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i]
def size(self) -> int:
"""Get heap size"""
"""Get the heap size"""
return len(self.max_heap)
def is_empty(self) -> bool:
"""Check if heap is empty"""
"""Check whether the heap is empty"""
return self.size() == 0
def peek(self) -> int:
"""Access top element"""
"""Access the top element of the heap"""
return self.max_heap[0]
def push(self, val: int):
"""Element enters heap"""
# Add node
"""Push an element onto the heap"""
# Add a node
self.max_heap.append(val)
# Heapify from bottom to top
# Sift up
self.sift_up(self.size() - 1)
def sift_up(self, i: int):
"""Starting from node i, heapify from bottom to top"""
"""Sift up starting from node i"""
while True:
# Get parent node of node i
# Get the parent of node i
p = self.parent(i)
# When "crossing root node" or "node needs no repair", end heapify
# Stop sifting when the root has been passed or the node needs no repair
if p < 0 or self.max_heap[i] <= self.max_heap[p]:
break
# Swap two nodes
# Swap the two nodes
self.swap(i, p)
# Loop upward heapify
# Continue sifting up
i = p
def pop(self) -> int:
"""Element exits heap"""
# Handle empty case
"""Pop an element from the heap"""
# Handle an empty heap
if self.is_empty():
raise IndexError("Heap is empty")
# Swap root node with rightmost leaf node (swap first element with last element)
# Swap the root with the rightmost leaf on the last level (swap the first and last elements)
self.swap(0, self.size() - 1)
# Delete node
# Remove a node
val = self.max_heap.pop()
# Heapify from top to bottom
# Sift down
self.sift_down(0)
# Return top element
# Return the top element of the heap
return val
def sift_down(self, i: int):
"""Starting from node i, heapify from top to bottom"""
"""Sift down starting from node i"""
while True:
# Find node with largest value among i, l, r, denoted as ma
# Find the node with the largest value among i, l, and r, and store its index in 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 largest or indices l, r are out of bounds, no need to continue heapify, break
# Stop if node i is largest or both child indices l and r are out of bounds
if ma == i:
break
# Swap two nodes
# Swap the two nodes
self.swap(i, ma)
# Loop downward heapify
# Continue sifting down
i = ma
def print(self):
"""Print heap (binary tree)"""
"""Print the heap (binary tree)"""
print_heap(self.max_heap)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize max heap
# Initialize a max heap
max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2])
print("\nAfter inputting list and building heap")
print("\nAfter building a heap from the input list:")
max_heap.print()
# Get top element
# Get the top element of the heap
peek = max_heap.peek()
print(f"\nTop element is {peek}")
print(f"\nTop element of the heap: {peek}")
# Element enters heap
# Push an element onto the heap
val = 7
max_heap.push(val)
print(f"\nAfter element {val} enters heap")
print(f"\nAfter pushing element {val} onto the heap:")
max_heap.print()
# Top element exits heap
# Pop the top element from the heap
peek = max_heap.pop()
print(f"\nAfter top element {peek} exits heap")
print(f"\nAfter popping top element {peek} from the heap:")
max_heap.print()
# Get heap size
# Get the heap size
size = max_heap.size()
print(f"\nNumber of heap elements is {size}")
print(f"\nNumber of elements in the heap: {size}")
# Check if heap is empty
# Check whether the heap is empty
is_empty = max_heap.is_empty()
print(f"\nIs heap empty {is_empty}")
print(f"\nIs the heap empty: {is_empty}")

View File

@@ -14,26 +14,26 @@ import heapq
def top_k_heap(nums: list[int], k: int) -> list[int]:
"""Find the largest k elements in array based on heap"""
# Initialize min heap
"""Use a heap to find the k largest elements in an array"""
# Initialize a min heap
heap = []
# Enter the first k elements of array into heap
# Push the first k array elements onto the heap
for i in range(k):
heapq.heappush(heap, nums[i])
# Starting from the (k+1)th element, maintain heap length as k
# Starting with element k+1, keep the heap size at k
for i in range(k, len(nums)):
# If current element is greater than top element, top element exits heap, current element enters heap
# If the current element is larger than the heap top, pop the top element and push the current element
if nums[i] > heap[0]:
heapq.heappop(heap)
heapq.heappush(heap, nums[i])
return heap
"""Driver Code"""
"""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(f"The {k} largest elements are")
print_heap(res)

View File

@@ -7,46 +7,46 @@ Author: timi (xisunyy@163.com)
def binary_search(nums: list[int], target: int) -> int:
"""Binary search (closed interval)"""
# Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array
# Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements
i, j = 0, len(nums) - 1
# Loop, exit when the search interval is empty (empty when i > j)
# Loop until the search interval is empty (when i > j)
while i <= j:
# In theory, Python numbers can be infinitely large (depending on memory size), no need to consider large number overflow
m = (i + j) // 2 # Calculate midpoint index m
# Python integers can grow arbitrarily large, subject to available memory, so integer overflow is not a concern
m = (i + j) // 2 # Calculate the midpoint index m
if nums[m] < target:
i = m + 1 # This means target is in the interval [m+1, j]
i = m + 1 # In this case, target must be in [m+1, j] if it exists
elif nums[m] > target:
j = m - 1 # This means target is in the interval [i, m-1]
j = m - 1 # In this case, target must be in [i, m-1] if it exists
else:
return m # Found the target element, return its index
return -1 # Target element not found, return -1
return m # The target element was found; return its index
return -1 # The target element was not found; 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 last element+1
"""Binary search (left-closed, right-open interval)"""
# Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last
i, j = 0, len(nums)
# Loop, exit when the search interval is empty (empty when i = j)
# Loop until the search interval is empty (when i = j)
while i < j:
m = (i + j) // 2 # Calculate midpoint index m
m = (i + j) // 2 # Calculate the midpoint index m
if nums[m] < target:
i = m + 1 # This means target is in the interval [m+1, j)
i = m + 1 # In this case, target must be in [m+1, j) if it exists
elif nums[m] > target:
j = m # This means target is in the interval [i, m)
j = m # In this case, target must be in [i, m) if it exists
else:
return m # Found the target element, return its index
return -1 # Target element not found, return -1
return m # The target element was found; return its index
return -1 # The target element was not found; return -1
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (closed interval)
index = binary_search(nums, target)
print("Index of target element 6 =", index)
print("Index of target element 6 = ", index)
# Binary search (left-closed right-open interval)
# Binary search (left-closed, right-open interval)
index = binary_search_lcro(nums, target)
print("Index of target element 6 =", index)
print("Index of target element 6 = ", index)

View File

@@ -12,38 +12,38 @@ 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
"""Find the leftmost occurrence of target using binary search"""
# This is equivalent to finding the insertion point of target
i = binary_search_insertion(nums, target)
# Target not found, return -1
# If target was not found, return -1
if i == len(nums) or nums[i] != target:
return -1
# Found target, return index i
# Target was found; 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
"""Find the rightmost occurrence of target using binary search"""
# Reduce the problem to finding the leftmost insertion point of 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 is the candidate rightmost target index; i is the index of the first element greater than target, or len(nums)
j = i - 1
# Target not found, return -1
# If target was not found, return -1
if j == -1 or nums[j] != target:
return -1
# Found target, return index j
# Target was found; return index j
return j
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Array with duplicate elements
# Array containing duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\nArray nums = {nums}")
# Binary search for left boundary and right boundary
# Find the left and right boundaries using binary search
for target in [6, 7]:
index = binary_search_left_edge(nums, target)
print(f"Index of the leftmost element {target} is {index}")
print(f"Index of the leftmost occurrence of {target}: {index}")
index = binary_search_right_edge(nums, target)
print(f"Index of the rightmost element {target} is {index}")
print(f"Index of the rightmost occurrence of {target}: {index}")

View File

@@ -6,49 +6,49 @@ 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 closed interval [0, n-1]
"""Find an insertion point using binary search (no duplicates)"""
i, j = 0, len(nums) - 1 # Initialize the closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
m = (i + j) // 2 # Calculate the midpoint index m
if nums[m] < target:
i = m + 1 # target is in the interval [m+1, j]
i = m + 1 # Target must be in [m+1, j] if it exists
elif nums[m] > target:
j = m - 1 # target is in the interval [i, m-1]
j = m - 1 # Target must be in [i, m-1] if it exists
else:
return m # Found target, return insertion point m
# Target not found, return insertion point i
return m # Target was found; return insertion point m
# Target was not found; 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 closed interval [0, n-1]
"""Find an insertion point using binary search (with duplicates)"""
i, j = 0, len(nums) - 1 # Initialize the closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
m = (i + j) // 2 # Calculate the midpoint index m
if nums[m] < target:
i = m + 1 # target is in the interval [m+1, j]
i = m + 1 # Target must be in [m+1, j] if it exists
elif nums[m] > target:
j = m - 1 # target is in the interval [i, m-1]
j = m - 1 # Target must be in [i, m-1] if it exists
else:
j = m - 1 # The rightmost element less than target is in the interval [i, m-1]
j = m - 1 # Continue in [i, m-1] to find the rightmost element smaller than target
# Return insertion point i
return i
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Array without duplicate elements
# Array with no duplicate elements
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
print(f"\nArray nums = {nums}")
# Binary search for insertion point
# Find an insertion point using binary search
for target in [6, 9]:
index = binary_search_insertion_simple(nums, target)
print(f"Index of insertion point for element {target} is {index}")
print(f"Insertion index for element {target}: {index}")
# Array with duplicate elements
# Array containing duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\nArray nums = {nums}")
# Binary search for insertion point
# Find an insertion point using binary search
for target in [2, 6, 20]:
index = binary_search_insertion(nums, target)
print(f"Index of insertion point for element {target} is {index}")
print(f"Insertion index for element {target}: {index}")

View File

@@ -12,40 +12,40 @@ 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 this key does not exist in the hash table, return -1
"""Hash-based search (array)"""
# Hash table key: target element; value: index
# Return -1 if the hash table does not contain this key
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 this key does not exist in the hash table, return None
"""Hash-based search (linked list)"""
# Hash table key: target element; value: node object
# Return None if the hash table does not contain this key
return hmap.get(target, None)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
target = 3
# Hash search (array)
# Hash-based search (array)
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
# Initialize hash table
# Initialize the hash table
map0 = dict[int, int]()
for i in range(len(nums)):
map0[nums[i]] = i # key: element, value: index
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)
# Hash-based search (linked list)
head: ListNode = list_to_linked_list(nums)
# Initialize hash table
# Initialize the hash table
map1 = dict[int, ListNode]()
while head:
map1[head.val] = head # key: node value, value: node
map1[head.val] = head # key: node value; value: node
head = head.next
node: ListNode = hashing_search_linkedlist(map1, target)
print("The corresponding node object for target node value 3 is", node)
print("The node object corresponding to target value 3 is", node)

View File

@@ -15,31 +15,31 @@ def linear_search_array(nums: list[int], target: int) -> int:
"""Linear search (array)"""
# Traverse the array
for i in range(len(nums)):
if nums[i] == target: # Found the target element, return its index
if nums[i] == target: # The target element was found; return its index
return i
return -1 # Target element not found, return -1
return -1 # The target element was not found; return -1
def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None:
"""Linear search (linked list)"""
# Traverse the linked list
while head:
if head.val == target: # Found the target node, return it
if head.val == target: # The target node was found; return it
return head
head = head.next
return None # Target node not found, return None
return None # The target node was not found; return None
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
target = 3
# Perform linear search in array
# Perform linear search on the 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
# Perform linear search on the linked list
head: ListNode = list_to_linked_list(nums)
node: ListNode | None = linear_search_linkedlist(head, target)
print("The corresponding node object for target node value 3 is", node)
print("The node object corresponding to target value 3 is", node)

View File

@@ -6,8 +6,8 @@ Author: krahets (krahets@163.com)
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
"""Method 1: Brute force enumeration"""
# Two nested loops, time complexity is O(n^2)
"""Method 1: brute-force enumeration"""
# Two nested loops give a time complexity of O(n^2)
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
@@ -16,10 +16,10 @@ def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
"""Method 2: Auxiliary hash table"""
# Auxiliary hash table, space complexity is O(n)
"""Method 2: auxiliary hash table"""
# The auxiliary hash table uses O(n) space
dic = {}
# Single loop, time complexity is O(n)
# A single loop with average O(1) hash operations gives an average time complexity of O(n)
for i in range(len(nums)):
if target - nums[i] in dic:
return [dic[target - nums[i]], i]
@@ -27,16 +27,16 @@ def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
return []
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# ======= Test Case =======
# ======= Test case =======
nums = [2, 7, 11, 15]
target = 13
# ====== Driver Code ======
# ====== Driver code ======
# Method 1
res: list[int] = two_sum_brute_force(nums, target)
print("Method 1 res =", res)
print("Method 1: res =", res)
# Method 2
res: list[int] = two_sum_hash_table(nums, target)
print("Method 2 res =", res)
print("Method 2: res =", res)

View File

@@ -8,9 +8,9 @@ Author: timi (xisunyy@163.com)
def bubble_sort(nums: list[int]):
"""Bubble sort"""
n = len(nums)
# Outer loop: unsorted interval is [0, i]
# Outer loop: the unsorted range is [0, i]
for i in range(n - 1, 0, -1):
# Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval
# Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps
for j in range(i):
if nums[j] > nums[j + 1]:
# Swap nums[j] and nums[j + 1]
@@ -20,25 +20,25 @@ def bubble_sort(nums: list[int]):
def bubble_sort_with_flag(nums: list[int]):
"""Bubble sort (flag optimization)"""
n = len(nums)
# Outer loop: unsorted interval is [0, i]
# Outer loop: the 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 interval [0, i] to the rightmost end of the interval
flag = False # Initialize the flag
# Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps
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 element swap
flag = True # Record that a swap occurred
if not flag:
break # No elements were swapped in this round of "bubbling", exit directly
break # Exit immediately if this pass made no swaps
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
bubble_sort(nums)
print("After bubble sort, nums =", nums)
print("After bubble sort, array =", nums)
nums1 = [4, 1, 3, 1, 5, 2]
bubble_sort_with_flag(nums1)
print("After bubble sort, nums =", nums1)
print("After bubble sort, array =", nums1)

View File

@@ -7,20 +7,20 @@ 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
# Initialize k = n/2 buckets, expecting 2 elements per bucket
k = len(nums) // 2
buckets = [[] for _ in range(k)]
# 1. Distribute array elements into various buckets
# 1. Distribute the array elements among the buckets
for num in nums:
# Input data range is [0, 1), use num * k to map to index range [0, k-1]
# The input range is [0, 1); use num * k to map each value to an index in [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
# Use the built-in sorting function, or replace it with another sorting algorithm
bucket.sort()
# 3. Traverse buckets to merge results
# 3. Traverse the buckets to combine the results
i = 0
for bucket in buckets:
for num in bucket:
@@ -29,7 +29,7 @@ def bucket_sort(nums: list[float]):
if __name__ == "__main__":
# Assume input data is floating point, interval [0, 1)
# Assume the input consists of floating-point numbers in [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("After bucket sort, nums =", nums)

View File

@@ -7,17 +7,15 @@ 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 number
# counter[num] represents the occurrence of num
# Simple implementation; cannot be used to sort objects
# 1. Find the maximum array element m
m = max(nums)
# 2. Count the occurrences of each number
# counter[num] is the number of occurrences 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
# 3. Traverse counter and fill the original array nums with the elements
i = 0
for num in range(m + 1):
for _ in range(counter[num]):
@@ -27,37 +25,37 @@ def counting_sort_naive(nums: list[int]):
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
# Full implementation; stable and adaptable to sorting objects
# 1. Find the maximum array element m
m = max(nums)
# 2. Count the occurrence of each number
# counter[num] represents the occurrence of num
# 2. Count the occurrences of each number
# counter[num] is the number of occurrences 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
# 3. Compute prefix sums of counter to convert occurrence counts into end positions
# That is, counter[num]-1 is the index of the last occurrence of num 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
# 4. Traverse nums in reverse and place each element in the result array res
# Initialize array res to store the result
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
counter[num] -= 1 # Decrement the prefix sum by 1 to determine where to place the next occurrence of num
# Overwrite the original array nums with the result array res
for i in range(n):
nums[i] = res[i]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort_naive(nums)
print(f"After counting sort (unable to sort objects), nums = {nums}")
print(f"After counting sort (without support for objects), nums = {nums}")
nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort(nums1)

View File

@@ -6,9 +6,9 @@ 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"""
"""Sift down from node i in a heap of size n"""
while True:
# Determine the largest node among i, l, r, noted as ma
# Find the node with the largest value among i, l, and r, and store its index in ma
l = 2 * i + 1
r = 2 * i + 2
ma = i
@@ -16,29 +16,29 @@ def sift_down(nums: list[int], n: int, i: int):
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
# Stop if node i is largest or both child indices l and r are out of bounds
if ma == i:
break
# Swap two nodes
# Swap the two nodes
nums[i], nums[ma] = nums[ma], nums[i]
# Loop downwards heapification
# Continue sifting down
i = ma
def heap_sort(nums: list[int]):
"""Heap sort"""
# Build heap operation: heapify all nodes except leaves
# Build the heap by sifting down from every non-leaf node
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
# Extract the largest element from the heap, repeating 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)
# Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)
nums[0], nums[i] = nums[i], nums[0]
# Start heapifying the root node, from top to bottom
# Sift down starting at the root
sift_down(nums, i, 0)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)

View File

@@ -7,18 +7,18 @@ Author: timi (xisunyy@163.com)
def insertion_sort(nums: list[int]):
"""Insertion sort"""
# Outer loop: sorted interval is [0, i-1]
# Outer loop: the 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 interval [0, i-1]
# Inner loop: insert base at the correct position in 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
nums[j + 1] = nums[j] # Shift nums[j] one position to the right
j -= 1
nums[j + 1] = base # Assign base to the correct position
nums[j + 1] = base # Place base at the correct position
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)

View File

@@ -6,13 +6,13 @@ 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
"""Merge the left and right subarrays"""
# The left subarray spans [left, mid], and the right subarray spans [mid+1, right]
# Create a temporary array tmp to store the merged result
tmp = [0] * (right - left + 1)
# Initialize the start indices of the left and right subarrays
# Initialize the starting 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 both subarrays have elements remaining, compare them and copy the smaller element into the temporary array
while i <= mid and j <= right:
if nums[i] <= nums[j]:
tmp[k] = nums[i]
@@ -30,25 +30,25 @@ def merge(nums: list[int], left: int, mid: int, right: int):
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
# Copy the elements of temporary array tmp back into the corresponding range of the original array nums
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
# Base case
if left >= right:
return # Terminate recursion when subarray length is 1
# Divide and conquer 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
return # Stop recursing when the subarray has at most one element
# Divide phase
mid = (left + right) // 2 # Calculate the midpoint
merge_sort(nums, left, mid) # Recurse on the left subarray
merge_sort(nums, mid + 1, right) # Recurse on the right subarray
# Merge phase
merge(nums, left, mid, right)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [7, 3, 2, 6, 0, 1, 5, 4]
merge_sort(nums, 0, len(nums) - 1)

View File

@@ -9,28 +9,28 @@ class QuickSort:
"""Quick sort class"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Sentinel partition"""
"""Two-pointer partitioning"""
# 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
j -= 1 # Scan 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
i += 1 # Scan from left to right for the first element larger than the pivot
# Swap the elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
# Swap the pivot into the boundary position between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
return i # Return the pivot index
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort"""
# Terminate recursion when subarray length is 1
# Stop recursing when the subarray has at most one element
if left >= right:
return
# Sentinel partition
# Two-pointer partitioning
pivot = self.partition(nums, left, right)
# Recursively process the left subarray and right subarray
# Recurse on the left and right subarrays
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
@@ -42,38 +42,38 @@ class QuickSortMedian:
"""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
return mid # m lies between l and r
if (m <= l <= r) or (r <= l <= m):
return left # l is between m and r
return left # l lies between m and r
return right
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Sentinel partition (median of three)"""
# Use nums[left] as the pivot
"""Two-pointer partitioning (median of three)"""
# Select the median of the leftmost, middle, and rightmost elements as the pivot
med = self.median_three(nums, left, (left + right) // 2, right)
# Swap the median to the array's leftmost position
# Swap the median into the leftmost position of the current subarray
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
j -= 1 # Scan 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
i += 1 # Scan from left to right for the first element larger than the pivot
# Swap the elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
# Swap the pivot into the boundary position between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
return i # Return the pivot index
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort"""
# Terminate recursion when subarray length is 1
# Stop recursing when the subarray has at most one element
if left >= right:
return
# Sentinel partition
# Two-pointer partitioning
pivot = self.partition(nums, left, right)
# Recursively process the left subarray and right subarray
# Recurse on the left and right subarrays
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
@@ -82,36 +82,36 @@ class QuickSortTailCall:
"""Quick sort class (recursion depth optimization)"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""Sentinel partition"""
"""Two-pointer partitioning"""
# 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
j -= 1 # Scan 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
i += 1 # Scan from left to right for the first element larger than the pivot
# Swap the elements
nums[i], nums[j] = nums[j], nums[i]
# Swap the pivot to the boundary between the two subarrays
# Swap the pivot into the boundary position between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
return i # Return the index of the pivot
return i # Return the pivot index
def quick_sort(self, nums: list[int], left: int, right: int):
"""Quick sort (recursion depth optimization)"""
# Terminate when subarray length is 1
# Stop when the subarray has at most one element
while left < right:
# Sentinel partition operation
# Perform two-pointer partitioning
pivot = self.partition(nums, left, right)
# Perform quick sort on the shorter of the two subarrays
# Apply quick sort to 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]
left = pivot + 1 # The remaining unsorted range 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]
right = pivot - 1 # The remaining unsorted range is [left, pivot - 1]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Quick sort
nums = [2, 4, 1, 0, 3, 5]
@@ -121,9 +121,9 @@ if __name__ == "__main__":
# Quick sort (median pivot optimization)
nums1 = [2, 4, 1, 0, 3, 5]
QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1)
print("After quick sort (median pivot optimization), nums =", nums1)
print("After quick sort (median pivot optimization), nums1 =", nums1)
# Quick sort (recursion depth optimization)
nums2 = [2, 4, 1, 0, 3, 5]
QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1)
print("After quick sort (recursion depth optimization), nums =", nums2)
print("After quick sort (recursion depth optimization), nums2 =", nums2)

View File

@@ -6,51 +6,51 @@ 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
"""Get the k-th digit of num, where exp = 10^(k-1)"""
# Passing exp instead of k avoids repeatedly performing 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
"""Counting sort (sort nums by the k-th digit)"""
# Decimal digits range from 0 to 9, so the bucket array needs length 10
counter = [0] * 10
n = len(nums)
# Count the occurrence of digits 0~9
# Count the occurrences of each digit from 0 to 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"
d = digit(nums[i], exp) # Get the k-th digit of nums[i] and store it in d
counter[d] += 1 # Count the occurrences of digit d
# Compute prefix sums to convert occurrence counts into array positions
for i in range(1, 10):
counter[i] += counter[i - 1]
# Traverse in reverse, based on bucket statistics, place each element into res
# Traverse in reverse and place each element into res according to the bucket counts
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
j = counter[d] - 1 # Get the destination index j for digit 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
counter[d] -= 1 # Decrease the count for d by 1
# Overwrite the original array nums with the result
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
# Get the largest array element to determine the maximum number of digits
m = max(nums)
# Traverse from the lowest to the highest digit
# Process digits from least significant to most significant
exp = 1
while exp <= m:
# Perform counting sort on the k-th digit of array elements
# Apply counting sort to the k-th digit of the array elements
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# i.e., exp = 10^(k-1)
# That is, exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Radix sort
nums = [

View File

@@ -8,18 +8,18 @@ Author: krahets (krahets@163.com)
def selection_sort(nums: list[int]):
"""Selection sort"""
n = len(nums)
# Outer loop: unsorted interval is [i, n-1]
# Outer loop: the unsorted range is [i, n-1]
for i in range(n - 1):
# Inner loop: find the smallest element within the unsorted interval
# Inner loop: find the smallest element in 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 interval
# Swap the smallest element with the first element of the unsorted range
nums[i], nums[k] = nums[k], nums[i]
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
selection_sort(nums)

View File

@@ -6,7 +6,7 @@ Author: krahets (krahets@163.com)
class ArrayDeque:
"""Double-ended queue based on circular array implementation"""
"""Deque implemented with a circular array"""
def __init__(self, capacity: int):
"""Constructor"""
@@ -15,115 +15,115 @@ class ArrayDeque:
self._size: int = 0
def capacity(self) -> int:
"""Get the capacity of the double-ended queue"""
"""Get the deque capacity"""
return len(self._nums)
def size(self) -> int:
"""Get the length of the double-ended queue"""
"""Get the deque size"""
return self._size
def is_empty(self) -> bool:
"""Check if the double-ended queue is empty"""
"""Check whether the deque is empty"""
return self._size == 0
def index(self, i: int) -> int:
"""Calculate circular array index"""
# Use modulo operation to wrap the array head and tail together
# When i passes the tail of the array, return to the head
# When i passes the head of the array, return to the tail
"""Calculate an index in the circular array"""
# Use modulo to connect the end of the array to its beginning
# When i passes the end of the array, wrap around to the beginning
# When i passes the beginning of the array, wrap around to the end
return (i + self.capacity()) % self.capacity()
def push_first(self, num: int):
"""Front of the queue enqueue"""
"""Enqueue at the front"""
if self._size == self.capacity():
print("Double-ended queue is full")
print("Deque is full")
return
# Front pointer moves one position to the left
# Use modulo operation to wrap front around to the tail after passing the head of the array
# Move the front pointer one position to the left
# Use modulo to wrap front to the end when it passes the beginning of the array
self._front = self.index(self._front - 1)
# Add num to the front of the queue
# Add num to the front
self._nums[self._front] = num
self._size += 1
def push_last(self, num: int):
"""Rear of the queue enqueue"""
"""Enqueue at the rear"""
if self._size == self.capacity():
print("Double-ended queue is full")
print("Deque is full")
return
# Calculate rear pointer, points to rear index + 1
# Calculate the rear pointer, which points one position past the last element
rear = self.index(self._front + self._size)
# Add num to the rear of the queue
# Add num to the rear
self._nums[rear] = num
self._size += 1
def pop_first(self) -> int:
"""Front of the queue dequeue"""
"""Dequeue from the front"""
num = self.peek_first()
# Front pointer moves one position backward
# Advance the front pointer by one position
self._front = self.index(self._front + 1)
self._size -= 1
return num
def pop_last(self) -> int:
"""Rear of the queue dequeue"""
"""Dequeue from the rear"""
num = self.peek_last()
self._size -= 1
return num
def peek_first(self) -> int:
"""Access front of the queue element"""
"""Access the front element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
raise IndexError("Deque is empty")
return self._nums[self._front]
def peek_last(self) -> int:
"""Access rear of the queue element"""
"""Access the rear element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# Calculate tail element index
raise IndexError("Deque is empty")
# Calculate the index of the last element
last = self.index(self._front + self._size - 1)
return self._nums[last]
def to_array(self) -> list[int]:
"""Return array for printing"""
# Only convert list elements within the valid length range
"""Return an array for printing"""
# Convert only the list elements within the occupied range
res = []
for i in range(self._size):
res.append(self._nums[self.index(self._front + i)])
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize double-ended queue
# Initialize the deque
deque = ArrayDeque(10)
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("double-ended queue deque =", deque.to_array())
print("Deque deque =", deque.to_array())
# Access elements
# Access an element
peek_first: int = deque.peek_first()
print("Front of the queue element peek_first =", peek_first)
print("Front element peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("Rear of the queue element peek_last =", peek_last)
print("Rear element peek_last =", peek_last)
# Elements enqueue
# Enqueue elements
deque.push_last(4)
print("Element 4 rear enqueue after deque =", deque.to_array())
print("After enqueuing element 4 at the rear, deque =", deque.to_array())
deque.push_first(1)
print("Element 1 front enqueue after deque =", deque.to_array())
print("After enqueuing element 1 at the front, deque =", deque.to_array())
# Elements dequeue
# Dequeue an element
pop_last: int = deque.pop_last()
print("Rear dequeued element =", pop_last, ", rear dequeue after deque =", deque.to_array())
print("Element dequeued from the rear =", pop_last, ", after dequeuing from the rear, deque =", deque.to_array())
pop_first: int = deque.pop_first()
print("Front dequeued element =", pop_first, ", front dequeue after deque =", deque.to_array())
print("Element dequeued from the front =", pop_first, ", after dequeuing from the front, deque =", deque.to_array())
# Get the length of the double-ended queue
# Get the deque size
size: int = deque.size()
print("Length of the double-ended queue size =", size)
print("Deque size =", size)
# Check if the double-ended queue is empty
# Check whether the deque is empty
is_empty: bool = deque.is_empty()
print("Is the double-ended queue empty =", is_empty)
print("Is the deque empty =", is_empty)

View File

@@ -6,53 +6,53 @@ Author: Peng Chen (pengchzn@gmail.com)
class ArrayQueue:
"""Queue based on circular array implementation"""
"""Queue implemented with a 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, points to the front of the queue element
self._size: int = 0 # Queue length
self._nums: list[int] = [0] * size # Array that stores the queue elements
self._front: int = 0 # Front pointer, pointing to the first element
self._size: int = 0 # Queue size
def capacity(self) -> int:
"""Get the capacity of the queue"""
"""Get the queue capacity"""
return len(self._nums)
def size(self) -> int:
"""Get the length of the queue"""
"""Get the queue size"""
return self._size
def is_empty(self) -> bool:
"""Check if the queue is empty"""
"""Check whether 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, points to rear index + 1
# Use modulo operation to wrap rear around to the head after passing the tail of the array
# Calculate the rear pointer, which points one position past the last element
# Use modulo to wrap rear to the beginning when it passes the end of the array
rear: int = (self._front + self._size) % self.capacity()
# Add num to the rear of the queue
# Add num to the rear
self._nums[rear] = num
self._size += 1
def pop(self) -> int:
"""Dequeue"""
num: int = self.peek()
# Front pointer moves one position backward, if it passes the tail, return to the head of the array
# Advance the front pointer by one position, wrapping to the beginning if it passes the end
self._front = (self._front + 1) % self.capacity()
self._size -= 1
return num
def peek(self) -> int:
"""Access front of the queue element"""
"""Access the front element"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._nums[self._front]
def to_list(self) -> list[int]:
"""Return list for printing"""
"""Return a list for printing"""
res = [0] * self.size()
j: int = self._front
for i in range(self.size()):
@@ -61,38 +61,38 @@ class ArrayQueue:
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize queue
# Initialize the queue
queue = ArrayQueue(10)
# Elements enqueue
# Enqueue elements
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("queue =", queue.to_list())
print("Queue queue =", queue.to_list())
# Access front of the queue element
# Access the front element
peek: int = queue.peek()
print("Front of the queue element peek =", peek)
print("Front element peek =", peek)
# Element dequeue
# Dequeue an element
pop: int = queue.pop()
print("Dequeued element pop =", pop)
print("After dequeue queue =", queue.to_list())
print("After dequeuing, queue =", queue.to_list())
# Get the length of the queue
# Get the queue size
size: int = queue.size()
print("Length of the queue size =", size)
print("Queue size =", size)
# Check if the queue is empty
# Check whether the queue is empty
is_empty: bool = queue.is_empty()
print("Is the queue empty =", is_empty)
# Test circular array
# Test the circular array
for i in range(10):
queue.push(i)
queue.pop()
print("Round", i, "enqueue + dequeue after queue = ", queue.to_list())
print("Round", i, ": after enqueue + dequeue, queue = ", queue.to_list())

View File

@@ -6,18 +6,18 @@ Author: Peng Chen (pengchzn@gmail.com)
class ArrayStack:
"""Stack based on array implementation"""
"""Stack implemented with an array"""
def __init__(self):
"""Constructor"""
self._stack: list[int] = []
def size(self) -> int:
"""Get the length of the stack"""
"""Get the stack size"""
return len(self._stack)
def is_empty(self) -> bool:
"""Check if the stack is empty"""
"""Check whether the stack is empty"""
return self.size() == 0
def push(self, item: int):
@@ -31,42 +31,42 @@ class ArrayStack:
return self._stack.pop()
def peek(self) -> int:
"""Access top of the stack element"""
"""Access the top element"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._stack[-1]
def to_list(self) -> list[int]:
"""Return list for printing"""
"""Return a list for printing"""
return self._stack
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize stack
# Initialize the stack
stack = ArrayStack()
# Elements push onto stack
# Push elements onto the stack
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("stack =", stack.to_list())
print("Stack stack =", stack.to_list())
# Access top of the stack element
# Access the top element
peek: int = stack.peek()
print("Top of the stack element peek =", peek)
print("Top element peek =", peek)
# Element pop from stack
# Pop an element from the stack
pop: int = stack.pop()
print("Popped element pop =", pop)
print("After pop stack =", stack.to_list())
print("After popping, stack =", stack.to_list())
# Get the length of the stack
# Get the stack size
size: int = stack.size()
print("Length of the stack size =", size)
print("Stack size =", size)
# Check if it is empty
# Check whether it is empty
is_empty: bool = stack.is_empty()
print("Is the stack empty =", is_empty)

View File

@@ -6,37 +6,37 @@ Author: Peng Chen (pengchzn@gmail.com)
from collections import deque
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize double-ended queue
# Initialize the deque
deq: deque[int] = deque()
# Elements enqueue
deq.append(2) # Add to the rear of the queue
# Enqueue elements
deq.append(2) # Add to the rear
deq.append(5)
deq.append(4)
deq.appendleft(3) # Add to the front of the queue
deq.appendleft(3) # Add to the front
deq.appendleft(1)
print("double-ended queue deque =", deq)
print("Deque deq =", deq)
# Access elements
front: int = deq[0] # Front of the queue element
print("Front of the queue element front =", front)
rear: int = deq[-1] # Rear of the queue element
print("Rear of the queue element rear =", rear)
# Access an element
front: int = deq[0] # Front element
print("Front element front =", front)
rear: int = deq[-1] # Rear element
print("Rear element rear =", rear)
# Elements dequeue
pop_front: int = deq.popleft() # Front of the queue element dequeues
print("Front dequeued element pop_front =", pop_front)
print("After front dequeue deque =", deq)
pop_rear: int = deq.pop() # Rear of the queue element dequeues
print("Rear dequeued element pop_rear =", pop_rear)
print("After rear dequeue deque =", deq)
# Dequeue an element
pop_front: int = deq.popleft() # Dequeue the front element
print("Element dequeued from the front, pop_front =", pop_front)
print("After dequeuing from the front, deq =", deq)
pop_rear: int = deq.pop() # Dequeue the rear element
print("Element dequeued from the rear, pop_rear =", pop_rear)
print("After dequeuing from the rear, deq =", deq)
# Get the length of the double-ended queue
# Get the deque size
size: int = len(deq)
print("Length of the double-ended queue size =", size)
print("Deque size =", size)
# Check if the double-ended queue is empty
# Check whether the deque is empty
is_empty: bool = len(deq) == 0
print("Is the double-ended queue empty =", is_empty)
print("Is the deque empty =", is_empty)

View File

@@ -11,25 +11,25 @@ class ListNode:
def __init__(self, val: int):
"""Constructor"""
self.val: int = val
self.next: ListNode | None = None # Successor node reference
self.prev: ListNode | None = None # Predecessor node reference
self.next: ListNode | None = None # Reference to the next node
self.prev: ListNode | None = None # Reference to the previous node
class LinkedListDeque:
"""Double-ended queue based on doubly linked list implementation"""
"""Deque implemented with a doubly 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
self._size: int = 0 # Deque size
def size(self) -> int:
"""Get the length of the double-ended queue"""
"""Get the deque size"""
return self._size
def is_empty(self) -> bool:
"""Check if the double-ended queue is empty"""
"""Check whether the deque is empty"""
return self._size == 0
def push(self, num: int, is_front: bool):
@@ -38,78 +38,78 @@ class LinkedListDeque:
# If the linked list is empty, make both front and rear point to node
if self.is_empty():
self._front = self._rear = node
# Front of the queue enqueue operation
# Enqueue at the front
elif is_front:
# Add node to the head of the linked list
self._front.prev = node
node.next = self._front
self._front = node # Update head node
# Rear of the queue enqueue operation
self._front = node # Update the head node
# Enqueue at the rear
else:
# Add node to the tail of the linked list
self._rear.next = node
node.prev = self._rear
self._rear = node # Update tail node
self._size += 1 # Update queue length
self._rear = node # Update the tail node
self._size += 1 # Update the deque size
def push_first(self, num: int):
"""Front of the queue enqueue"""
"""Enqueue at the front"""
self.push(num, True)
def push_last(self, num: int):
"""Rear of the queue enqueue"""
"""Enqueue at the rear"""
self.push(num, False)
def pop(self, is_front: bool) -> int:
"""Dequeue operation"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# When the deque has one element, front and rear point to the same node
raise IndexError("Deque is empty")
# When the deque size is 1, the head and tail point to the same node
if self._size == 1:
val: int = self._front.val
self._front = None
self._rear = None
# Front of the queue dequeue operation
# Dequeue from the front
elif is_front:
val: int = self._front.val # Temporarily store head node value
# Delete head node
val: int = self._front.val # Save the head node value
# Remove the head node
fnext: ListNode | None = self._front.next
fnext.prev = None
self._front.next = None
self._front = fnext # Update head node
# Rear of the queue dequeue operation
self._front = fnext # Update the head node
# Dequeue from the rear
else:
val: int = self._rear.val # Temporarily store tail node value
# Delete tail node
val: int = self._rear.val # Save the tail node value
# Remove the tail node
rprev: ListNode | None = self._rear.prev
rprev.next = None
self._rear.prev = None
self._rear = rprev # Update tail node
self._size -= 1 # Update queue length
self._rear = rprev # Update the tail node
self._size -= 1 # Update the deque size
return val
def pop_first(self) -> int:
"""Front of the queue dequeue"""
"""Dequeue from the front"""
return self.pop(True)
def pop_last(self) -> int:
"""Rear of the queue dequeue"""
"""Dequeue from the rear"""
return self.pop(False)
def peek_first(self) -> int:
"""Access front of the queue element"""
"""Access the front element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
raise IndexError("Deque is empty")
return self._front.val
def peek_last(self) -> int:
"""Access rear of the queue element"""
"""Access the rear element"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
raise IndexError("Deque is empty")
return self._rear.val
def to_array(self) -> list[int]:
"""Return array for printing"""
"""Return an array for printing"""
node = self._front
res = [0] * self.size()
for i in range(self.size()):
@@ -118,37 +118,37 @@ class LinkedListDeque:
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize double-ended queue
# Initialize the deque
deque = LinkedListDeque()
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("double-ended queue deque =", deque.to_array())
print("Deque deque =", deque.to_array())
# Access elements
# Access an element
peek_first: int = deque.peek_first()
print("Front of the queue element peek_first =", peek_first)
print("Front element peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("Rear of the queue element peek_last =", peek_last)
print("Rear element peek_last =", peek_last)
# Elements enqueue
# Enqueue elements
deque.push_last(4)
print("Element 4 rear enqueue after deque =", deque.to_array())
print("After enqueuing element 4 at the rear, deque =", deque.to_array())
deque.push_first(1)
print("Element 1 front enqueue after deque =", deque.to_array())
print("After enqueuing element 1 at the front, deque =", deque.to_array())
# Elements dequeue
# Dequeue an element
pop_last: int = deque.pop_last()
print("Rear dequeued element =", pop_last, ", rear dequeue after deque =", deque.to_array())
print("Element dequeued from the rear =", pop_last, ", after dequeuing from the rear, deque =", deque.to_array())
pop_first: int = deque.pop_first()
print("Front dequeued element =", pop_first, ", front dequeue after deque =", deque.to_array())
print("Element dequeued from the front =", pop_first, ", after dequeuing from the front, deque =", deque.to_array())
# Get the length of the double-ended queue
# Get the deque size
size: int = deque.size()
print("Length of the double-ended queue size =", size)
print("Deque size =", size)
# Check if the double-ended queue is empty
# Check whether the deque is empty
is_empty: bool = deque.is_empty()
print("Is the double-ended queue empty =", is_empty)
print("Is the deque empty =", is_empty)

View File

@@ -12,7 +12,7 @@ from modules import ListNode
class LinkedListQueue:
"""Queue based on linked list implementation"""
"""Queue implemented with a linked list"""
def __init__(self):
"""Constructor"""
@@ -21,22 +21,22 @@ class LinkedListQueue:
self._size: int = 0
def size(self) -> int:
"""Get the length of the queue"""
"""Get the queue size"""
return self._size
def is_empty(self) -> bool:
"""Check if the queue is empty"""
"""Check whether the queue is empty"""
return self._size == 0
def push(self, num: int):
"""Enqueue"""
# Add num after the tail node
node = ListNode(num)
# If the queue is empty, make both front and rear point to the node
# If the queue is empty, make both the head and tail point to this node
if self._front is None:
self._front = node
self._rear = node
# If the queue is not empty, add the node after the tail node
# If the queue is not empty, add this node after the tail node
else:
self._rear.next = node
self._rear = node
@@ -45,19 +45,19 @@ class LinkedListQueue:
def pop(self) -> int:
"""Dequeue"""
num = self.peek()
# Delete head node
# Remove the head node
self._front = self._front.next
self._size -= 1
return num
def peek(self) -> int:
"""Access front of the queue element"""
"""Access the front element"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._front.val
def to_list(self) -> list[int]:
"""Convert to list for printing"""
"""Convert to a list for printing"""
queue = []
temp = self._front
while temp:
@@ -66,32 +66,32 @@ class LinkedListQueue:
return queue
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize queue
# Initialize the queue
queue = LinkedListQueue()
# Elements enqueue
# Enqueue elements
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("queue =", queue.to_list())
print("Queue queue =", queue.to_list())
# Access front of the queue element
# Access the front element
peek: int = queue.peek()
print("Front of the queue element front =", peek)
print("Front element peek =", peek)
# Element dequeue
# Dequeue an element
pop_front: int = queue.pop()
print("Dequeued element pop =", pop_front)
print("After dequeue queue =", queue.to_list())
print("Dequeued element pop_front =", pop_front)
print("After dequeuing, queue =", queue.to_list())
# Get the length of the queue
# Get the queue size
size: int = queue.size()
print("Length of the queue size =", size)
print("Queue size =", size)
# Check if the queue is empty
# Check whether the queue is empty
is_empty: bool = queue.is_empty()
print("Is the queue empty =", is_empty)

View File

@@ -12,7 +12,7 @@ from modules import ListNode
class LinkedListStack:
"""Stack based on linked list implementation"""
"""Stack implemented with a linked list"""
def __init__(self):
"""Constructor"""
@@ -20,11 +20,11 @@ class LinkedListStack:
self._size: int = 0
def size(self) -> int:
"""Get the length of the stack"""
"""Get the stack size"""
return self._size
def is_empty(self) -> bool:
"""Check if the stack is empty"""
"""Check whether the stack is empty"""
return self._size == 0
def push(self, val: int):
@@ -42,13 +42,13 @@ class LinkedListStack:
return num
def peek(self) -> int:
"""Access top of the stack element"""
"""Access the top element"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._peek.val
def to_list(self) -> list[int]:
"""Convert to list for printing"""
"""Convert to a list for printing"""
arr = []
node = self._peek
while node:
@@ -58,32 +58,32 @@ class LinkedListStack:
return arr
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize stack
# Initialize the stack
stack = LinkedListStack()
# Elements push onto stack
# Push elements onto the stack
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("stack =", stack.to_list())
print("Stack stack =", stack.to_list())
# Access top of the stack element
# Access the top element
peek: int = stack.peek()
print("Top of the stack element peek =", peek)
print("Top element peek =", peek)
# Element pop from stack
# Pop an element from the stack
pop: int = stack.pop()
print("Popped element pop =", pop)
print("After pop stack =", stack.to_list())
print("After popping, stack =", stack.to_list())
# Get the length of the stack
# Get the stack size
size: int = stack.size()
print("Length of the stack size =", size)
print("Stack size =", size)
# Check if it is empty
# Check whether it is empty
is_empty: bool = stack.is_empty()
print("Is the stack empty =", is_empty)

View File

@@ -6,34 +6,34 @@ Author: Peng Chen (pengchzn@gmail.com)
from collections import deque
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize queue
# In Python, we generally use the double-ended queue class deque as a queue
# Although queue.Queue() is a proper queue class, it is not very convenient to use
# Initialize the queue
# In Python, we generally use the deque class as a queue
# Although queue.Queue() is a dedicated queue class, deque is more convenient for this example
que: deque[int] = deque()
# Elements enqueue
# Enqueue elements
que.append(1)
que.append(3)
que.append(2)
que.append(5)
que.append(4)
print("queue que =", que)
print("Queue que =", que)
# Access front of the queue element
# Access the front element
front: int = que[0]
print("Front of the queue element front =", front)
print("Front element front =", front)
# Element dequeue
# Dequeue an element
pop: int = que.popleft()
print("Dequeued element pop =", pop)
print("After dequeue que =", que)
print("After dequeuing, que =", que)
# Get the length of the queue
# Get the queue size
size: int = len(que)
print("Length of the queue size =", size)
print("Queue size =", size)
# Check if the queue is empty
# Check whether the queue is empty
is_empty: bool = len(que) == 0
print("Is the queue empty =", is_empty)

View File

@@ -4,33 +4,33 @@ Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize stack
# Python does not have a built-in stack class, we can use list as a stack
# Initialize the stack
# Python has no built-in stack class; a list can be used as a stack
stack: list[int] = []
# Elements push onto stack
# Push elements onto the stack
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
print("stack =", stack)
print("Stack stack =", stack)
# Access top of the stack element
# Access the top element
peek: int = stack[-1]
print("Top of the stack element peek =", peek)
print("Top element peek =", peek)
# Element pop from stack
# Pop an element from the stack
pop: int = stack.pop()
print("Popped element pop =", pop)
print("After pop stack =", stack)
print("After popping, stack =", stack)
# Get the length of the stack
# Get the stack size
size: int = len(stack)
print("Length of the stack size =", size)
print("Stack size =", size)
# Check if it is empty
# Check whether it is empty
is_empty: bool = len(stack) == 0
print("Is the stack empty =", is_empty)

View File

@@ -12,39 +12,39 @@ from modules import TreeNode, list_to_tree, print_tree
class ArrayBinaryTree:
"""Binary tree class represented by array"""
"""Binary tree class using an array representation"""
def __init__(self, arr: list[int | None]):
"""Constructor"""
self._tree = list(arr)
def size(self):
"""List capacity"""
"""Length of the backing list"""
return len(self._tree)
def val(self, i: int) -> int | None:
"""Get value of node at index i"""
# If index is out of bounds, return None, representing empty position
"""Get the value of the node at index i"""
# If the index is out of bounds, return None to represent an empty position
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
"""Get index of left child node of node at index i"""
"""Get the left child index of the node at index i"""
return 2 * i + 1
def right(self, i: int) -> int | None:
"""Get index of right child node of node at index i"""
"""Get the right child index of the node at index i"""
return 2 * i + 2
def parent(self, i: int) -> int | None:
"""Get index of parent node of node at index i"""
"""Get the parent index of the node at index i"""
return (i - 1) // 2
def level_order(self) -> list[int]:
"""Level-order traversal"""
self.res = []
# Traverse array directly
# Traverse the array directly
for i in range(self.size()):
if self.val(i) is not None:
self.res.append(self.val(i))
@@ -85,35 +85,35 @@ class ArrayBinaryTree:
return self.res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
# Initialize the binary tree
# Use a helper function that builds a binary tree directly from an array
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 binary tree:")
print("\nInitialize the binary tree\n")
print("Array representation of the binary tree:")
print(arr)
print("Linked list representation of binary tree:")
print("Linked representation of the binary tree:")
print_tree(root)
# Binary tree class represented by array
# Binary tree class using an array representation
abt = ArrayBinaryTree(arr)
# Access nodes
# Access a node
i = 1
l, r, p = abt.left(i), abt.right(i), abt.parent(i)
print(f"\nCurrent node's index is {i}, value is {abt.val(i)}")
print(f"Its left child node's index is {l}, value is {abt.val(l)}")
print(f"Its right child node's index is {r}, value is {abt.val(r)}")
print(f"Its parent node's index is {p}, value is {abt.val(p)}")
print(f"\nCurrent node index: {i}, value: {abt.val(i)}")
print(f"Left child index: {l}, value: {abt.val(l)}")
print(f"Right child index: {r}, value: {abt.val(r)}")
print(f"Parent index: {p}, value: {abt.val(p)}")
# Traverse tree
# Traverse the tree
res = abt.level_order()
print("\nLevel-order traversal is:", res)
print("\nLevel-order traversal:", res)
res = abt.pre_order()
print("Preorder traversal is:", res)
print("Preorder traversal:", res)
res = abt.in_order()
print("Inorder traversal is:", res)
print("Inorder traversal:", res)
res = abt.post_order()
print("Postorder traversal is:", res)
print("Postorder traversal:", res)

View File

@@ -19,110 +19,110 @@ class AVLTree:
self._root = None
def get_root(self) -> TreeNode | None:
"""Get binary tree root node"""
"""Get the root of the binary tree"""
return self._root
def height(self, node: TreeNode | None) -> int:
"""Get node height"""
# Empty node height is -1, leaf node height is 0
"""Get the node height"""
# An empty subtree has height -1; a leaf has height 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
"""Update the node height"""
# The node height equals the height of its taller 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
"""Get the balance factor"""
# An empty subtree has balance factor 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"""
"""Right rotation"""
child = node.left
grand_child = child.right
# Using child as pivot, rotate node to the right
# Rotate node to the right around child
child.right = node
node.left = grand_child
# Update node height
# Update the node height
self.update_height(node)
self.update_height(child)
# Return root node of subtree after rotation
# Return the root of the rotated subtree
return child
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""Left rotation operation"""
"""Left rotation"""
child = node.right
grand_child = child.left
# Using child as pivot, rotate node to the left
# Rotate node to the left around child
child.left = node
node.right = grand_child
# Update node height
# Update the node height
self.update_height(node)
self.update_height(child)
# Return root node of subtree after rotation
# Return the root of the rotated subtree
return child
def rotate(self, node: TreeNode | None) -> TreeNode | None:
"""Perform rotation operation to restore balance to this subtree"""
# Get balance factor of node
"""Perform rotations to restore balance in this subtree"""
# Get the balance factor of node
balance_factor = self.balance_factor(node)
# Left-leaning tree
# Left-heavy tree
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
# Right rotation
# Rotate right
return self.right_rotate(node)
else:
# First left rotation then right rotation
# Rotate left, then right
node.left = self.left_rotate(node.left)
return self.right_rotate(node)
# Right-leaning tree
# Right-heavy tree
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
# Left rotation
# Rotate left
return self.left_rotate(node)
else:
# First right rotation then left rotation
# Rotate right, then left
node.right = self.right_rotate(node.right)
return self.left_rotate(node)
# Balanced tree, no rotation needed, return directly
# The tree is balanced; return without rotating
return node
def insert(self, val):
"""Insert node"""
"""Insert a node"""
self._root = self.insert_helper(self._root, val)
def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:
"""Recursively insert node (helper method)"""
"""Insert a node recursively (helper method)"""
if node is None:
return TreeNode(val)
# 1. Find insertion position and insert node
# 1. Find the insertion position and insert the 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:
# Duplicate node not inserted, return directly
# Return without inserting a duplicate node
return node
# Update node height
# Update the node height
self.update_height(node)
# 2. Perform rotation operation to restore balance to this subtree
# 2. Perform rotations to restore balance in this subtree
return self.rotate(node)
def remove(self, val: int):
"""Delete node"""
"""Remove a node"""
self._root = self.remove_helper(self._root, val)
def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:
"""Recursively delete node (helper method)"""
"""Remove a node recursively (helper method)"""
if node is None:
return None
# 1. Find node and delete
# 1. Find and remove the node
if val < node.val:
node.left = self.remove_helper(node.left, val)
elif val > node.val:
@@ -130,71 +130,71 @@ class AVLTree:
else:
if node.left is None or node.right is None:
child = node.left or node.right
# Number of child nodes = 0, delete node directly and return
# Number of children = 0: remove node and return
if child is None:
return None
# Number of child nodes = 1, delete node directly
# Number of children = 1: remove node directly
else:
node = child
else:
# Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it
# Number of children = 2: remove the inorder successor and use its value to replace the current node value
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
# Update the node height
self.update_height(node)
# 2. Perform rotation operation to restore balance to this subtree
# 2. Perform rotations to restore balance in this subtree
return self.rotate(node)
def search(self, val: int) -> TreeNode | None:
"""Search node"""
"""Find a node"""
cur = self._root
# Loop search, exit after passing leaf node
# Search in a loop, stopping if cur becomes None
while cur is not None:
# Target node is in cur's right subtree
# If present, the target node lies in the right subtree of cur
if cur.val < val:
cur = cur.right
# Target node is in cur's left subtree
# If present, the target node lies in the left subtree of cur
elif cur.val > val:
cur = cur.left
# Found target node, exit loop
# The target node was found; exit the loop
else:
break
# Return target node
# Return the target node, or None if it was not found
return cur
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
def test_insert(tree: AVLTree, val: int):
tree.insert(val)
print("\nAfter inserting node {}, AVL tree is".format(val))
print("\nAVL tree after inserting node {}:".format(val))
print_tree(tree.get_root())
def test_remove(tree: AVLTree, val: int):
tree.remove(val)
print("\nAfter deleting node {}, AVL tree is".format(val))
print("\nAVL tree after removing node {}:".format(val))
print_tree(tree.get_root())
# Initialize empty AVL tree
# Initialize an empty AVL tree
avl_tree = AVLTree()
# Insert nodes
# Please pay attention to how the AVL tree maintains balance after inserting nodes
# Insert a node
# Observe how the AVL tree maintains balance after a node is inserted
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]:
test_insert(avl_tree, val)
# Insert duplicate node
# Insert a duplicate node
test_insert(avl_tree, 7)
# Delete nodes
# Please pay attention to how the AVL tree maintains balance after deleting nodes
test_remove(avl_tree, 8) # Delete node with degree 0
test_remove(avl_tree, 5) # Delete node with degree 1
test_remove(avl_tree, 4) # Delete node with degree 2
# Remove a node
# Observe how the AVL tree maintains balance after a node is removed
test_remove(avl_tree, 8) # Remove a node with 0 children
test_remove(avl_tree, 5) # Remove a node with 1 child
test_remove(avl_tree, 4) # Remove a node with 2 children
result_node = avl_tree.search(7)
print("\nFound node object is {}, node value = {}".format(result_node, result_node.val))
print("\nFound node object: {}, node value = {}".format(result_node, result_node.val))

View File

@@ -16,49 +16,49 @@ class BinarySearchTree:
def __init__(self):
"""Constructor"""
# Initialize empty tree
# Initialize an empty tree
self._root = None
def get_root(self) -> TreeNode | None:
"""Get binary tree root node"""
"""Get the root of the binary tree"""
return self._root
def search(self, num: int) -> TreeNode | None:
"""Search node"""
"""Find a node"""
cur = self._root
# Loop search, exit after passing leaf node
# Search in a loop, stopping if cur becomes None
while cur is not None:
# Target node is in cur's right subtree
# If present, the target node is in the right subtree of cur
if cur.val < num:
cur = cur.right
# Target node is in cur's left subtree
# If present, the target node is in the left subtree of cur
elif cur.val > num:
cur = cur.left
# Found target node, exit loop
# The target node was found; exit the loop
else:
break
return cur
def insert(self, num: int):
"""Insert node"""
# If tree is empty, initialize root node
"""Insert a node"""
# If the tree is empty, initialize the root node
if self._root is None:
self._root = TreeNode(num)
return
# Loop search, exit after passing leaf node
# Search in a loop, stopping if cur becomes None
cur, pre = self._root, None
while cur is not None:
# Found duplicate node, return directly
# Return immediately if a duplicate node is found
if cur.val == num:
return
pre = cur
# Insertion position is in cur's right subtree
# The insertion position lies in the right subtree of cur
if cur.val < num:
cur = cur.right
# Insertion position is in cur's left subtree
# The insertion position lies in the left subtree of cur
else:
cur = cur.left
# Insert node
# Insert a node
node = TreeNode(num)
if pre.val < num:
pre.right = node
@@ -66,81 +66,81 @@ class BinarySearchTree:
pre.left = node
def remove(self, num: int):
"""Delete node"""
# If tree is empty, return directly
"""Remove a node"""
# Return immediately if the tree is empty
if self._root is None:
return
# Loop search, exit after passing leaf node
# Search in a loop, stopping if cur becomes None
cur, pre = self._root, None
while cur is not None:
# Found node to delete, exit loop
# The node to remove was found; exit the loop
if cur.val == num:
break
pre = cur
# Node to delete is in cur's right subtree
# If present, the node to remove is in the right subtree of cur
if cur.val < num:
cur = cur.right
# Node to delete is in cur's left subtree
# If present, the node to remove is in the left subtree of cur
else:
cur = cur.left
# If no node to delete, return directly
# Return immediately if the node to remove does not exist
if cur is None:
return
# Number of child nodes = 0 or 1
# Number of children = 0 or 1
if cur.left is None or cur.right is None:
# When number of child nodes = 0 / 1, child = null / that child node
# With 0 or 1 child, child is None or that child, respectively
child = cur.left or cur.right
# Delete node cur
# Remove node cur
if cur != self._root:
if pre.left == cur:
pre.left = child
else:
pre.right = child
else:
# If deleted node is root node, reassign root node
# If the removed node is the root, assign the new root
self._root = child
# Number of child nodes = 2
# Number of children = 2
else:
# Get next node of cur in inorder traversal
# Get the inorder successor of cur
tmp: TreeNode = cur.right
while tmp.left is not None:
tmp = tmp.left
# Recursively delete node tmp
# Recursively remove node tmp
self.remove(tmp.val)
# Replace cur with tmp
# Replace the value of cur with the value of tmp
cur.val = tmp.val
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize binary search tree
# Initialize the binary search tree
bst = BinarySearchTree()
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree
# Different insertion orders produce different binary trees; this sequence produces a perfect binary tree
for num in nums:
bst.insert(num)
print("\nInitialized binary tree is\n")
print("\nThe initialized binary tree is:\n")
print_tree(bst.get_root())
# Search node
# Find a node
node = bst.search(7)
print("\nFound node object is: {}, node value = {}".format(node, node.val))
print("\nFound node object: {}, node value = {}".format(node, node.val))
# Insert node
# Insert a node
bst.insert(16)
print("\nAfter inserting node 16, binary tree is\n")
print("\nBinary tree after inserting node 16:\n")
print_tree(bst.get_root())
# Delete node
# Remove a node
bst.remove(1)
print("\nAfter deleting node 1, binary tree is\n")
print("\nBinary tree after removing node 1:\n")
print_tree(bst.get_root())
bst.remove(2)
print("\nAfter deleting node 2, binary tree is\n")
print("\nBinary tree after removing node 2:\n")
print_tree(bst.get_root())
bst.remove(4)
print("\nAfter deleting node 4, binary tree is\n")
print("\nBinary tree after removing node 4:\n")
print_tree(bst.get_root())

View File

@@ -11,31 +11,31 @@ sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize binary tree
# Initialize nodes
# Initialize the binary tree
# Initialize the nodes
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# Build references (pointers) between nodes
# Set up references (pointers) between the nodes
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
print("\nInitialize binary tree\n")
print("\nInitialize the binary tree\n")
print_tree(n1)
# Insert and delete nodes
# Insert and remove nodes
P = TreeNode(0)
# Insert node P between n1 -> n2
# Insert node P between n1 and n2 in n1 -> n2
n1.left = P
P.left = n2
print("\nAfter inserting node P\n")
print("\nAfter inserting node P:\n")
print_tree(n1)
# Delete node
# Remove a node
n1.left = n2
print("\nAfter deleting node P\n")
print("\nAfter removing node P:\n")
print_tree(n1)

View File

@@ -14,29 +14,29 @@ from collections import deque
def level_order(root: TreeNode | None) -> list[int]:
"""Level-order traversal"""
# Initialize queue, add root node
# Initialize a queue and enqueue the root
queue: deque[TreeNode] = deque()
queue.append(root)
# Initialize a list to save the traversal sequence
# Initialize a list to store the traversal sequence
res = []
while queue:
node: TreeNode = queue.popleft() # Dequeue
res.append(node.val) # Save node value
node: TreeNode = queue.popleft() # Dequeue from the queue
res.append(node.val) # Store the node value
if node.left is not None:
queue.append(node.left) # Left child node enqueue
queue.append(node.left) # Enqueue the left child
if node.right is not None:
queue.append(node.right) # Right child node enqueue
queue.append(node.right) # Enqueue the right child
return res
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
# Initialize the binary tree
# Use a helper function that builds a binary tree directly from an array
root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\nInitialize binary tree\n")
print("\nInitialize the binary tree\n")
print_tree(root)
# Level-order traversal
res: list[int] = level_order(root)
print("\nLevel-order traversal node print sequence = ", res)
print("\nNode sequence from level-order traversal = ", res)

View File

@@ -15,7 +15,7 @@ def pre_order(root: TreeNode | None):
"""Preorder traversal"""
if root is None:
return
# Visit priority: root node -> left subtree -> right subtree
# Visit order: root -> left subtree -> right subtree
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
@@ -25,7 +25,7 @@ def in_order(root: TreeNode | None):
"""Inorder traversal"""
if root is None:
return
# Visit priority: left subtree -> root node -> right subtree
# Visit order: left subtree -> root -> right subtree
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
@@ -35,31 +35,31 @@ def post_order(root: TreeNode | None):
"""Postorder traversal"""
if root is None:
return
# Visit priority: left subtree -> right subtree -> root node
# Visit order: left subtree -> right subtree -> root
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
"""Driver Code"""
"""Driver code"""
if __name__ == "__main__":
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
# Initialize the binary tree
# Use a helper function that builds a binary tree directly from an array
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\nInitialize binary tree\n")
print("\nInitialize the binary tree\n")
print_tree(root)
# Preorder traversal
res = []
pre_order(root)
print("\nPreorder traversal node print sequence = ", res)
print("\nNode sequence from preorder traversal = ", res)
# Inorder traversal
res.clear()
in_order(root)
print("\nInorder traversal node print sequence = ", res)
print("\nNode sequence from inorder traversal = ", res)
# Postorder traversal
res.clear()
post_order(root)
print("\nPostorder traversal node print sequence = ", res)
print("\nNode sequence from postorder traversal = ", res)

View File

@@ -1,8 +1,8 @@
# Follow the PEP 585 - Type Hinting Generics In Standard Collections
# Follow 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 *`
# Import common utilities here to simplify code with `from modules import *`
from .list_node import (
ListNode,
list_to_linked_list,

View File

@@ -10,7 +10,7 @@ class ListNode:
def __init__(self, val: int):
self.val: int = val # Node value
self.next: ListNode | None = None # Reference to next node
self.next: ListNode | None = None # Reference to the next node
def list_to_linked_list(arr: list[int]) -> ListNode | None:

View File

@@ -9,7 +9,7 @@ from .list_node import ListNode, linked_list_to_list
def print_matrix(mat: list[list[int]]):
"""Print matrix"""
"""Print a matrix"""
s = []
for arr in mat:
s.append(" " + str(arr))
@@ -17,7 +17,7 @@ def print_matrix(mat: list[list[int]]):
def print_linked_list(head: ListNode | None):
"""Print linked list"""
"""Print a linked list"""
arr: list[int] = linked_list_to_list(head)
print(" -> ".join([str(a) for a in arr]))
@@ -39,7 +39,7 @@ def print_tree(
root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False
):
"""
Print binary tree
Print a binary tree
This tree printer is borrowed from TECHIE DELIGHT
https://www.techiedelight.com/c-program-print-binary-tree/
"""
@@ -68,13 +68,13 @@ def print_tree(
def print_dict(hmap: dict):
"""Print dictionary"""
"""Print a dictionary"""
for key, value in hmap.items():
print(key, "->", value)
def print_heap(heap: list[int]):
"""Print heap"""
"""Print a heap"""
print("Array representation of the heap:", heap)
print("Tree representation of the heap:")
root: TreeNode | None = list_to_tree(heap)

View File

@@ -13,14 +13,14 @@ class TreeNode:
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 left child node
self.right: TreeNode | None = None # Reference to right child node
self.left: TreeNode | None = None # Reference to the left child
self.right: TreeNode | None = None # Reference to the right child
# For the serialization encoding rules, please refer to:
# https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
# Array representation of binary tree:
# For the serialization rules, see:
# https://www.hello-algo.com/en/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 binary tree:
# Linked representation of the binary tree:
# /——— 15
# /——— 7
# /——— 3
@@ -35,10 +35,10 @@ class TreeNode:
def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None:
"""Deserialize a list into a binary tree: recursion"""
# If the index exceeds the array length, or the corresponding element is None, return None
# Return None if the index is out of bounds or the corresponding element is None
if i < 0 or i >= len(arr) or arr[i] is None:
return None
# Build the current node
# Create the current node
root = TreeNode(arr[i])
# Recursively build the left and right subtrees
root.left = list_to_tree_dfs(arr, 2 * i + 1)

View File

@@ -11,10 +11,10 @@ class Vertex:
def vals_to_vets(vals: list[int]) -> list["Vertex"]:
"""Input value list vals, return vertex list vets"""
"""Given the list of values vals, return the list of vertices vets"""
return [Vertex(val) for val in vals]
def vets_to_vals(vets: list["Vertex"]) -> list[int]:
"""Input vertex list vets, return value list vals"""
"""Given the list of vertices vets, return the list of values vals"""
return [vet.val for vet in vets]

View File

@@ -6,11 +6,11 @@ env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
if __name__ == "__main__":
# find source code files
# Find source code files
src_paths = sorted(glob.glob("chapter_*/*.py"))
errors = []
# run python code
# Run Python code
for src_path in src_paths:
process = subprocess.Popen(
["python", src_path],
@@ -20,7 +20,7 @@ if __name__ == "__main__":
env=env,
encoding='utf-8'
)
# Wait for the process to complete, and get the output and error messages
# Wait for the process to finish and collect its output and error messages
stdout, stderr = process.communicate()
# Check the exit status
exit_status = process.returncode
@@ -28,6 +28,6 @@ if __name__ == "__main__":
errors.append(stderr)
print(f"Tested {len(src_paths)} files")
print(f"Found exception in {len(errors)} files")
print(f"Found exceptions in {len(errors)} files")
if len(errors) > 0:
raise RuntimeError("\n\n".join(errors))