mirror of
https://github.com/krahets/hello-algo.git
synced 2026-11-10 16:00:36 +08:00
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:
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()}")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user