mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 12:43:12 +08:00
misc: restructure contents
This commit is contained in:
68
experimental/utilities/python/binary_search.py
Normal file
68
experimental/utilities/python/binary_search.py
Normal file
@ -0,0 +1,68 @@
|
||||
def binary_search(arr, target):
|
||||
left = 0;
|
||||
right = len(arr) - 1
|
||||
while left <= right:
|
||||
mid = left + (right - left) // 2;
|
||||
if arr[mid] == target:
|
||||
return mid
|
||||
elif arr[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
return -1
|
||||
|
||||
def bisect_left(arr, target):
|
||||
"""Returns the leftmost position that `target` should
|
||||
go to such that the sequence remains sorted."""
|
||||
left = 0
|
||||
right = len(arr)
|
||||
while left < right:
|
||||
mid = (left + right) // 2
|
||||
if arr[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid
|
||||
return left
|
||||
|
||||
def bisect_right(arr, target):
|
||||
"""Returns the rightmost position that `target` should
|
||||
go to such that the sequence remains sorted."""
|
||||
left = 0
|
||||
right = len(arr)
|
||||
while left < right:
|
||||
mid = (left + right) // 2
|
||||
if arr[mid] > target:
|
||||
right = mid
|
||||
else:
|
||||
left = mid + 1
|
||||
return left
|
||||
|
||||
print(binary_search([1, 2, 3, 10], 1) == 0)
|
||||
print(binary_search([1, 2, 3, 10], 2) == 1)
|
||||
print(binary_search([1, 2, 3, 10], 3) == 2)
|
||||
print(binary_search([1, 2, 3, 10], 10) == 3)
|
||||
print(binary_search([1, 2, 3, 10], 9) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 4) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 0) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 11) == -1)
|
||||
print(binary_search([5, 7, 8, 10], 3) == -1)
|
||||
|
||||
print(bisect_left([1, 2, 3, 3, 10], 1) == 0)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 2) == 1)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 3) == 2) # First "3" is at index 2
|
||||
print(bisect_left([1, 2, 3, 3, 10], 10) == 4)
|
||||
|
||||
# These return a valid index despite target not being in array.
|
||||
print(bisect_left([1, 2, 3, 3, 10], 9) == 4)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
|
||||
print(bisect_left([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
|
||||
|
||||
print(bisect_right([1, 2, 3, 3, 10], 1) == 1)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 2) == 2)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 3) == 4) # Last "3" is at index 3, so insert new "3" at index 4
|
||||
print(bisect_right([1, 2, 3, 3, 10], 10) == 5)
|
||||
|
||||
# These return a valid index despite target not being in array.
|
||||
print(bisect_right([1, 2, 3, 3, 10], 9) == 4)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
|
||||
print(bisect_right([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
|
19
experimental/utilities/python/char_prime_map.py
Normal file
19
experimental/utilities/python/char_prime_map.py
Normal file
@ -0,0 +1,19 @@
|
||||
# For mapping a lowercase character to a prime number.
|
||||
# Useful for checking whether two strings are anagram or permutations of each other.
|
||||
primes = {
|
||||
'a': 2, 'b': 3, 'c': 5, 'd': 7, 'e': 11, 'f': 13,
|
||||
'g': 17, 'h': 19, 'i': 23, 'j': 29, 'k': 31, 'l': 37,
|
||||
'm': 41, 'n': 43, 'o': 47, 'p': 53, 'q': 59, 'r': 61,
|
||||
's': 67, 't': 71, 'u': 73, 'v': 79, 'w': 83, 'x': 89,
|
||||
'y': 97, 'z': 101, ' ': 103,
|
||||
}
|
||||
|
||||
import functools
|
||||
|
||||
def mul(seq):
|
||||
return functools.reduce(lambda a, b: a * b, seq, 1)
|
||||
|
||||
def prime_value_of_string(string):
|
||||
return mul([primes[c] for c in string])
|
||||
|
||||
print(prime_value_of_string('abcde'))
|
45
experimental/utilities/python/graph_dfs.py
Normal file
45
experimental/utilities/python/graph_dfs.py
Normal file
@ -0,0 +1,45 @@
|
||||
def graph_dfs(matrix):
|
||||
rows, cols = len(matrix), len(matrix[0])
|
||||
visited = set()
|
||||
directions = ((0, 1), (0, -1), (1, 0), (-1, 0))
|
||||
def dfs(i, j):
|
||||
if (i, j) in visited:
|
||||
return
|
||||
visited.add((i, j))
|
||||
# Traverse neighbors.
|
||||
for direction in directions:
|
||||
next_i, next_j = i + direction[0], j + direction[1]
|
||||
if 0 <= next_i < rows and 0 <= next_j < cols: # Check boundary.
|
||||
# Add any other checking here ^
|
||||
dfs(next_i, next_j)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
dfs(i, j)
|
||||
|
||||
# Follow up:
|
||||
# 1) Diagonal cells are considered neighbors
|
||||
# 2) View the matrix like Earth, right boundary is adjacent to the left boundary, top adjacent to left, etc.
|
||||
def graph_dfs_diagonals_and_boundary_wrap(matrix):
|
||||
rows, cols = len(matrix), len(matrix[0])
|
||||
visited = set()
|
||||
# Change 1: Add 4 more diagonal directions.
|
||||
directions = ((0, 1), (0, -1), (1, 0), (-1, 0), (-1, -1), (1, 1), (1, -1), (-1, 1))
|
||||
def dfs(i, j):
|
||||
if (i, j) in visited:
|
||||
return
|
||||
visited.add((i, j))
|
||||
for direction in directions:
|
||||
# Change 2: No more boundary, use modulo to allow traversal that exceed boundaries to wrap around.
|
||||
next_i, next_j = (i + direction[0] + rows) % rows, (j + direction[1] + cols) % cols
|
||||
dfs(next_i, next_j)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
dfs(i, j)
|
||||
|
||||
graph_dfs([
|
||||
[1, 2, 3, 4],
|
||||
[5, 6, 7, 8],
|
||||
[9, 10, 11, 12],
|
||||
])
|
21
experimental/utilities/python/graph_topo_sort.py
Normal file
21
experimental/utilities/python/graph_topo_sort.py
Normal file
@ -0,0 +1,21 @@
|
||||
def graph_topo_sort(num_nodes, edges):
|
||||
from collections import deque
|
||||
nodes, order, queue = {}, [], deque()
|
||||
for node_id in range(num_nodes):
|
||||
nodes[node_id] = { 'in': 0, 'out': set() }
|
||||
for node_id, pre_id in edges:
|
||||
nodes[node_id]['in'] += 1
|
||||
nodes[pre_id]['out'].add(node_id)
|
||||
for node_id in nodes.keys():
|
||||
if nodes[node_id]['in'] == 0:
|
||||
queue.append(node_id)
|
||||
while len(queue):
|
||||
node_id = queue.pop()
|
||||
for outgoing_id in nodes[node_id]['out']:
|
||||
nodes[outgoing_id]['in'] -= 1
|
||||
if nodes[outgoing_id]['in'] == 0:
|
||||
queue.append(outgoing_id)
|
||||
order.append(node_id)
|
||||
return order if len(order) == num_nodes else []
|
||||
|
||||
print(graph_topo_sort(3, [[0, 1], [0, 2]]))
|
84
experimental/utilities/python/heap.py
Normal file
84
experimental/utilities/python/heap.py
Normal file
@ -0,0 +1,84 @@
|
||||
# Implements a min-heap. For max-heap, simply reverse all comparison orders.
|
||||
#
|
||||
# Note on alternate subroutine namings (used in some textbooks):
|
||||
# - _bubble_up = siftdown
|
||||
# - _bubble_down = siftup
|
||||
|
||||
def _bubble_up(heap, i):
|
||||
while i > 0:
|
||||
parent_i = (i - 1) // 2
|
||||
if heap[i] < heap[parent_i]:
|
||||
heap[i], heap[parent_i] = heap[parent_i], heap[i]
|
||||
i = parent_i
|
||||
continue
|
||||
break
|
||||
|
||||
def _bubble_down(heap, i):
|
||||
startpos = i
|
||||
newitem = heap[i]
|
||||
left_i = 2 * i + 1
|
||||
while left_i < len(heap):
|
||||
# Pick the smaller of the L and R children
|
||||
right_i = left_i + 1
|
||||
if right_i < len(heap) and not heap[left_i] < heap[right_i]:
|
||||
child_i = right_i
|
||||
else:
|
||||
child_i = left_i
|
||||
|
||||
# Break if heap invariant satisfied
|
||||
if heap[i] < heap[child_i]:
|
||||
break
|
||||
|
||||
# Move the smaller child up.
|
||||
heap[i], heap[child_i] = heap[child_i], heap[i]
|
||||
i = child_i
|
||||
left_i = 2 * i + 1
|
||||
|
||||
def heapify(lst):
|
||||
for i in reversed(range(len(lst) // 2)):
|
||||
_bubble_down(lst, i)
|
||||
|
||||
def heappush(heap, item):
|
||||
heap.append(item)
|
||||
_bubble_up(heap, len(heap) - 1)
|
||||
|
||||
def heappop(heap):
|
||||
if len(heap) == 1:
|
||||
return heap.pop()
|
||||
min_value = heap[0]
|
||||
heap[0] = heap[-1]
|
||||
del heap[-1]
|
||||
_bubble_down(heap, 0)
|
||||
return min_value
|
||||
|
||||
|
||||
|
||||
# Example usage
|
||||
heap = [3, 2, 1, 0]
|
||||
heapify(heap)
|
||||
print('Heap(0, 1, 2, 3):', heap)
|
||||
heappush(heap, 4)
|
||||
heappush(heap, 7)
|
||||
heappush(heap, 6)
|
||||
heappush(heap, 5)
|
||||
print('Heap(0, 1, 2, 3, 4, 5, 6, 7):', heap)
|
||||
|
||||
sorted_list = [heappop(heap) for _ in range(8)]
|
||||
print('Heap-sorted list:', sorted_list)
|
||||
|
||||
# Large test case, for randomized tests
|
||||
import random
|
||||
|
||||
# Heapify 0 ~ 99
|
||||
heap = list(range(100))
|
||||
random.shuffle(heap)
|
||||
heapify(heap)
|
||||
|
||||
# Push 100 ~ 199 in random order
|
||||
new_elems = list(range(100, 200))
|
||||
random.shuffle(new_elems)
|
||||
for elem in new_elems:
|
||||
heappush(heap, elem)
|
||||
|
||||
sorted_list = [heappop(heap) for _ in range(200)]
|
||||
print(sorted_list == sorted(sorted_list))
|
13
experimental/utilities/python/is_subsequence.py
Normal file
13
experimental/utilities/python/is_subsequence.py
Normal file
@ -0,0 +1,13 @@
|
||||
def is_subsequence(s, t):
|
||||
"""
|
||||
:type s: str
|
||||
:type t: str
|
||||
:rtype: bool
|
||||
"""
|
||||
if len(s) > len(t):
|
||||
return False
|
||||
matched_s = 0
|
||||
for char in t:
|
||||
if matched_s < len(s) and s[matched_s] == char:
|
||||
matched_s += 1
|
||||
return matched_s == len(s)
|
109
experimental/utilities/python/linked_list.py
Normal file
109
experimental/utilities/python/linked_list.py
Normal file
@ -0,0 +1,109 @@
|
||||
# Singly-Linked List
|
||||
#
|
||||
# The linked list is passed around as a variable pointing to the
|
||||
# root node of the linked list, or None if the list is empty.
|
||||
|
||||
class LinkedListNode:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.next = None
|
||||
|
||||
def linked_list_append(linked_list, value):
|
||||
'''Appends a value to the end of the linked list'''
|
||||
node = linked_list
|
||||
insert_node = LinkedListNode(value)
|
||||
if not node:
|
||||
return insert_node
|
||||
while node.next:
|
||||
node = node.next
|
||||
node.next = insert_node
|
||||
return linked_list
|
||||
|
||||
def linked_list_insert_index(linked_list, value, index):
|
||||
'''Inserts a value at a particular index'''
|
||||
node = linked_list
|
||||
insert_node = LinkedListNode(value)
|
||||
|
||||
# Check if inserting at head
|
||||
if index == 0:
|
||||
insert_node.next = node
|
||||
return insert_node
|
||||
|
||||
# Skip ahead
|
||||
for _ in range(index - 1):
|
||||
node = node.next
|
||||
if not node:
|
||||
raise ValueError
|
||||
insert_node.next = node.next
|
||||
node.next = insert_node
|
||||
return linked_list
|
||||
|
||||
def linked_list_delete(linked_list, value):
|
||||
'''Deletes the first occurrence of a value in the linked list'''
|
||||
node = linked_list
|
||||
|
||||
# Check if deleting at head
|
||||
if node.value == value:
|
||||
return node.next
|
||||
|
||||
# Skip ahead
|
||||
while node.next:
|
||||
if node.next.value == value:
|
||||
node.next = node.next.next
|
||||
return linked_list
|
||||
node = node.next
|
||||
raise ValueError
|
||||
|
||||
def linked_list_delete_index(linked_list, index):
|
||||
'''Deletes the element at a particular index in the linked list'''
|
||||
node = linked_list
|
||||
|
||||
# Check if deleting at head
|
||||
if index == 0:
|
||||
return node.next
|
||||
|
||||
# Skip ahead
|
||||
for _ in range(index - 1):
|
||||
node = node.next
|
||||
if not node:
|
||||
raise ValueError
|
||||
if not node.next:
|
||||
raise ValueError
|
||||
node.next = node.next.next
|
||||
return linked_list
|
||||
|
||||
def linked_list_iter(linked_list):
|
||||
'''Lazy iterator over each node in the linked list'''
|
||||
node = linked_list
|
||||
while node is not None:
|
||||
yield node
|
||||
node = node.next
|
||||
|
||||
|
||||
# Append to back
|
||||
linked_list = None # Start with an empty linked list
|
||||
linked_list = linked_list_append(linked_list, 1)
|
||||
linked_list = linked_list_append(linked_list, 2)
|
||||
linked_list = linked_list_append(linked_list, 4)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Insert by index
|
||||
linked_list = linked_list_insert_index(linked_list, 0, 0) # Front
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
linked_list = linked_list_insert_index(linked_list, 3, 3) # Back
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete "3"
|
||||
linked_list = linked_list_delete(linked_list, 3)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete by index
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
linked_list = linked_list_delete_index(linked_list, 1)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete until empty
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
60
experimental/utilities/python/quick_select.py
Normal file
60
experimental/utilities/python/quick_select.py
Normal file
@ -0,0 +1,60 @@
|
||||
## QuickSelect -- Linear-time k-th order statistic
|
||||
## (i.e. select the k-th smallest element in an unsorted array)
|
||||
## https://en.wikipedia.org/wiki/Quickselect
|
||||
|
||||
def partition(array, start, end, pivot):
|
||||
"""Partitions by a pivot value, which might not necessarily be in the array.
|
||||
This variant is useful when you want to bound your recursion depth by the
|
||||
range of the input values, and not the length of the array."""
|
||||
pivot_index = start
|
||||
for i in range(start, end):
|
||||
if array[i] <= pivot:
|
||||
array[i], array[pivot_index] = array[pivot_index], array[i]
|
||||
pivot_index += 1
|
||||
return pivot_index
|
||||
|
||||
import random
|
||||
def partition_first(array, start, end):
|
||||
"""Selects the first element as pivot. Returns the index where the pivot went to.
|
||||
In this variant, we can guarantee that the pivot will be in its final sorted position.
|
||||
We need this guarantee for QuickSelect."""
|
||||
if start + 1 == end:
|
||||
return start
|
||||
pivot = array[start]
|
||||
pivot_index = start + 1
|
||||
for i in range(start + 1, end):
|
||||
if array[i] <= pivot:
|
||||
array[i], array[pivot_index] = array[pivot_index], array[i]
|
||||
pivot_index += 1
|
||||
# Move pivot to front
|
||||
array[start], array[pivot_index - 1] = array[pivot_index - 1], array[start]
|
||||
return pivot_index - 1
|
||||
|
||||
def quick_select(array, k):
|
||||
"""NOTE: k-th smallest element counts from 0!"""
|
||||
left = 0
|
||||
right = len(array)
|
||||
while True:
|
||||
random_index = random.sample(range(left, right), 1)[0]
|
||||
array[left], array[random_index] = array[random_index], array[left]
|
||||
pivot_index = partition_first(array, left, right)
|
||||
if k == pivot_index:
|
||||
return array[pivot_index]
|
||||
if k < pivot_index:
|
||||
right = pivot_index
|
||||
else:
|
||||
left = pivot_index + 1
|
||||
|
||||
|
||||
|
||||
print(quick_select([0], 0) == 0)
|
||||
print(quick_select([0, 1, 2, 3, 4], 2) == 2)
|
||||
print(quick_select([4, 3, 2, 1, 0], 2) == 2)
|
||||
print(quick_select([1, 3, 4, 2, 0], 2) == 2)
|
||||
|
||||
# Large test case, for randomized tests
|
||||
lst = list(range(1000))
|
||||
for _ in range(10):
|
||||
k = random.randint(0, 999)
|
||||
random.shuffle(lst)
|
||||
print(quick_select(lst, k) == k)
|
41
experimental/utilities/python/rabin_karp_hash.py
Normal file
41
experimental/utilities/python/rabin_karp_hash.py
Normal file
@ -0,0 +1,41 @@
|
||||
## Rabin-Karp Rolling Hash
|
||||
## Implementation of: https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm#Hash_function_used
|
||||
##
|
||||
## This rolling hash function is useful when you need to compute the hash of successive substrings
|
||||
## of text. E.g. note that going from 'abcd' to 'bcde', we drop the 'a' from the back and add an 'e'
|
||||
## on the right. The rolling hash function thus allows us to update the hash in-place O(1) instead of
|
||||
## recomputing the full hash of the substring O(m), where m is the length of the substring.
|
||||
##
|
||||
## NOTE: The implementation below takes in a tuple of integers, to be as general as possible. For use
|
||||
## with strings, simply take the ASCII value of each character before passing into the functions.
|
||||
|
||||
BASE = 101 # Arbitrary prime number
|
||||
|
||||
def rk_hash_init(tpl):
|
||||
'''Initializes the hash with a tuple of integers.'''
|
||||
return sum(n * BASE ** i for i, n in enumerate(reversed(tpl)))
|
||||
|
||||
def rk_hash_update(curr_hash, size, add_n, rem_n):
|
||||
'''Updates the hash by removing an integer from the left and appending
|
||||
an integer to the right.
|
||||
|
||||
curr_hash: The previous hash
|
||||
size: The size of the rolling window
|
||||
add_n: The integer appended to the right
|
||||
rem_n: The integer removed from the left'''
|
||||
return (curr_hash - (rem_n * BASE ** (size - 1))) * BASE + add_n
|
||||
|
||||
|
||||
|
||||
abc_hash = rk_hash_init(tuple(map(ord, 'abc'))) # Init the hash with 'abc'
|
||||
print('abc:', abc_hash)
|
||||
bcd_hash_1 = rk_hash_update(abc_hash, 3, ord('d'), ord('a')) # Add a 'd' to the right, remove an 'a' from the left
|
||||
print('bcd 1:', bcd_hash_1)
|
||||
|
||||
zbc_hash = rk_hash_init(tuple(map(ord, 'zbc'))) # Init the hash with 'zbc'
|
||||
print('zbc:', zbc_hash)
|
||||
bcd_hash_2 = rk_hash_update(zbc_hash, 3, ord('d'), ord('z')) # Add a 'd' to the right, remove a 'z' from the left
|
||||
print('bcd 2:', bcd_hash_2)
|
||||
|
||||
# Notice that both hash values are the same despite arriving via different paths
|
||||
print(bcd_hash_1 == bcd_hash_2)
|
8
experimental/utilities/python/tree_equal.py
Normal file
8
experimental/utilities/python/tree_equal.py
Normal file
@ -0,0 +1,8 @@
|
||||
def tree_equal(node1, node2):
|
||||
if not node1 and not node2:
|
||||
return True
|
||||
if not node1 or not node2:
|
||||
return False
|
||||
return node1.val == node2.val and \
|
||||
tree_equal(node1.left, node2.left) and \
|
||||
tree_equal(node1.right, node2.right)
|
6
experimental/utilities/python/tree_mirror.py
Normal file
6
experimental/utilities/python/tree_mirror.py
Normal file
@ -0,0 +1,6 @@
|
||||
def tree_mirror(node):
|
||||
if not node:
|
||||
return
|
||||
node.left, node.right = node.right, node.left
|
||||
tree_mirror(node.left)
|
||||
tree_mirror(node.right)
|
62
experimental/utilities/python/tree_traversal.py
Normal file
62
experimental/utilities/python/tree_traversal.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Various iterative ways of traversing a tree.
|
||||
def inorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
if curr_node.left:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.left)
|
||||
curr_node.left = None
|
||||
else:
|
||||
result.append(curr_node.val)
|
||||
if curr_node.right:
|
||||
stack.append(curr_node.right)
|
||||
return result
|
||||
|
||||
def preorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
result.append(curr_node.val)
|
||||
if curr_node.right:
|
||||
stack.append(curr_node.right)
|
||||
if curr_node.left:
|
||||
stack.append(curr_node.left)
|
||||
return result
|
||||
|
||||
def postorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
if curr_node.left:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.left)
|
||||
curr_node.left = None
|
||||
elif curr_node.right:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.right)
|
||||
curr_node.right = None
|
||||
else:
|
||||
result.append(curr_node.val)
|
||||
return result
|
80
experimental/utilities/python/trie.py
Normal file
80
experimental/utilities/python/trie.py
Normal file
@ -0,0 +1,80 @@
|
||||
class Trie(object):
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize your data structure here.
|
||||
"""
|
||||
self.d = {}
|
||||
|
||||
def insert(self, word):
|
||||
"""
|
||||
Inserts a word into the trie.
|
||||
:type word: str
|
||||
:rtype: void
|
||||
"""
|
||||
curr = self.d
|
||||
for char in word:
|
||||
if char not in curr:
|
||||
curr[char] = {}
|
||||
curr = curr[char]
|
||||
curr['#'] = {} # Using an empty dict rather than a boolean value makes recursive traversal easier.
|
||||
|
||||
def search(self, word):
|
||||
"""
|
||||
Returns if the word is in the trie.
|
||||
:type word: str
|
||||
:rtype: bool
|
||||
"""
|
||||
curr = self.d
|
||||
for char in word:
|
||||
if char in curr:
|
||||
curr = curr[char]
|
||||
else:
|
||||
return False
|
||||
return '#' in curr
|
||||
|
||||
def startsWith(self, prefix):
|
||||
"""
|
||||
Returns if there is any word in the trie that starts with the given prefix.
|
||||
:type prefix: str
|
||||
:rtype: bool
|
||||
"""
|
||||
curr = self.d
|
||||
for char in prefix:
|
||||
if char in curr:
|
||||
curr = curr[char]
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def searchRegex(self, word):
|
||||
"""
|
||||
Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter.
|
||||
:type word: str
|
||||
:rtype: bool
|
||||
"""
|
||||
def traverse(node, index):
|
||||
if len(word) == index:
|
||||
return '#' in node
|
||||
char = word[index]
|
||||
if char == '.':
|
||||
for key in node.keys():
|
||||
if traverse(node[key], index+1):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
if char not in node:
|
||||
return False
|
||||
return traverse(node[char], index + 1)
|
||||
return traverse(self.d, 0)
|
||||
|
||||
# Example
|
||||
trie = Trie()
|
||||
trie.insert('hello')
|
||||
print(trie.search('hello') == True)
|
||||
print(trie.startsWith('hello') == True)
|
||||
print(trie.startsWith('hel') == True)
|
||||
print(trie.search('world') == False)
|
||||
print(trie.startsWith('wor') == False)
|
||||
print(trie.searchRegex('..llo') == True)
|
||||
print(trie.searchRegex('..llx') == False)
|
||||
print(trie.searchRegex('..') == False)
|
50
experimental/utilities/python/union_find.py
Normal file
50
experimental/utilities/python/union_find.py
Normal file
@ -0,0 +1,50 @@
|
||||
## Union-Find data structure
|
||||
## https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
|
||||
parents = [0, 1, 2, 3, 4, 5, 6] # parent[i] is the parent of i
|
||||
weights = [1, 1, 1, 1, 1, 1, 1]
|
||||
|
||||
def find_root(parents, p):
|
||||
'''Average: O(log n)'''
|
||||
root = p
|
||||
while parents[root] != root:
|
||||
root = parents[root]
|
||||
# Flatten tree
|
||||
while parents[p] != p:
|
||||
parents[p], p = root, parents[p]
|
||||
return root
|
||||
|
||||
def union(parents, p, q):
|
||||
'''Average: O(log n)'''
|
||||
p = find_root(parents, p)
|
||||
q = find_root(parents, q)
|
||||
# Link the smaller node to the larger node
|
||||
if weights[p] > weights[q]:
|
||||
parents[q] = p
|
||||
weights[p] += weights[q]
|
||||
else:
|
||||
parents[p] = q
|
||||
weights[q] += weights[p]
|
||||
|
||||
|
||||
|
||||
# Start with all elements separate
|
||||
# -> [0], [1], [2], [3], [4], [5], [6]
|
||||
print(find_root(parents, 2) == 2)
|
||||
|
||||
# Merge 1, 2, 3 and 4, 5, 6
|
||||
# -> [0], [1, 2, 3], [4, 5, 6]
|
||||
union(parents, 1, 2)
|
||||
union(parents, 2, 3)
|
||||
union(parents, 4, 5)
|
||||
union(parents, 4, 6)
|
||||
|
||||
# Roots of 1, 2, 3 and 4, 5, 6 are the same
|
||||
print(find_root(parents, 0))
|
||||
print(list(find_root(parents, i) for i in (1, 2, 3)))
|
||||
print(list(find_root(parents, i) for i in (4, 5, 6)))
|
||||
|
||||
# Merge 2, 4
|
||||
# -> [0], [1, 2, 3, 4, 5, 6]
|
||||
union(parents, 2, 4)
|
||||
print(list(find_root(parents, i) for i in (1, 2, 3, 4, 5, 6)))
|
Reference in New Issue
Block a user