Skip to content

Instantly share code, notes, and snippets.

@iamaziz
Created September 21, 2019 01:57
Show Gist options
  • Select an option

  • Save iamaziz/b227ca8052ead38127c12a325c51b23f to your computer and use it in GitHub Desktop.

Select an option

Save iamaziz/b227ca8052ead38127c12a325c51b23f to your computer and use it in GitHub Desktop.
Code Book: read source code just like reading a book.
# --- Code Book: Python Algorithms
# --- Source : github.com/keon/algorithms.git
#========================================================================================================================
# :: tree/path_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree and a sum, determine if the tree has a root-to-leaf
| 3| path such that adding up all the values along the path equals the given sum.
| 4|
| 5| For example:
| 6| Given the below binary tree and sum = 22,
| 7| 5
| 8| / \
| 9| 4 8
| 10| / / \
| 11| 11 13 4
| 12| / \ \
| 13| 7 2 1
| 14| return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
| 15| """
| 16|
| 17|
| 18| def has_path_sum(root, sum):
| 19| """
| 20| :type root: TreeNode
| 21| :type sum: int
| 22| :rtype: bool
| 23| """
| 24| if root is None:
| 25| return False
| 26| if root.left is None and root.right is None and root.val == sum:
| 27| return True
| 28| sum -= root.val
| 29| return has_path_sum(root.left, sum) or has_path_sum(root.right, sum)
| 30|
| 31|
| 32| # DFS with stack
| 33| def has_path_sum2(root, sum):
| 34| if root is None:
| 35| return False
| 36| stack = [(root, root.val)]
| 37| while stack:
| 38| node, val = stack.pop()
| 39| if node.left is None and node.right is None:
| 40| if val == sum:
| 41| return True
| 42| if node.left is not None:
| 43| stack.append((node.left, val+node.left.val))
| 44| if node.right is not None:
| 45| stack.append((node.right, val+node.right.val))
| 46| return False
| 47|
| 48|
| 49| # BFS with queue
| 50| def has_path_sum3(root, sum):
| 51| if root is None:
| 52| return False
| 53| queue = [(root, sum-root.val)]
| 54| while queue:
| 55| node, val = queue.pop(0) # popleft
| 56| if node.left is None and node.right is None:
| 57| if val == 0:
| 58| return True
| 59| if node.left is not None:
| 60| queue.append((node.left, val-node.left.val))
| 61| if node.right is not None:
| 62| queue.append((node.right, val-node.right.val))
| 63| return False
#========================================================================================================================
# :: tree/max_path_sum.py ::
#========================================================================================================================
| 1| def max_path_sum(root):
| 2| maximum = float("-inf")
| 3| helper(root, maximum)
| 4| return maximum
| 5|
| 6|
| 7| def helper(root, maximum):
| 8| if root is None:
| 9| return 0
| 10| left = helper(root.left, maximum)
| 11| right = helper(root.right, maximum)
| 12| maximum = max(maximum, left+right+root.val)
| 13| return root.val + maximum
#========================================================================================================================
# :: tree/same_tree.py ::
#========================================================================================================================
| 1| """
| 2| Given two binary trees, write a function to check
| 3| if they are equal or not.
| 4|
| 5| Two binary trees are considered equal if they are
| 6| structurally identical and the nodes have the same value.
| 7| """
| 8|
| 9|
| 10| def is_same_tree(p, q):
| 11| if p is None and q is None:
| 12| return True
| 13| if p is not None and q is not None and p.val == q.val:
| 14| return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)
| 15| return False
| 16|
| 17| # Time Complexity O(min(N,M))
| 18| # where N and M are the number of nodes for the trees.
| 19|
| 20| # Space Complexity O(min(height1, height2))
| 21| # levels of recursion is the mininum height between the two trees.
#========================================================================================================================
# :: tree/tree.py ::
#========================================================================================================================
| 1| class TreeNode:
| 2| def __init__(self, val=0):
| 3| self.val = val
| 4| self.left = None
| 5| self.right = None
#========================================================================================================================
# :: tree/invert_tree.py ::
#========================================================================================================================
| 1| # invert a binary tree
| 2|
| 3| def reverse(root):
| 4| if root is None:
| 5| return
| 6| root.left, root.right = root.right, root.left
| 7| if root.left:
| 8| reverse(root.left)
| 9| if root.right:
| 10| reverse(root.right)
#========================================================================================================================
# :: tree/is_balanced.py ::
#========================================================================================================================
| 1| def is_balanced(root):
| 2| return __is_balanced_recursive(root)
| 3|
| 4|
| 5| def __is_balanced_recursive(root):
| 6| """
| 7| O(N) solution
| 8| """
| 9| return -1 != __get_depth(root)
| 10|
| 11|
| 12| def __get_depth(root):
| 13| """
| 14| return 0 if unbalanced else depth + 1
| 15| """
| 16| if root is None:
| 17| return 0
| 18| left = __get_depth(root.left)
| 19| right = __get_depth(root.right)
| 20| if abs(left-right) > 1 or -1 in [left, right]:
| 21| return -1
| 22| return 1 + max(left, right)
| 23|
| 24|
| 25| # def is_balanced(root):
| 26| # """
| 27| # O(N^2) solution
| 28| # """
| 29| # left = max_height(root.left)
| 30| # right = max_height(root.right)
| 31| # return abs(left-right) <= 1 and is_balanced(root.left) and is_balanced(root.right)
| 32|
| 33| # def max_height(root):
| 34| # if root is None:
| 35| # return 0
| 36| # return max(max_height(root.left), max_height(root.right)) + 1
#========================================================================================================================
# :: tree/binary_tree_paths.py ::
#========================================================================================================================
| 1| def binary_tree_paths(root):
| 2| res = []
| 3| if root is None:
| 4| return res
| 5| dfs(res, root, str(root.val))
| 6| return res
| 7|
| 8|
| 9| def dfs(res, root, cur):
| 10| if root.left is None and root.right is None:
| 11| res.append(cur)
| 12| if root.left:
| 13| dfs(res, root.left, cur+'->'+str(root.left.val))
| 14| if root.right:
| 15| dfs(res, root.right, cur+'->'+str(root.right.val))
#========================================================================================================================
# :: tree/pretty_print.py ::
#========================================================================================================================
| 1| # a -> Adam -> Book -> 4
| 2| # b -> Bill -> Computer -> 5
| 3| # -> TV -> 6
| 4| # Jill -> Sports -> 1
| 5| # c -> Bill -> Sports -> 3
| 6| # d -> Adam -> Computer -> 3
| 7| # Quin -> Computer -> 3
| 8| # e -> Quin -> Book -> 5
| 9| # -> TV -> 2
| 10| # f -> Adam -> Computer -> 7
| 11|
| 12| from __future__ import print_function
| 13|
| 14|
| 15| def tree_print(tree):
| 16| for key in tree:
| 17| print(key, end=' ') # end=' ' prevents a newline character
| 18| tree_element = tree[key] # multiple lookups is expensive, even amortized O(1)!
| 19| for subElem in tree_element:
| 20| print(" -> ", subElem, end=' ')
| 21| if type(subElem) != str: # OP wants indenting after digits
| 22| print("\n ") # newline and a space to match indenting
| 23| print() # forces a newline
#========================================================================================================================
# :: tree/b_tree.py ::
#========================================================================================================================
| 1| """
| 2| B-tree is used to disk operations. Each node (except root) contains
| 3| at least t-1 keys (t children) and at most 2*t - 1 keys (2*t children)
| 4| where t is the degree of b-tree. It is not a kind of typical bst tree, because
| 5| this tree grows up.
| 6| B-tree is balanced which means that the difference between height of left subtree and right subtree is at most 1.
| 7|
| 8| Complexity
| 9| n - number of elements
| 10| t - degree of tree
| 11| Tree always has height at most logt (n+1)/2
| 12| Algorithm Average Worst case
| 13| Space O(n) O(n)
| 14| Search O(log n) O(log n)
| 15| Insert O(log n) O(log n)
| 16| Delete O(log n) O(log n)
| 17| """
| 18|
| 19|
| 20| class Node:
| 21| def __init__(self):
| 22| # self.is_leaf = is_leaf
| 23| self.keys = []
| 24| self.children = []
| 25|
| 26| def __repr__(self):
| 27| return "<id_node: {0}>".format(self.keys)
| 28|
| 29| @property
| 30| def is_leaf(self):
| 31| return len(self.children) == 0
| 32|
| 33|
| 34| class BTree:
| 35| def __init__(self, t=2):
| 36| self.min_numbers_of_keys = t - 1
| 37| self.max_number_of_keys = 2 * t - 1
| 38|
| 39| self.root = Node()
| 40|
| 41| def _split_child(self, parent: Node, child_index: int):
| 42| new_right_child = Node()
| 43| half_max = self.max_number_of_keys // 2
| 44| child = parent.children[child_index]
| 45| middle_key = child.keys[half_max]
| 46| new_right_child.keys = child.keys[half_max + 1:]
| 47| child.keys = child.keys[:half_max]
| 48| # child is left child of parent after splitting
| 49|
| 50| if not child.is_leaf:
| 51| new_right_child.children = child.children[half_max + 1:]
| 52| child.children = child.children[:half_max + 1]
| 53|
| 54| parent.keys.insert(child_index, middle_key)
| 55| parent.children.insert(child_index + 1, new_right_child)
| 56|
| 57| def insert_key(self, key):
| 58| if len(self.root.keys) >= self.max_number_of_keys: # overflow, tree increases in height
| 59| new_root = Node()
| 60| new_root.children.append(self.root)
| 61| self.root = new_root
| 62| self._split_child(new_root, 0)
| 63| self._insert_to_nonfull_node(self.root, key)
| 64| else:
| 65| self._insert_to_nonfull_node(self.root, key)
| 66|
| 67| def _insert_to_nonfull_node(self, node: Node, key):
| 68| i = len(node.keys) - 1
| 69| while i >= 0 and node.keys[i] >= key: # find position where insert key
| 70| i -= 1
| 71|
| 72| if node.is_leaf:
| 73| node.keys.insert(i + 1, key)
| 74| else:
| 75| if len(node.children[i + 1].keys) >= self.max_number_of_keys: # overflow
| 76| self._split_child(node, i + 1)
| 77| if node.keys[i + 1] < key: # decide which child is going to have a new key
| 78| i += 1
| 79|
| 80| self._insert_to_nonfull_node(node.children[i + 1], key)
| 81|
| 82| def find(self, key) -> bool:
| 83| current_node = self.root
| 84| while True:
| 85| i = len(current_node.keys) - 1
| 86| while i >= 0 and current_node.keys[i] > key:
| 87| i -= 1
| 88|
| 89| if i >= 0 and current_node.keys[i] == key:
| 90| return True
| 91| elif current_node.is_leaf:
| 92| return False
| 93| else:
| 94| current_node = current_node.children[i + 1]
| 95|
| 96| def remove_key(self, key):
| 97| self._remove_key(self.root, key)
| 98|
| 99| def _remove_key(self, node: Node, key) -> bool:
|100| try:
|101| key_index = node.keys.index(key)
|102| if node.is_leaf:
|103| node.keys.remove(key)
|104| return True
|105| else:
|106| self._remove_from_nonleaf_node(node, key_index)
|107|
|108| return True
|109|
|110| except ValueError: # key not found in node
|111| if node.is_leaf:
|112| print("Key not found.")
|113| return False # key not found
|114| else:
|115| i = 0
|116| number_of_keys = len(node.keys)
|117| while i < number_of_keys and key > node.keys[i]: # decide in which subtree may be key
|118| i += 1
|119|
|120| action_performed = self._repair_tree(node, i)
|121| if action_performed:
|122| return self._remove_key(node, key)
|123| else:
|124| return self._remove_key(node.children[i], key)
|125|
|126| def _repair_tree(self, node: Node, child_index: int) -> bool:
|127| child = node.children[child_index]
|128| if self.min_numbers_of_keys < len(child.keys) <= self.max_number_of_keys: # The leaf/node is correct
|129| return False
|130|
|131| if child_index > 0 and len(node.children[child_index - 1].keys) > self.min_numbers_of_keys:
|132| self._rotate_right(node, child_index)
|133| return True
|134|
|135| if (child_index < len(node.children) - 1 and
|136| len(node.children[child_index + 1].keys) > self.min_numbers_of_keys): # 0 <-- 1
|137| self._rotate_left(node, child_index)
|138| return True
|139|
|140| if child_index > 0:
|141| # merge child with brother on the left
|142| self._merge(node, child_index - 1, child_index)
|143| else:
|144| # merge child with brother on the right
|145| self._merge(node, child_index, child_index + 1)
|146|
|147| return True
|148|
|149| def _rotate_left(self, parent_node: Node, child_index: int):
|150| """
|151| Take key from right brother of the child and transfer to the child
|152| """
|153| new_child_key = parent_node.keys[child_index]
|154| new_parent_key = parent_node.children[child_index + 1].keys.pop(0)
|155| parent_node.children[child_index].keys.append(new_child_key)
|156| parent_node.keys[child_index] = new_parent_key
|157|
|158| if not parent_node.children[child_index + 1].is_leaf:
|159| ownerless_child = parent_node.children[child_index + 1].children.pop(0)
|160| # make ownerless_child as a new biggest child (with highest key) -> transfer from right subtree to left subtree
|161| parent_node.children[child_index].children.append(ownerless_child)
|162|
|163| def _rotate_right(self, parent_node: Node, child_index: int):
|164| """
|165| Take key from left brother of the child and transfer to the child
|166| """
|167| parent_key = parent_node.keys[child_index - 1]
|168| new_parent_key = parent_node.children[child_index - 1].keys.pop()
|169| parent_node.children[child_index].keys.insert(0, parent_key)
|170| parent_node.keys[child_index - 1] = new_parent_key
|171|
|172| if not parent_node.children[child_index - 1].is_leaf:
|173| ownerless_child = parent_node.children[child_index - 1].children.pop()
|174| # make ownerless_child as a new lowest child (with lowest key) -> transfer from left subtree to right subtree
|175| parent_node.children[child_index].children.insert(0, ownerless_child)
|176|
|177| def _merge(self, parent_node: Node, to_merge_index: int, transfered_child_index: int):
|178| from_merge_node = parent_node.children.pop(transfered_child_index)
|179| parent_key_to_merge = parent_node.keys.pop(to_merge_index)
|180| to_merge_node = parent_node.children[to_merge_index]
|181| to_merge_node.keys.append(parent_key_to_merge)
|182| to_merge_node.keys.extend(from_merge_node.keys)
|183|
|184| if not to_merge_node.is_leaf:
|185| to_merge_node.children.extend(from_merge_node.children)
|186|
|187| if parent_node == self.root and not parent_node.keys:
|188| self.root = to_merge_node
|189|
|190| def _remove_from_nonleaf_node(self, node: Node, key_index: int):
|191| key = node.keys[key_index]
|192| left_subtree = node.children[key_index]
|193| if len(left_subtree.keys) > self.min_numbers_of_keys:
|194| largest_key = self._find_largest_and_delete_in_left_subtree(left_subtree)
|195| elif len(node.children[key_index + 1].keys) > self.min_numbers_of_keys:
|196| largest_key = self._find_largest_and_delete_in_right_subtree(node.children[key_index + 1])
|197| else:
|198| self._merge(node, key_index, key_index + 1)
|199| return self._remove_key(node, key)
|200|
|201| node.keys[key_index] = largest_key
|202|
|203| def _find_largest_and_delete_in_left_subtree(self, node: Node):
|204| if node.is_leaf:
|205| return node.keys.pop()
|206| else:
|207| ch_index = len(node.children) - 1
|208| self._repair_tree(node, ch_index)
|209| largest_key_in_subtree = self._find_largest_and_delete_in_left_subtree(
|210| node.children[len(node.children) - 1])
|211| # self._repair_tree(node, ch_index)
|212| return largest_key_in_subtree
|213|
|214| def _find_largest_and_delete_in_right_subtree(self, node: Node):
|215| if node.is_leaf:
|216| return node.keys.pop(0)
|217| else:
|218| ch_index = 0
|219| self._repair_tree(node, ch_index)
|220| largest_key_in_subtree = self._find_largest_and_delete_in_right_subtree(node.children[0])
|221| # self._repair_tree(node, ch_index)
|222| return largest_key_in_subtree
|223|
|224| def traverse_tree(self):
|225| self._traverse_tree(self.root)
|226| print()
|227|
|228| def _traverse_tree(self, node: Node):
|229| if node.is_leaf:
|230| print(node.keys, end=" ")
|231| else:
|232| for i, key in enumerate(node.keys):
|233| self._traverse_tree(node.children[i])
|234| print(key, end=" ")
|235| self._traverse_tree(node.children[-1])
#========================================================================================================================
# :: tree/is_symmetric.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, check whether it is a mirror of
| 3| itself (ie, symmetric around its center).
| 4|
| 5| For example, this binary tree [1,2,2,3,4,4,3] is symmetric:
| 6|
| 7| 1
| 8| / \
| 9| 2 2
| 10| / \ / \
| 11| 3 4 4 3
| 12| But the following [1,2,2,null,3,null,3] is not:
| 13| 1
| 14| / \
| 15| 2 2
| 16| \ \
| 17| 3 3
| 18| Note:
| 19| Bonus points if you could solve it both recursively and iteratively.
| 20| """
| 21|
| 22| # TC: O(b) SC: O(log n)
| 23| def is_symmetric(root):
| 24| if root is None:
| 25| return True
| 26| return helper(root.left, root.right)
| 27|
| 28|
| 29| def helper(p, q):
| 30| if p is None and q is None:
| 31| return True
| 32| if p is not None or q is not None or q.val != p.val:
| 33| return False
| 34| return helper(p.left, q.right) and helper(p.right, q.left)
| 35|
| 36|
| 37| def is_symmetric_iterative(root):
| 38| if root is None:
| 39| return True
| 40| stack = [[root.left, root.right]]
| 41| while stack:
| 42| left, right = stack.pop() # popleft
| 43| if left is None and right is None:
| 44| continue
| 45| if left is None or right is None:
| 46| return False
| 47| if left.val == right.val:
| 48| stack.append([left.left, right.right])
| 49| stack.append([left.right, right.left])
| 50| else:
| 51| return False
| 52| return True
#========================================================================================================================
# :: tree/is_subtree.py ::
#========================================================================================================================
| 1| """
| 2| Given two binary trees s and t, check if t is a subtree of s.
| 3| A subtree of a tree t is a tree consisting of a node in t and
| 4| all of its descendants in t.
| 5|
| 6| Example 1:
| 7|
| 8| Given s:
| 9|
| 10| 3
| 11| / \
| 12| 4 5
| 13| / \
| 14| 1 2
| 15|
| 16| Given t:
| 17|
| 18| 4
| 19| / \
| 20| 1 2
| 21| Return true, because t is a subtree of s.
| 22|
| 23| Example 2:
| 24|
| 25| Given s:
| 26|
| 27| 3
| 28| / \
| 29| 4 5
| 30| / \
| 31| 1 2
| 32| /
| 33| 0
| 34|
| 35| Given t:
| 36|
| 37| 3
| 38| /
| 39| 4
| 40| / \
| 41| 1 2
| 42| Return false, because even though t is part of s,
| 43| it does not contain all descendants of t.
| 44|
| 45| Follow up:
| 46| What if one tree is significantly lager than the other?
| 47| """
| 48| import collections
| 49|
| 50|
| 51| def is_subtree(big, small):
| 52| flag = False
| 53| queue = collections.deque()
| 54| queue.append(big)
| 55| while queue:
| 56| node = queue.popleft()
| 57| if node.val == small.val:
| 58| flag = comp(node, small)
| 59| break
| 60| else:
| 61| queue.append(node.left)
| 62| queue.append(node.right)
| 63| return flag
| 64|
| 65|
| 66| def comp(p, q):
| 67| if p is None and q is None:
| 68| return True
| 69| if p is not None and q is not None:
| 70| return p.val == q.val and comp(p.left,q.left) and comp(p.right, q.right)
| 71| return False
#========================================================================================================================
# :: tree/max_height.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, find its maximum depth.
| 3|
| 4| The maximum depth is the number of nodes along the
| 5| longest path from the root node down to the farthest leaf node.
| 6| """
| 7|
| 8| # def max_height(root):
| 9| # if not root:
| 10| # return 0
| 11| # return max(maxDepth(root.left), maxDepth(root.right)) + 1
| 12|
| 13| # iterative
| 14|
| 15| from tree.tree import TreeNode
| 16|
| 17|
| 18| def max_height(root):
| 19| if root is None:
| 20| return 0
| 21| height = 0
| 22| queue = [root]
| 23| while queue:
| 24| height += 1
| 25| level = []
| 26| while queue:
| 27| node = queue.pop(0)
| 28| if node.left is not None:
| 29| level.append(node.left)
| 30| if node.right is not None:
| 31| level.append(node.right)
| 32| queue = level
| 33| return height
| 34|
| 35|
| 36| def print_tree(root):
| 37| if root is not None:
| 38| print(root.val)
| 39| print_tree(root.left)
| 40| print_tree(root.right)
| 41|
| 42|
| 43| if __name__ == '__main__':
| 44| tree = TreeNode(10)
| 45| tree.left = TreeNode(12)
| 46| tree.right = TreeNode(15)
| 47| tree.left.left = TreeNode(25)
| 48| tree.left.left.right = TreeNode(100)
| 49| tree.left.right = TreeNode(30)
| 50| tree.right.left = TreeNode(36)
| 51|
| 52| height = max_height(tree)
| 53| print_tree(tree)
| 54| print("height:", height)
#========================================================================================================================
# :: tree/min_height.py ::
#========================================================================================================================
| 1| from tree.tree import TreeNode
| 2|
| 3|
| 4| def min_depth(self, root):
| 5| """
| 6| :type root: TreeNode
| 7| :rtype: int
| 8| """
| 9| if root is None:
| 10| return 0
| 11| if root.left is not None or root.right is not None:
| 12| return max(self.minDepth(root.left), self.minDepth(root.right))+1
| 13| return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
| 14|
| 15|
| 16| # iterative
| 17| def min_height(root):
| 18| if root is None:
| 19| return 0
| 20| height = 0
| 21| level = [root]
| 22| while level:
| 23| height += 1
| 24| new_level = []
| 25| for node in level:
| 26| if node.left is None and node.right is None:
| 27| return height
| 28| if node.left is not None:
| 29| new_level.append(node.left)
| 30| if node.right is not None:
| 31| new_level.append(node.right)
| 32| level = new_level
| 33| return height
| 34|
| 35|
| 36| def print_tree(root):
| 37| if root is not None:
| 38| print(root.val)
| 39| print_tree(root.left)
| 40| print_tree(root.right)
| 41|
| 42|
| 43| if __name__ == '__main__':
| 44| tree = TreeNode(10)
| 45| tree.left = TreeNode(12)
| 46| tree.right = TreeNode(15)
| 47| tree.left.left = TreeNode(25)
| 48| tree.left.left.right = TreeNode(100)
| 49| tree.left.right = TreeNode(30)
| 50| tree.right.left = TreeNode(36)
| 51|
| 52| height = min_height(tree)
| 53| print_tree(tree)
| 54| print("height:", height)
#========================================================================================================================
# :: tree/bin_tree_to_list.py ::
#========================================================================================================================
| 1| from tree.tree import TreeNode
| 2|
| 3|
| 4| def bin_tree_to_list(root):
| 5| """
| 6| type root: root class
| 7| """
| 8| if not root:
| 9| return root
| 10| root = bin_tree_to_list_util(root)
| 11| while root.left:
| 12| root = root.left
| 13| return root
| 14|
| 15|
| 16| def bin_tree_to_list_util(root):
| 17| if not root:
| 18| return root
| 19| if root.left:
| 20| left = bin_tree_to_list_util(root.left)
| 21| while left.right:
| 22| left = left.right
| 23| left.right = root
| 24| root.left = left
| 25| if root.right:
| 26| right = bin_tree_to_list_util(root.right)
| 27| while right.left:
| 28| right = right.left
| 29| right.left = root
| 30| root.right = right
| 31| return root
| 32|
| 33|
| 34| def print_tree(root):
| 35| while root:
| 36| print(root.val)
| 37| root = root.right
#========================================================================================================================
# :: tree/lowest_common_ancestor.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, find the lowest common ancestor
| 3| (LCA) of two given nodes in the tree.
| 4|
| 5| According to the definition of LCA on Wikipedia:
| 6| “The lowest common ancestor is defined between two nodes
| 7| v and w as the lowest node in T that has both v and w as
| 8| descendants
| 9| (where we allow a node to be a descendant of itself).”
| 10|
| 11| _______3______
| 12| / \
| 13| ___5__ ___1__
| 14| / \ / \
| 15| 6 _2 0 8
| 16| / \
| 17| 7 4
| 18| For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3.
| 19| Another example is LCA of nodes 5 and 4 is 5,
| 20| since a node can be a descendant of itself according to the LCA definition.
| 21| """
| 22|
| 23|
| 24| def lca(root, p, q):
| 25| """
| 26| :type root: TreeNode
| 27| :type p: TreeNode
| 28| :type q: TreeNode
| 29| :rtype: TreeNode
| 30| """
| 31| if root is None or root is p or root is q:
| 32| return root
| 33| left = lca(root.left, p, q)
| 34| right = lca(root.right, p, q)
| 35| if left is not None and right is not None:
| 36| return root
| 37| return left if left else right
#========================================================================================================================
# :: tree/path_sum2.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree and a sum, find all root-to-leaf
| 3| paths where each path's sum equals the given sum.
| 4|
| 5| For example:
| 6| Given the below binary tree and sum = 22,
| 7| 5
| 8| / \
| 9| 4 8
| 10| / / \
| 11| 11 13 4
| 12| / \ / \
| 13| 7 2 5 1
| 14| return
| 15| [
| 16| [5,4,11,2],
| 17| [5,8,4,5]
| 18| ]
| 19| """
| 20|
| 21|
| 22| def path_sum(root, sum):
| 23| if root is None:
| 24| return []
| 25| res = []
| 26| dfs(root, sum, [], res)
| 27| return res
| 28|
| 29|
| 30| def dfs(root, sum, ls, res):
| 31| if root.left is None and root.right is None and root.val == sum:
| 32| ls.append(root.val)
| 33| res.append(ls)
| 34| if root.left is not None:
| 35| dfs(root.left, sum-root.val, ls+[root.val], res)
| 36| if root.right is not None:
| 37| dfs(root.right, sum-root.val, ls+[root.val], res)
| 38|
| 39|
| 40| # DFS with stack
| 41| def path_sum2(root, s):
| 42| if root is None:
| 43| return []
| 44| res = []
| 45| stack = [(root, [root.val])]
| 46| while stack:
| 47| node, ls = stack.pop()
| 48| if node.left is None and node.right is None and sum(ls) == s:
| 49| res.append(ls)
| 50| if node.left is not None:
| 51| stack.append((node.left, ls+[node.left.val]))
| 52| if node.right is not None:
| 53| stack.append((node.right, ls+[node.right.val]))
| 54| return res
| 55|
| 56|
| 57| # BFS with queue
| 58| def path_sum3(root, sum):
| 59| if root is None:
| 60| return []
| 61| res = []
| 62| queue = [(root, root.val, [root.val])]
| 63| while queue:
| 64| node, val, ls = queue.pop(0) # popleft
| 65| if node.left is None and node.right is None and val == sum:
| 66| res.append(ls)
| 67| if node.left is not None:
| 68| queue.append((node.left, val+node.left.val, ls+[node.left.val]))
| 69| if node.right is not None:
| 70| queue.append((node.right, val+node.right.val, ls+[node.right.val]))
| 71| return res
#========================================================================================================================
# :: tree/longest_consecutive.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, find the length of the longest consecutive sequence path.
| 3|
| 4| The path refers to any sequence of nodes from some starting node to any node
| 5| in the tree along the parent-child connections.
| 6| The longest consecutive path need to be from parent to child
| 7| (cannot be the reverse).
| 8|
| 9| For example,
| 10| 1
| 11| \
| 12| 3
| 13| / \
| 14| 2 4
| 15| \
| 16| 5
| 17| Longest consecutive sequence path is 3-4-5, so return 3.
| 18| 2
| 19| \
| 20| 3
| 21| /
| 22| 2
| 23| /
| 24| 1
| 25| """
| 26|
| 27|
| 28| def longest_consecutive(root):
| 29| """
| 30| :type root: TreeNode
| 31| :rtype: int
| 32| """
| 33| if root is None:
| 34| return 0
| 35| max_len = 0
| 36| dfs(root, 0, root.val, max_len)
| 37| return max_len
| 38|
| 39|
| 40| def dfs(root, cur, target, max_len):
| 41| if root is None:
| 42| return
| 43| if root.val == target:
| 44| cur += 1
| 45| else:
| 46| cur = 1
| 47| max_len = max(cur, max_len)
| 48| dfs(root.left, cur, root.val+1, max_len)
| 49| dfs(root.right, cur, root.val+1, max_len)
#========================================================================================================================
# :: tree/deepest_left.py ::
#========================================================================================================================
| 1| # Given a binary tree, find the deepest node
| 2| # that is the left child of its parent node.
| 3|
| 4| # Example:
| 5|
| 6| # 1
| 7| # / \
| 8| # 2 3
| 9| # / \ \
| 10| # 4 5 6
| 11| # \
| 12| # 7
| 13| # should return 4.
| 14|
| 15| from tree.tree import TreeNode
| 16|
| 17|
| 18| class DeepestLeft:
| 19| def __init__(self):
| 20| self.depth = 0
| 21| self.Node = None
| 22|
| 23|
| 24| def find_deepest_left(root, is_left, depth, res):
| 25| if not root:
| 26| return
| 27| if is_left and depth > res.depth:
| 28| res.depth = depth
| 29| res.Node = root
| 30| find_deepest_left(root.left, True, depth + 1, res)
| 31| find_deepest_left(root.right, False, depth + 1, res)
| 32|
| 33|
| 34| if __name__ == '__main__':
| 35| root = TreeNode(1)
| 36| root.left = TreeNode(2)
| 37| root.right = TreeNode(3)
| 38| root.left.left = TreeNode(4)
| 39| root.left.right = TreeNode(5)
| 40| root.right.right = TreeNode(6)
| 41| root.right.right.right = TreeNode(7)
| 42|
| 43| res = DeepestLeft()
| 44| find_deepest_left(root, True, 1, res)
| 45| if res.Node:
| 46| print(res.Node.val)
#========================================================================================================================
# :: tree/segment_tree/segment_tree.py ::
#========================================================================================================================
| 1| '''
| 2| Segment_tree creates a segment tree with a given array and function,
| 3| allowing queries to be done later in log(N) time
| 4| function takes 2 values and returns a same type value
| 5| '''
| 6| class SegmentTree:
| 7| def __init__(self,arr,function):
| 8| self.segment = [0 for x in range(3*len(arr)+3)]
| 9| self.arr = arr
| 10| self.fn = function
| 11| self.maketree(0,0,len(arr)-1)
| 12|
| 13| def make_tree(self,i,l,r):
| 14| if l==r:
| 15| self.segment[i] = self.arr[l]
| 16| elif l<r:
| 17| self.make_tree(2*i+1,l,int((l+r)/2))
| 18| self.make_tree(2*i+2,int((l+r)/2)+1,r)
| 19| self.segment[i] = self.fn(self.segment[2*i+1],self.segment[2*i+2])
| 20|
| 21| def __query(self,i,L,R,l,r):
| 22| if l>R or r<L or L>R or l>r:
| 23| return None
| 24| if L>=l and R<=r:
| 25| return self.segment[i]
| 26| val1 = self.__query(2*i+1,L,int((L+R)/2),l,r)
| 27| val2 = self.__query(2*i+2,int((L+R+2)/2),R,l,r)
| 28| print(L,R," returned ",val1,val2)
| 29| if val1 != None:
| 30| if val2 != None:
| 31| return self.fn(val1,val2)
| 32| return val1
| 33| return val2
| 34|
| 35|
| 36| def query(self,L,R):
| 37| return self.__query(0,0,len(self.arr)-1,L,R)
| 38|
| 39| '''
| 40| Example -
| 41| mytree = SegmentTree([2,4,5,3,4],max)
| 42| mytree.query(2,4)
| 43| mytree.query(0,3) ...
| 44|
| 45| mytree = SegmentTree([4,5,2,3,4,43,3],sum)
| 46| mytree.query(1,8)
| 47| ...
| 48|
| 49| '''
#========================================================================================================================
# :: tree/trie/add_and_search.py ::
#========================================================================================================================
| 1| """
| 2| We are asked to design an efficient data structure
| 3| that allows us to add and search for words.
| 4| The search can be a literal word or regular expression
| 5| containing “.”, where “.” can be any letter.
| 6|
| 7| Example:
| 8| addWord(“bad”)
| 9| addWord(“dad”)
| 10| addWord(“mad”)
| 11| search(“pad”) -> false
| 12| search(“bad”) -> true
| 13| search(“.ad”) -> true
| 14| search(“b..”) -> true
| 15| """
| 16| import collections
| 17|
| 18| class TrieNode(object):
| 19| def __init__(self, letter, is_terminal=False):
| 20| self.children = dict()
| 21| self.letter = letter
| 22| self.is_terminal = is_terminal
| 23|
| 24| class WordDictionary(object):
| 25| def __init__(self):
| 26| self.root = TrieNode("")
| 27|
| 28| def add_word(self, word):
| 29| cur = self.root
| 30| for letter in word:
| 31| if letter not in cur.children:
| 32| cur.children[letter] = TrieNode(letter)
| 33| cur = cur.children[letter]
| 34| cur.is_terminal = True
| 35|
| 36| def search(self, word, node=None):
| 37| cur = node
| 38| if not cur:
| 39| cur = self.root
| 40| for i, letter in enumerate(word):
| 41| # if dot
| 42| if letter == ".":
| 43| if i == len(word) - 1: # if last character
| 44| for child in cur.children.itervalues():
| 45| if child.is_terminal:
| 46| return True
| 47| return False
| 48| for child in cur.children.itervalues():
| 49| if self.search(word[i+1:], child) == True:
| 50| return True
| 51| return False
| 52| # if letter
| 53| if letter not in cur.children:
| 54| return False
| 55| cur = cur.children[letter]
| 56| return cur.is_terminal
| 57|
| 58| class WordDictionary2(object):
| 59| def __init__(self):
| 60| self.word_dict = collections.defaultdict(list)
| 61|
| 62|
| 63| def add_word(self, word):
| 64| if word:
| 65| self.word_dict[len(word)].append(word)
| 66|
| 67| def search(self, word):
| 68| if not word:
| 69| return False
| 70| if '.' not in word:
| 71| return word in self.word_dict[len(word)]
| 72| for v in self.word_dict[len(word)]:
| 73| # match xx.xx.x with yyyyyyy
| 74| for i, ch in enumerate(word):
| 75| if ch != v[i] and ch != '.':
| 76| break
| 77| else:
| 78| return True
| 79| return False
#========================================================================================================================
# :: tree/trie/trie.py ::
#========================================================================================================================
| 1| """
| 2| Implement a trie with insert, search, and startsWith methods.
| 3|
| 4| Note:
| 5| You may assume that all inputs are consist of lowercase letters a-z.
| 6| """
| 7| import collections
| 8|
| 9|
| 10| class TrieNode:
| 11| def __init__(self):
| 12| self.children = collections.defaultdict(TrieNode)
| 13| self.is_word = False
| 14|
| 15|
| 16| class Trie:
| 17| def __init__(self):
| 18| self.root = TrieNode()
| 19|
| 20| def insert(self, word):
| 21| current = self.root
| 22| for letter in word:
| 23| current = current.children[letter]
| 24| current.is_word = True
| 25|
| 26| def search(self, word):
| 27| current = self.root
| 28| for letter in word:
| 29| current = current.children.get(letter)
| 30| if current is None:
| 31| return False
| 32| return current.is_word
| 33|
| 34| def starts_with(self, prefix):
| 35| current = self.root
| 36| for letter in prefix:
| 37| current = current.children.get(letter)
| 38| if current is None:
| 39| return False
| 40| return True
| 41|
#========================================================================================================================
# :: tree/traversal/inorder.py ::
#========================================================================================================================
| 1| '''
| 2| Time complexity : O(n)
| 3| '''
| 4|
| 5| class Node:
| 6|
| 7| def __init__(self, val, left=None, right=None):
| 8| self.val = val
| 9| self.left = left
| 10| self.right = right
| 11|
| 12|
| 13| def inorder(root):
| 14| res = []
| 15| if not root:
| 16| return res
| 17| stack = []
| 18| while root or stack:
| 19| while root:
| 20| stack.append(root)
| 21| root = root.left
| 22| root = stack.pop()
| 23| res.append(root.val)
| 24| root = root.right
| 25| return res
| 26|
| 27| # Recursive Implementation
| 28| def inorder_rec(root, res=None):
| 29| if root is None:
| 30| return []
| 31| if res is None:
| 32| res = []
| 33| inorder_rec(root.left, res)
| 34| res.append(root.val)
| 35| inorder_rec(root.right, res)
| 36| return res
| 37|
| 38| if __name__ == '__main__':
| 39| n1 = Node(100)
| 40| n2 = Node(50)
| 41| n3 = Node(150)
| 42| n4 = Node(25)
| 43| n5 = Node(75)
| 44| n6 = Node(125)
| 45| n7 = Node(175)
| 46| n1.left, n1.right = n2, n3
| 47| n2.left, n2.right = n4, n5
| 48| n3.left, n3.right = n6, n7
| 49|
| 50| assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175]
| 51| assert inorder_rec(n1) == [25, 50, 75, 100, 125, 150, 175]
#========================================================================================================================
# :: tree/traversal/level_order.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, return the level order traversal of
| 3| its nodes' values. (ie, from left to right, level by level).
| 4|
| 5| For example:
| 6| Given binary tree [3,9,20,null,null,15,7],
| 7| 3
| 8| / \
| 9| 9 20
| 10| / \
| 11| 15 7
| 12| return its level order traversal as:
| 13| [
| 14| [3],
| 15| [9,20],
| 16| [15,7]
| 17| ]
| 18| """
| 19|
| 20|
| 21| def level_order(root):
| 22| ans = []
| 23| if not root:
| 24| return ans
| 25| level = [root]
| 26| while level:
| 27| current = []
| 28| new_level = []
| 29| for node in level:
| 30| current.append(node.val)
| 31| if node.left:
| 32| new_level.append(node.left)
| 33| if node.right:
| 34| new_level.append(node.right)
| 35| level = new_level
| 36| ans.append(current)
| 37| return ans
#========================================================================================================================
# :: tree/traversal/postorder.py ::
#========================================================================================================================
| 1| '''
| 2| Time complexity : O(n)
| 3| '''
| 4|
| 5| class Node:
| 6|
| 7| def __init__(self, val, left=None, right=None):
| 8| self.val = val
| 9| self.left = left
| 10| self.right = right
| 11|
| 12|
| 13| def postorder(root):
| 14| res_temp = []
| 15| res = []
| 16| if not root:
| 17| return res
| 18| stack = []
| 19| stack.append(root)
| 20| while stack:
| 21| root = stack.pop()
| 22| res_temp.append(root.val)
| 23| if root.left:
| 24| stack.append(root.left)
| 25| if root.right:
| 26| stack.append(root.right)
| 27| while res_temp:
| 28| res.append(res_temp.pop())
| 29| return res
| 30|
| 31| # Recursive Implementation
| 32| def postorder_rec(root, res=None):
| 33| if root is None:
| 34| return []
| 35| if res is None:
| 36| res = []
| 37| postorder_rec(root.left, res)
| 38| postorder_rec(root.right, res)
| 39| res.append(root.val)
| 40| return res
| 41|
#========================================================================================================================
# :: tree/traversal/__init__.py ::
#========================================================================================================================
| 1| from .preorder import *
| 2| from .postorder import *
| 3| from .inorder import *
#========================================================================================================================
# :: tree/traversal/zigzag.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, return the zigzag level order traversal
| 3| of its nodes' values.
| 4| (ie, from left to right, then right to left
| 5| for the next level and alternate between).
| 6|
| 7| For example:
| 8| Given binary tree [3,9,20,null,null,15,7],
| 9| 3
| 10| / \
| 11| 9 20
| 12| / \
| 13| 15 7
| 14| return its zigzag level order traversal as:
| 15| [
| 16| [3],
| 17| [20,9],
| 18| [15,7]
| 19| ]
| 20| """
| 21|
| 22|
| 23| def zigzag_level(root):
| 24| res = []
| 25| if not root:
| 26| return res
| 27| level = [root]
| 28| flag = 1
| 29| while level:
| 30| current = []
| 31| new_level = []
| 32| for node in level:
| 33| current.append(node.val)
| 34| if node.left:
| 35| new_level.append(node.left)
| 36| if node.right:
| 37| new_level.append(node.right)
| 38| level = new_level
| 39| res.append(current[::flag])
| 40| flag *= -1
| 41| return res
#========================================================================================================================
# :: tree/traversal/preorder.py ::
#========================================================================================================================
| 1| '''
| 2| Time complexity : O(n)
| 3| '''
| 4|
| 5| class Node:
| 6|
| 7| def __init__(self, val, left=None, right=None):
| 8| self.val = val
| 9| self.left = left
| 10| self.right = right
| 11|
| 12|
| 13| def preorder(root):
| 14| res = []
| 15| if not root:
| 16| return res
| 17| stack = []
| 18| stack.append(root)
| 19| while stack:
| 20| root = stack.pop()
| 21| res.append(root.val)
| 22| if root.right:
| 23| stack.append(root.right)
| 24| if root.left:
| 25| stack.append(root.left)
| 26| return res
| 27|
| 28| # Recursive Implementation
| 29| def preorder_rec(root, res=None):
| 30| if root is None:
| 31| return []
| 32| if res is None:
| 33| res = []
| 34| res.append(root.val)
| 35| preorder_rec(root.left, res)
| 36| preorder_rec(root.right, res)
| 37| return res
| 38|
#========================================================================================================================
# :: tree/avl/__init__.py ::
#========================================================================================================================
#========================================================================================================================
# :: tree/avl/avl.py ::
#========================================================================================================================
| 1| from tree.tree import TreeNode
| 2|
| 3|
| 4| class AvlTree(object):
| 5| """
| 6| An avl tree.
| 7| """
| 8|
| 9| def __init__(self):
| 10| # Root node of the tree.
| 11| self.node = None
| 12| self.height = -1
| 13| self.balance = 0
| 14|
| 15| def insert(self, key):
| 16| """
| 17| Insert new key into node
| 18| """
| 19| # Create new node
| 20| n = TreeNode(key)
| 21| if not self.node:
| 22| self.node = n
| 23| self.node.left = AvlTree()
| 24| self.node.right = AvlTree()
| 25| elif key < self.node.val:
| 26| self.node.left.insert(key)
| 27| elif key > self.node.val:
| 28| self.node.right.insert(key)
| 29| self.re_balance()
| 30|
| 31| def re_balance(self):
| 32| """
| 33| Re balance tree. After inserting or deleting a node,
| 34| """
| 35| self.update_heights(recursive=False)
| 36| self.update_balances(False)
| 37|
| 38| while self.balance < -1 or self.balance > 1:
| 39| if self.balance > 1:
| 40| if self.node.left.balance < 0:
| 41| self.node.left.rotate_left()
| 42| self.update_heights()
| 43| self.update_balances()
| 44| self.rotate_right()
| 45| self.update_heights()
| 46| self.update_balances()
| 47|
| 48| if self.balance < -1:
| 49| if self.node.right.balance > 0:
| 50| self.node.right.rotate_right()
| 51| self.update_heights()
| 52| self.update_balances()
| 53| self.rotate_left()
| 54| self.update_heights()
| 55| self.update_balances()
| 56|
| 57| def update_heights(self, recursive=True):
| 58| """
| 59| Update tree height
| 60| """
| 61| if self.node:
| 62| if recursive:
| 63| if self.node.left:
| 64| self.node.left.update_heights()
| 65| if self.node.right:
| 66| self.node.right.update_heights()
| 67|
| 68| self.height = 1 + max(self.node.left.height, self.node.right.height)
| 69| else:
| 70| self.height = -1
| 71|
| 72| def update_balances(self, recursive=True):
| 73| """
| 74| Calculate tree balance factor
| 75|
| 76| """
| 77| if self.node:
| 78| if recursive:
| 79| if self.node.left:
| 80| self.node.left.update_balances()
| 81| if self.node.right:
| 82| self.node.right.update_balances()
| 83|
| 84| self.balance = self.node.left.height - self.node.right.height
| 85| else:
| 86| self.balance = 0
| 87|
| 88| def rotate_right(self):
| 89| """
| 90| Right rotation
| 91| """
| 92| new_root = self.node.left.node
| 93| new_left_sub = new_root.right.node
| 94| old_root = self.node
| 95|
| 96| self.node = new_root
| 97| old_root.left.node = new_left_sub
| 98| new_root.right.node = old_root
| 99|
|100| def rotate_left(self):
|101| """
|102| Left rotation
|103| """
|104| new_root = self.node.right.node
|105| new_left_sub = new_root.left.node
|106| old_root = self.node
|107|
|108| self.node = new_root
|109| old_root.right.node = new_left_sub
|110| new_root.left.node = old_root
|111|
|112| def in_order_traverse(self):
|113| """
|114| In-order traversal of the tree
|115| """
|116| result = []
|117|
|118| if not self.node:
|119| return result
|120|
|121| result.extend(self.node.left.in_order_traverse())
|122| result.append(self.node.key)
|123| result.extend(self.node.right.in_order_traverse())
|124| return result
#========================================================================================================================
# :: tree/red_black_tree/red_black_tree.py ::
#========================================================================================================================
| 1| """
| 2| Implementation of Red-Black tree.
| 3| """
| 4|
| 5|
| 6| class RBNode:
| 7| def __init__(self, val, is_red, parent=None, left=None, right=None):
| 8| self.val = val
| 9| self.parent = parent
| 10| self.left = left
| 11| self.right = right
| 12| self.color = is_red
| 13|
| 14|
| 15| class RBTree:
| 16| def __init__(self):
| 17| self.root = None
| 18|
| 19| def left_rotate(self, node):
| 20| # set the node as the left child node of the current node's right node
| 21| right_node = node.right
| 22| if right_node is None:
| 23| return
| 24| else:
| 25| # right node's left node become the right node of current node
| 26| node.right = right_node.left
| 27| if right_node.left is not None:
| 28| right_node.left.parent = node
| 29| right_node.parent = node.parent
| 30| # check the parent case
| 31| if node.parent is None:
| 32| self.root = right_node
| 33| elif node is node.parent.left:
| 34| node.parent.left = right_node
| 35| else:
| 36| node.parent.right = right_node
| 37| right_node.left = node
| 38| node.parent = right_node
| 39|
| 40| def right_rotate(self, node):
| 41| # set the node as the right child node of the current node's left node
| 42| left_node = node.left
| 43| if left_node is None:
| 44| return
| 45| else:
| 46| # left node's right node become the left node of current node
| 47| node.left = left_node.right
| 48| if left_node.right is not None:
| 49| left_node.right.parent = node
| 50| left_node.parent = node.parent
| 51| # check the parent case
| 52| if node.parent is None:
| 53| self.root = left_node
| 54| elif node is node.parent.left:
| 55| node.parent.left = left_node
| 56| else:
| 57| node.parent.right = left_node
| 58| left_node.right = node
| 59| node.parent = left_node
| 60|
| 61| def insert(self, node):
| 62| # the inserted node's color is default is red
| 63| root = self.root
| 64| insert_node_parent = None
| 65| # find the position of inserted node
| 66| while root is not None:
| 67| insert_node_parent = root
| 68| if insert_node_parent.val < node.val:
| 69| root = root.right
| 70| else:
| 71| root = root.left
| 72| # set the n ode's parent node
| 73| node.parent = insert_node_parent
| 74| if insert_node_parent is None:
| 75| # case 1 inserted tree is null
| 76| self.root = node
| 77| elif insert_node_parent.val > node.val:
| 78| # case 2 not null and find left or right
| 79| insert_node_parent.left = node
| 80| else:
| 81| insert_node_parent.right = node
| 82| node.left = None
| 83| node.right = None
| 84| node.color = 1
| 85| # fix the tree to
| 86| self.fix_insert(node)
| 87|
| 88| def fix_insert(self, node):
| 89| # case 1 the parent is null, then set the inserted node as root and color = 0
| 90| if node.parent is None:
| 91| node.color = 0
| 92| self.root = node
| 93| return
| 94| # case 2 the parent color is black, do nothing
| 95| # case 3 the parent color is red
| 96| while node.parent and node.parent.color == 1:
| 97| if node.parent is node.parent.parent.left:
| 98| uncle_node = node.parent.parent.right
| 99| if uncle_node and uncle_node.color == 1:
|100| # case 3.1 the uncle node is red
|101| # then set parent and uncle color is black and grandparent is red
|102| # then node => node.parent
|103| node.parent.color = 0
|104| node.parent.parent.right.color = 0
|105| node.parent.parent.color = 1
|106| node = node.parent.parent
|107| continue
|108| elif node is node.parent.right:
|109| # case 3.2 the uncle node is black or null, and the node is right of parent
|110| # then set his parent node is current node
|111| # left rotate the node and continue the next
|112| node = node.parent
|113| self.left_rotate(node)
|114| # case 3.3 the uncle node is black and parent node is left
|115| # then parent node set black and grandparent set red
|116| node.parent.color = 0
|117| node.parent.parent.color = 1
|118| self.right_rotate(node.parent.parent)
|119| else:
|120| uncle_node = node.parent.parent.left
|121| if uncle_node and uncle_node.color == 1:
|122| # case 3.1 the uncle node is red
|123| # then set parent and uncle color is black and grandparent is red
|124| # then node => node.parent
|125| node.parent.color = 0
|126| node.parent.parent.left.color = 0
|127| node.parent.parent.color = 1
|128| node = node.parent.parent
|129| continue
|130| elif node is node.parent.left:
|131| # case 3.2 the uncle node is black or null, and the node is right of parent
|132| # then set his parent node is current node
|133| # left rotate the node and continue the next
|134| node = node.parent
|135| self.right_rotate(node)
|136| # case 3.3 the uncle node is black and parent node is left
|137| # then parent node set black and grandparent set red
|138| node.parent.color = 0
|139| node.parent.parent.color = 1
|140| self.left_rotate(node.parent.parent)
|141| self.root.color = 0
|142|
|143| def transplant(self, node_u, node_v):
|144| """
|145| replace u with v
|146| :param node_u: replaced node
|147| :param node_v:
|148| :return: None
|149| """
|150| if node_u.parent is None:
|151| self.root = node_v
|152| elif node_u is node_u.parent.left:
|153| node_u.parent.left = node_v
|154| elif node_u is node_u.parent.right:
|155| node_u.parent.right = node_v
|156| # check is node_v is None
|157| if node_v:
|158| node_v.parent = node_u.parent
|159|
|160| def maximum(self, node):
|161| """
|162| find the max node when node regard as a root node
|163| :param node:
|164| :return: max node
|165| """
|166| temp_node = node
|167| while temp_node.right is not None:
|168| temp_node = temp_node.right
|169| return temp_node
|170|
|171| def minimum(self, node):
|172| """
|173| find the minimum node when node regard as a root node
|174| :param node:
|175| :return: minimum node
|176| """
|177| temp_node = node
|178| while temp_node.left:
|179| temp_node = temp_node.left
|180| return temp_node
|181|
|182| def delete(self, node):
|183| # find the node position
|184| node_color = node.color
|185| if node.left is None:
|186| temp_node = node.right
|187| self.transplant(node, node.right)
|188| elif node.right is None:
|189| temp_node = node.left
|190| self.transplant(node, node.left)
|191| else:
|192| # both child exits ,and find minimum child of right child
|193| node_min = self.minimum(node.right)
|194| node_color = node_min.color
|195| temp_node = node_min.right
|196| ##
|197| if node_min.parent != node:
|198| self.transplant(node_min, node_min.right)
|199| node_min.right = node.right
|200| node_min.right.parent = node_min
|201| self.transplant(node, node_min)
|202| node_min.left = node.left
|203| node_min.left.parent = node_min
|204| node_min.color = node.color
|205| # when node is black, then need to fix it with 4 cases
|206| if node_color == 0:
|207| self.delete_fixup(temp_node)
|208|
|209| def delete_fixup(self, node):
|210| # 4 cases
|211| while node != self.root and node.color == 0:
|212| # node is not root and color is black
|213| if node == node.parent.left:
|214| # node is left node
|215| node_brother = node.parent.right
|216|
|217| # case 1: node's red, can not get black node
|218| # set brother is black and parent is red
|219| if node_brother.color == 1:
|220| node_brother.color = 0
|221| node.parent.color = 1
|222| self.left_rotate(node.parent)
|223| node_brother = node.parent.right
|224|
|225| # case 2: brother node is black, and its children node is both black
|226| if (node_brother.left is None or node_brother.left.color == 0) and (
|227| node_brother.right is None or node_brother.right.color == 0):
|228| node_brother.color = 1
|229| node = node.parent
|230| else:
|231|
|232| # case 3: brother node is black , and its left child node is red and right is black
|233| if node_brother.right is None or node_brother.right.color == 0:
|234| node_brother.color = 1
|235| node_brother.left.color = 0
|236| self.right_rotate(node_brother)
|237| node_brother = node.parent.right
|238|
|239| # case 4: brother node is black, and right is red, and left is any color
|240| node_brother.color = node.parent.color
|241| node.parent.color = 0
|242| node_brother.right.color = 0
|243| self.left_rotate(node.parent)
|244| node = self.root
|245| break
|246| else:
|247| node_brother = node.parent.left
|248| if node_brother.color == 1:
|249| node_brother.color = 0
|250| node.parent.color = 1
|251| self.left_rotate(node.parent)
|252| node_brother = node.parent.right
|253| if (node_brother.left is None or node_brother.left.color == 0) and (
|254| node_brother.right is None or node_brother.right.color == 0):
|255| node_brother.color = 1
|256| node = node.parent
|257| else:
|258| if node_brother.left is None or node_brother.left.color == 0:
|259| node_brother.color = 1
|260| node_brother.right.color = 0
|261| self.left_rotate(node_brother)
|262| node_brother = node.parent.left
|263| node_brother.color = node.parent.color
|264| node.parent.color = 0
|265| node_brother.left.color = 0
|266| self.right_rotate(node.parent)
|267| node = self.root
|268| break
|269| node.color = 0
|270|
|271| def inorder(self):
|272| res = []
|273| if not self.root:
|274| return res
|275| stack = []
|276| root = self.root
|277| while root or stack:
|278| while root:
|279| stack.append(root)
|280| root = root.left
|281| root = stack.pop()
|282| res.append({'val': root.val, 'color': root.color})
|283| root = root.right
|284| return res
|285|
|286|
|287| if __name__ == "__main__":
|288| rb = RBTree()
|289| children = [11, 2, 14, 1, 7, 15, 5, 8, 4]
|290| for child in children:
|291| node = RBNode(child, 1)
|292| print(child)
|293| rb.insert(node)
|294| print(rb.inorder())
#========================================================================================================================
# :: tree/bst/kth_smallest.py ::
#========================================================================================================================
| 1| class Node:
| 2|
| 3| def __init__(self, val, left=None, right=None):
| 4| self.val = val
| 5| self.left = left
| 6| self.right = right
| 7|
| 8|
| 9| def kth_smallest(root, k):
| 10| stack = []
| 11| while root or stack:
| 12| while root:
| 13| stack.append(root)
| 14| root = root.left
| 15| root = stack.pop()
| 16| k -= 1
| 17| if k == 0:
| 18| break
| 19| root = root.right
| 20| return root.val
| 21|
| 22|
| 23| class Solution(object):
| 24| def kth_smallest(self, root, k):
| 25| """
| 26| :type root: TreeNode
| 27| :type k: int
| 28| :rtype: int
| 29| """
| 30| count = []
| 31| self.helper(root, count)
| 32| return count[k-1]
| 33|
| 34| def helper(self, node, count):
| 35| if not node:
| 36| return
| 37|
| 38| self.helper(node.left, count)
| 39| count.append(node.val)
| 40| self.helper(node.right, count)
| 41|
| 42| if __name__ == '__main__':
| 43| n1 = Node(100)
| 44| n2 = Node(50)
| 45| n3 = Node(150)
| 46| n4 = Node(25)
| 47| n5 = Node(75)
| 48| n6 = Node(125)
| 49| n7 = Node(175)
| 50| n1.left, n1.right = n2, n3
| 51| n2.left, n2.right = n4, n5
| 52| n3.left, n3.right = n6, n7
| 53| print(kth_smallest(n1, 2))
| 54| print(Solution().kth_smallest(n1, 2))
#========================================================================================================================
# :: tree/bst/serialize_deserialize.py ::
#========================================================================================================================
| 1|
| 2|
| 3| class TreeNode(object):
| 4| def __init__(self, x):
| 5| self.val = x
| 6| self.left = None
| 7| self.right = None
| 8|
| 9|
| 10| def serialize(root):
| 11| def build_string(node):
| 12| if node:
| 13| vals.append(str(node.val))
| 14| build_string(node.left)
| 15| build_string(node.right)
| 16| else:
| 17| vals.append("#")
| 18| vals = []
| 19| build_string(root)
| 20| return " ".join(vals)
| 21|
| 22|
| 23| def deserialize(data):
| 24| def build_tree():
| 25| val = next(vals)
| 26| if val == "#":
| 27| return None
| 28| node = TreeNode(int(val))
| 29| node.left = build_tree()
| 30| node.right = build_tree()
| 31| return node
| 32| vals = iter(data.split())
| 33| return build_tree()
#========================================================================================================================
# :: tree/bst/unique_bst.py ::
#========================================================================================================================
| 1| """
| 2| Given n, how many structurally unique BST's
| 3| (binary search trees) that store values 1...n?
| 4|
| 5| For example,
| 6| Given n = 3, there are a total of 5 unique BST's.
| 7|
| 8| 1 3 3 2 1
| 9| \ / / / \ \
| 10| 3 2 1 1 3 2
| 11| / / \ \
| 12| 2 1 2 3
| 13| """
| 14|
| 15|
| 16| """
| 17| Taking 1~n as root respectively:
| 18| 1 as root: # of trees = F(0) * F(n-1) // F(0) == 1
| 19| 2 as root: # of trees = F(1) * F(n-2)
| 20| 3 as root: # of trees = F(2) * F(n-3)
| 21| ...
| 22| n-1 as root: # of trees = F(n-2) * F(1)
| 23| n as root: # of trees = F(n-1) * F(0)
| 24|
| 25| So, the formulation is:
| 26| F(n) = F(0) * F(n-1) + F(1) * F(n-2) + F(2) * F(n-3) + ... + F(n-2) * F(1) + F(n-1) * F(0)
| 27| """
| 28|
| 29| def num_trees(n):
| 30| """
| 31| :type n: int
| 32| :rtype: int
| 33| """
| 34| dp = [0] * (n+1)
| 35| dp[0] = 1
| 36| dp[1] = 1
| 37| for i in range(2, n+1):
| 38| for j in range(i+1):
| 39| dp[i] += dp[i-j] * dp[j-1]
| 40| return dp[-1]
#========================================================================================================================
# :: tree/bst/height.py ::
#========================================================================================================================
| 1| """
| 2| Write a function height returns the height of a tree. The height is defined to
| 3| be the number of levels. The empty tree has height 0, a tree of one node has
| 4| height 1, a root node with one or two leaves as children has height 2, and so on
| 5| For example: height of tree is 4
| 6|
| 7| 9
| 8| / \
| 9| 6 12
| 10| / \ / \
| 11| 3 8 10 15
| 12| / \
| 13| 7 18
| 14|
| 15| height = 4
| 16|
| 17| """
| 18| import unittest
| 19| from bst import Node
| 20| from bst import bst
| 21|
| 22| def height(root):
| 23| if root is None:
| 24| return 0
| 25| else:
| 26| return 1 + max(height(root.left), height(root.right))
| 27|
| 28| """
| 29| The tree is created for testing:
| 30|
| 31| 9
| 32| / \
| 33| 6 12
| 34| / \ / \
| 35| 3 8 10 15
| 36| / \
| 37| 7 18
| 38|
| 39| count_left_node = 4
| 40|
| 41| """
| 42|
| 43| class TestSuite(unittest.TestCase):
| 44| def setUp(self):
| 45| self.tree = bst()
| 46| self.tree.insert(9)
| 47| self.tree.insert(6)
| 48| self.tree.insert(12)
| 49| self.tree.insert(3)
| 50| self.tree.insert(8)
| 51| self.tree.insert(10)
| 52| self.tree.insert(15)
| 53| self.tree.insert(7)
| 54| self.tree.insert(18)
| 55|
| 56| def test_height(self):
| 57| self.assertEqual(4, height(self.tree.root))
| 58|
| 59| if __name__ == '__main__':
| 60| unittest.main()
#========================================================================================================================
# :: tree/bst/bst.py ::
#========================================================================================================================
| 1| """
| 2| Implement Binary Search Tree. It has method:
| 3| 1. Insert
| 4| 2. Search
| 5| 3. Size
| 6| 4. Traversal (Preorder, Inorder, Postorder)
| 7| """
| 8|
| 9| import unittest
| 10|
| 11| class Node(object):
| 12| def __init__(self, data):
| 13| self.data = data
| 14| self.left = None
| 15| self.right = None
| 16|
| 17| class BST(object):
| 18| def __init__(self):
| 19| self.root = None
| 20|
| 21| def get_root(self):
| 22| return self.root
| 23|
| 24| """
| 25| Get the number of elements
| 26| Using recursion. Complexity O(logN)
| 27| """
| 28| def size(self):
| 29| return self.recur_size(self.root)
| 30|
| 31| def recur_size(self, root):
| 32| if root is None:
| 33| return 0
| 34| else:
| 35| return 1 + self.recur_size(root.left) + self.recur_size(root.right)
| 36|
| 37| """
| 38| Search data in bst
| 39| Using recursion. Complexity O(logN)
| 40| """
| 41| def search(self, data):
| 42| return self.recur_search(self.root, data)
| 43|
| 44| def recur_search(self, root, data):
| 45| if root is None:
| 46| return False
| 47| if root.data == data:
| 48| return True
| 49| elif data > root.data: # Go to right root
| 50| return self.recur_search(root.right, data)
| 51| else: # Go to left root
| 52| return self.recur_search(root.left, data)
| 53|
| 54| """
| 55| Insert data in bst
| 56| Using recursion. Complexity O(logN)
| 57| """
| 58| def insert(self, data):
| 59| if self.root:
| 60| return self.recur_insert(self.root, data)
| 61| else:
| 62| self.root = Node(data)
| 63| return True
| 64|
| 65| def recur_insert(self, root, data):
| 66| if root.data == data: # The data is already there
| 67| return False
| 68| elif data < root.data: # Go to left root
| 69| if root.left: # If left root is a node
| 70| return self.recur_insert(root.left, data)
| 71| else: # left root is a None
| 72| root.left = Node(data)
| 73| return True
| 74| else: # Go to right root
| 75| if root.right: # If right root is a node
| 76| return self.recur_insert(root.right, data)
| 77| else:
| 78| root.right = Node(data)
| 79| return True
| 80|
| 81| """
| 82| Preorder, Postorder, Inorder traversal bst
| 83| """
| 84| def preorder(self, root):
| 85| if root:
| 86| print(str(root.data), end = ' ')
| 87| self.preorder(root.left)
| 88| self.preorder(root.right)
| 89|
| 90| def inorder(self, root):
| 91| if root:
| 92| self.inorder(root.left)
| 93| print(str(root.data), end = ' ')
| 94| self.inorder(root.right)
| 95|
| 96| def postorder(self, root):
| 97| if root:
| 98| self.postorder(root.left)
| 99| self.postorder(root.right)
|100| print(str(root.data), end = ' ')
|101|
|102| """
|103| The tree is created for testing:
|104|
|105| 10
|106| / \
|107| 6 15
|108| / \ / \
|109| 4 9 12 24
|110| / / \
|111| 7 20 30
|112| /
|113| 18
|114| """
|115|
|116| class TestSuite(unittest.TestCase):
|117| def setUp(self):
|118| self.tree = BST()
|119| self.tree.insert(10)
|120| self.tree.insert(15)
|121| self.tree.insert(6)
|122| self.tree.insert(4)
|123| self.tree.insert(9)
|124| self.tree.insert(12)
|125| self.tree.insert(24)
|126| self.tree.insert(7)
|127| self.tree.insert(20)
|128| self.tree.insert(30)
|129| self.tree.insert(18)
|130|
|131| def test_search(self):
|132| self.assertTrue(self.tree.search(24))
|133| self.assertFalse(self.tree.search(50))
|134|
|135| def test_size(self):
|136| self.assertEqual(11, self.tree.size())
|137|
|138| if __name__ == '__main__':
|139| unittest.main()
#========================================================================================================================
# :: tree/bst/predecessor.py ::
#========================================================================================================================
| 1| def predecessor(root, node):
| 2| pred = None
| 3| while root:
| 4| if node.val > root.val:
| 5| pred = root
| 6| root = root.right
| 7| else:
| 8| root = root.left
| 9| return pred
#========================================================================================================================
# :: tree/bst/num_empty.py ::
#========================================================================================================================
| 1| """
| 2| Write a function num_empty returns returns the number of empty branches in a
| 3| tree. Function should count the total number of empty branches among the nodes
| 4| of the tree. A leaf node has two empty branches. In the case, if root is None,
| 5| it considered as a 1 empty branch
| 6| For example: the following tree has 10 empty branch (* is empty branch)
| 7|
| 8| 9 __
| 9| / \___
| 10| 6 12
| 11| / \ / \
| 12| 3 8 10 15
| 13| / \ / \ / \ / \
| 14| * * 7 * * * * 18
| 15| / \ / \
| 16| * * * *
| 17|
| 18| empty_branch = 10
| 19|
| 20| """
| 21| import unittest
| 22| from bst import Node
| 23| from bst import bst
| 24|
| 25| def num_empty(root):
| 26| if root is None:
| 27| return 1
| 28| elif root.left is None and root.right:
| 29| return 1 + num_empty(root.right)
| 30| elif root.right is None and root.left:
| 31| return 1 + num_empty(root.left)
| 32| else:
| 33| return num_empty(root.left) + num_empty(root.right)
| 34|
| 35| """
| 36| The tree is created for testing:
| 37|
| 38| 9
| 39| / \
| 40| 6 12
| 41| / \ / \
| 42| 3 8 10 15
| 43| / \
| 44| 7 18
| 45|
| 46| num_empty = 10
| 47|
| 48| """
| 49|
| 50| class TestSuite(unittest.TestCase):
| 51| def setUp(self):
| 52| self.tree = bst()
| 53| self.tree.insert(9)
| 54| self.tree.insert(6)
| 55| self.tree.insert(12)
| 56| self.tree.insert(3)
| 57| self.tree.insert(8)
| 58| self.tree.insert(10)
| 59| self.tree.insert(15)
| 60| self.tree.insert(7)
| 61| self.tree.insert(18)
| 62|
| 63| def test_num_empty(self):
| 64| self.assertEqual(10, num_empty(self.tree.root))
| 65|
| 66| if __name__ == '__main__':
| 67| unittest.main()
#========================================================================================================================
# :: tree/bst/delete_node.py ::
#========================================================================================================================
| 1| """
| 2| Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.
| 3|
| 4| Basically, the deletion can be divided into two stages:
| 5|
| 6| Search for a node to remove.
| 7| If the node is found, delete the node.
| 8| Note: Time complexity should be O(height of tree).
| 9|
| 10| Example:
| 11|
| 12| root = [5,3,6,2,4,null,7]
| 13| key = 3
| 14|
| 15| 5
| 16| / \
| 17| 3 6
| 18| / \ \
| 19| 2 4 7
| 20|
| 21| Given key to delete is 3. So we find the node with value 3 and delete it.
| 22|
| 23| One valid answer is [5,4,6,2,null,null,7], shown in the following BST.
| 24|
| 25| 5
| 26| / \
| 27| 4 6
| 28| / \
| 29| 2 7
| 30|
| 31| Another valid answer is [5,2,6,null,4,null,7].
| 32|
| 33| 5
| 34| / \
| 35| 2 6
| 36| \ \
| 37| 4 7
| 38| """
| 39|
| 40| class Solution(object):
| 41| def delete_node(self, root, key):
| 42| """
| 43| :type root: TreeNode
| 44| :type key: int
| 45| :rtype: TreeNode
| 46| """
| 47| if not root: return None
| 48|
| 49| if root.val == key:
| 50| if root.left:
| 51| # Find the right most leaf of the left sub-tree
| 52| left_right_most = root.left
| 53| while left_right_most.right:
| 54| left_right_most = left_right_most.right
| 55| # Attach right child to the right of that leaf
| 56| left_right_most.right = root.right
| 57| # Return left child instead of root, a.k.a delete root
| 58| return root.left
| 59| else:
| 60| return root.right
| 61| # If left or right child got deleted, the returned root is the child of the deleted node.
| 62| elif root.val > key:
| 63| root.left = self.deleteNode(root.left, key)
| 64| else:
| 65| root.right = self.deleteNode(root.right, key)
| 66| return root
#========================================================================================================================
# :: tree/bst/array_to_bst.py ::
#========================================================================================================================
| 1| """
| 2| Given an array where elements are sorted in ascending order,
| 3| convert it to a height balanced BST.
| 4| """
| 5|
| 6|
| 7| class TreeNode(object):
| 8| def __init__(self, x):
| 9| self.val = x
| 10| self.left = None
| 11| self.right = None
| 12|
| 13|
| 14| def array_to_bst(nums):
| 15| if not nums:
| 16| return None
| 17| mid = len(nums)//2
| 18| node = TreeNode(nums[mid])
| 19| node.left = array_to_bst(nums[:mid])
| 20| node.right = array_to_bst(nums[mid+1:])
| 21| return node
#========================================================================================================================
# :: tree/bst/successor.py ::
#========================================================================================================================
| 1| def successor(root, node):
| 2| succ = None
| 3| while root:
| 4| if node.val < root.val:
| 5| succ = root
| 6| root = root.left
| 7| else:
| 8| root = root.right
| 9| return succ
#========================================================================================================================
# :: tree/bst/depth_sum.py ::
#========================================================================================================================
| 1| """
| 2| Write a function depthSum returns the sum of the values stored
| 3| in a binary search tree of integers weighted by the depth of each value.
| 4|
| 5| For example:
| 6|
| 7| 9
| 8| / \
| 9| 6 12
| 10| / \ / \
| 11| 3 8 10 15
| 12| / \
| 13| 7 18
| 14|
| 15| depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18)
| 16|
| 17| """
| 18| import unittest
| 19| from bst import Node
| 20| from bst import bst
| 21|
| 22| def depth_sum(root, n):
| 23| if root:
| 24| return recur_depth_sum(root, 1)
| 25|
| 26| def recur_depth_sum(root, n):
| 27| if root is None:
| 28| return 0
| 29| elif root.left is None and root.right is None:
| 30| return root.data * n
| 31| else:
| 32| return n * root.data + recur_depth_sum(root.left, n+1) + recur_depth_sum(root.right, n+1)
| 33|
| 34| """
| 35| The tree is created for testing:
| 36|
| 37| 9
| 38| / \
| 39| 6 12
| 40| / \ / \
| 41| 3 8 10 15
| 42| / \
| 43| 7 18
| 44|
| 45| depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18)
| 46|
| 47| """
| 48|
| 49| class TestSuite(unittest.TestCase):
| 50| def setUp(self):
| 51| self.tree = bst()
| 52| self.tree.insert(9)
| 53| self.tree.insert(6)
| 54| self.tree.insert(12)
| 55| self.tree.insert(3)
| 56| self.tree.insert(8)
| 57| self.tree.insert(10)
| 58| self.tree.insert(15)
| 59| self.tree.insert(7)
| 60| self.tree.insert(18)
| 61|
| 62| def test_depth_sum(self):
| 63| self.assertEqual(253, depth_sum(self.tree.root, 4))
| 64|
| 65| if __name__ == '__main__':
| 66| unittest.main()
#========================================================================================================================
# :: tree/bst/bst_closest_value.py ::
#========================================================================================================================
| 1| # Given a non-empty binary search tree and a target value,
| 2| # find the value in the BST that is closest to the target.
| 3|
| 4| # Note:
| 5| # Given target value is a floating point.
| 6| # You are guaranteed to have only one unique value in the BST
| 7| # that is closest to the target.
| 8|
| 9|
| 10| # Definition for a binary tree node.
| 11| # class TreeNode(object):
| 12| # def __init__(self, x):
| 13| # self.val = x
| 14| # self.left = None
| 15| # self.right = None
| 16|
| 17| def closest_value(root, target):
| 18| """
| 19| :type root: TreeNode
| 20| :type target: float
| 21| :rtype: int
| 22| """
| 23| a = root.val
| 24| kid = root.left if target < a else root.right
| 25| if not kid:
| 26| return a
| 27| b = closest_value(kid, target)
| 28| return min((a,b), key=lambda x: abs(target-x))
#========================================================================================================================
# :: tree/bst/lowest_common_ancestor.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary search tree (BST),
| 3| find the lowest common ancestor (LCA) of two given nodes in the BST.
| 4|
| 5| According to the definition of LCA on Wikipedia:
| 6| “The lowest common ancestor is defined between two
| 7| nodes v and w as the lowest node in T that has both v and w
| 8| as descendants (where we allow a node to be a descendant of itself).”
| 9|
| 10| _______6______
| 11| / \
| 12| ___2__ ___8__
| 13| / \ / \
| 14| 0 _4 7 9
| 15| / \
| 16| 3 5
| 17|
| 18| For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6.
| 19| Another example is LCA of nodes 2 and 4 is 2,
| 20| since a node can be a descendant of itself according to the LCA definition.
| 21| """
| 22|
| 23|
| 24| def lowest_common_ancestor(root, p, q):
| 25| """
| 26| :type root: Node
| 27| :type p: Node
| 28| :type q: Node
| 29| :rtype: Node
| 30| """
| 31| while root:
| 32| if p.val > root.val < q.val:
| 33| root = root.right
| 34| elif p.val < root.val > q.val:
| 35| root = root.left
| 36| else:
| 37| return root
#========================================================================================================================
# :: tree/bst/BSTIterator.py ::
#========================================================================================================================
| 1|
| 2| class BSTIterator:
| 3| def __init__(self, root):
| 4| self.stack = []
| 5| while root:
| 6| self.stack.append(root)
| 7| root = root.left
| 8|
| 9| def has_next(self):
| 10| return bool(self.stack)
| 11|
| 12| def next(self):
| 13| node = self.stack.pop()
| 14| tmp = node
| 15| if tmp.right:
| 16| tmp = tmp.right
| 17| while tmp:
| 18| self.stack.append(tmp)
| 19| tmp = tmp.left
| 20| return node.val
| 21|
| 22|
| 23|
| 24|
#========================================================================================================================
# :: tree/bst/is_bst.py ::
#========================================================================================================================
| 1| """
| 2| Given a binary tree, determine if it is a valid binary search tree (BST).
| 3|
| 4| Assume a BST is defined as follows:
| 5|
| 6| The left subtree of a node contains only nodes
| 7| with keys less than the node's key.
| 8| The right subtree of a node contains only nodes
| 9| with keys greater than the node's key.
| 10| Both the left and right subtrees must also be binary search trees.
| 11| Example 1:
| 12| 2
| 13| / \
| 14| 1 3
| 15| Binary tree [2,1,3], return true.
| 16| Example 2:
| 17| 1
| 18| / \
| 19| 2 3
| 20| Binary tree [1,2,3], return false.
| 21| """
| 22|
| 23| def is_bst(root):
| 24| """
| 25| :type root: TreeNode
| 26| :rtype: bool
| 27| """
| 28|
| 29| stack = []
| 30| pre = None
| 31|
| 32| while root or stack:
| 33| while root:
| 34| stack.append(root)
| 35| root = root.left
| 36| root = stack.pop()
| 37| if pre and root.val <= pre.val:
| 38| return False
| 39| pre = root
| 40| root = root.right
| 41|
| 42| return True
#========================================================================================================================
# :: tree/bst/count_left_node.py ::
#========================================================================================================================
| 1| """
| 2| Write a function count_left_node returns the number of left children in the
| 3| tree. For example: the following tree has four left children (the nodes
| 4| storing the values 6, 3, 7, and 10):
| 5|
| 6| 9
| 7| / \
| 8| 6 12
| 9| / \ / \
| 10| 3 8 10 15
| 11| / \
| 12| 7 18
| 13|
| 14| count_left_node = 4
| 15|
| 16| """
| 17| import unittest
| 18| from bst import Node
| 19| from bst import bst
| 20|
| 21| def count_left_node(root):
| 22| if root is None:
| 23| return 0
| 24| elif root.left is None:
| 25| return count_left_node(root.right)
| 26| else:
| 27| return 1 + count_left_node(root.left) + count_left_node(root.right)
| 28|
| 29| """
| 30| The tree is created for testing:
| 31|
| 32| 9
| 33| / \
| 34| 6 12
| 35| / \ / \
| 36| 3 8 10 15
| 37| / \
| 38| 7 18
| 39|
| 40| count_left_node = 4
| 41|
| 42| """
| 43|
| 44| class TestSuite(unittest.TestCase):
| 45| def setUp(self):
| 46| self.tree = bst()
| 47| self.tree.insert(9)
| 48| self.tree.insert(6)
| 49| self.tree.insert(12)
| 50| self.tree.insert(3)
| 51| self.tree.insert(8)
| 52| self.tree.insert(10)
| 53| self.tree.insert(15)
| 54| self.tree.insert(7)
| 55| self.tree.insert(18)
| 56|
| 57| def test_count_left_node(self):
| 58| self.assertEqual(4, count_left_node(self.tree.root))
| 59|
| 60| if __name__ == '__main__':
| 61| unittest.main()
#========================================================================================================================
# :: strings/breaking_bad.py ::
#========================================================================================================================
| 1| """
| 2| Given an api which returns an array of words and an array of symbols, display
| 3| the word with their matched symbol surrounded by square brackets.
| 4|
| 5| If the word string matches more than one symbol, then choose the one with
| 6| longest length. (ex. 'Microsoft' matches 'i' and 'cro'):
| 7|
| 8| Example:
| 9| Words array: ['Amazon', 'Microsoft', 'Google']
| 10| Symbols: ['i', 'Am', 'cro', 'Na', 'le', 'abc']
| 11|
| 12| Output:
| 13| [Am]azon, Mi[cro]soft, Goog[le]
| 14|
| 15| My solution(Wrong):
| 16| (I sorted the symbols array in descending order of length and ran loop over
| 17| words array to find a symbol match(using indexOf in javascript) which
| 18| worked. But I didn't make it through the interview, I am guessing my solution
| 19| was O(n^2) and they expected an efficient algorithm.
| 20|
| 21| output:
| 22| ['[Am]azon', 'Mi[cro]soft', 'Goog[le]', 'Amaz[o]n', 'Micr[o]s[o]ft', 'G[o][o]gle']
| 23| """
| 24|
| 25| from functools import reduce
| 26|
| 27|
| 28| def match_symbol(words, symbols):
| 29| import re
| 30| combined = []
| 31| for s in symbols:
| 32| for c in words:
| 33| r = re.search(s, c)
| 34| if r:
| 35| combined.append(re.sub(s, "[{}]".format(s), c))
| 36| return combined
| 37|
| 38| def match_symbol_1(words, symbols):
| 39| res = []
| 40| # reversely sort the symbols according to their lengths.
| 41| symbols = sorted(symbols, key=lambda _: len(_), reverse=True)
| 42| for word in words:
| 43| for symbol in symbols:
| 44| word_replaced = ''
| 45| # once match, append the `word_replaced` to res, process next word
| 46| if word.find(symbol) != -1:
| 47| word_replaced = word.replace(symbol, '[' + symbol + ']')
| 48| res.append(word_replaced)
| 49| break
| 50| # if this word matches no symbol, append it.
| 51| if word_replaced == '':
| 52| res.append(word)
| 53| return res
| 54|
| 55| """
| 56| Another approach is to use a Tree for the dictionary (the symbols), and then
| 57| match brute force. The complexity will depend on the dictionary;
| 58| if all are suffixes of the other, it will be n*m
| 59| (where m is the size of the dictionary). For example, in Python:
| 60| """
| 61|
| 62|
| 63| class TreeNode:
| 64| def __init__(self):
| 65| self.c = dict()
| 66| self.sym = None
| 67|
| 68|
| 69| def bracket(words, symbols):
| 70| root = TreeNode()
| 71| for s in symbols:
| 72| t = root
| 73| for char in s:
| 74| if char not in t.c:
| 75| t.c[char] = TreeNode()
| 76| t = t.c[char]
| 77| t.sym = s
| 78| result = dict()
| 79| for word in words:
| 80| i = 0
| 81| symlist = list()
| 82| while i < len(word):
| 83| j, t = i, root
| 84| while j < len(word) and word[j] in t.c:
| 85| t = t.c[word[j]]
| 86| if t.sym is not None:
| 87| symlist.append((j + 1 - len(t.sym), j + 1, t.sym))
| 88| j += 1
| 89| i += 1
| 90| if len(symlist) > 0:
| 91| sym = reduce(lambda x, y: x if x[1] - x[0] >= y[1] - y[0] else y,
| 92| symlist)
| 93| result[word] = "{}[{}]{}".format(word[:sym[0]], sym[2],
| 94| word[sym[1]:])
| 95| return tuple(word if word not in result else result[word] for word in words)
#========================================================================================================================
# :: strings/license_number.py ::
#========================================================================================================================
| 1|
| 2| def license_number(key, k):
| 3| res, alnum = [], []
| 4| for char in key:
| 5| if char != "-":
| 6| alnum.append(char)
| 7| for i, char in enumerate(reversed(alnum)):
| 8| res.append(char)
| 9| if (i+1) % k == 0 and i != len(alnum)-1:
| 10| res.append("-")
| 11| return "".join(res[::-1])
#========================================================================================================================
# :: strings/strong_password.py ::
#========================================================================================================================
| 1| """
| 2| The signup page required her to input a name and a password. However, the password
| 3| must be strong. The website considers a password to be strong if it satisfies the following criteria:
| 4|
| 5| 1) Its length is at least 6.
| 6| 2) It contains at least one digit.
| 7| 3) It contains at least one lowercase English character.
| 8| 4) It contains at least one uppercase English character.
| 9| 5) It contains at least one special character. The special characters are: !@#$%^&*()-+
| 10| She typed a random string of length in the password field but wasn't sure if it was strong.
| 11| Given the string she typed, can you find the minimum number of characters she must add to make her password strong?
| 12|
| 13| Note: Here's the set of types of characters in a form you can paste in your solution:
| 14| numbers = "0123456789"
| 15| lower_case = "abcdefghijklmnopqrstuvwxyz"
| 16| upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
| 17| special_characters = "!@#$%^&*()-+"
| 18|
| 19| Input Format
| 20| The first line contains an integer denoting the length of the string.
| 21| The second line contains a string consisting of characters, the password
| 22| typed by Louise. Each character is either a lowercase/uppercase English alphabet, a digit, or a special character.
| 23|
| 24| Sample Input 1: strong_password(3,"Ab1")
| 25| Output: 3 (Because She can make the password strong by adding characters,for example, $hk, turning the password into Ab1$hk which is strong.
| 26| 2 characters aren't enough since the length must be at least 6.)
| 27|
| 28| Sample Output 2: strong_password(11,"#Algorithms")
| 29| Output: 1 (Because the password isn't strong, but she can make it strong by adding a single digit.)
| 30|
| 31| """
| 32| def strong_password(n, password):
| 33| count_error = 0
| 34| # Return the minimum number of characters to make the password strong
| 35| if any(i.isdigit() for i in password) == False:
| 36| count_error = count_error + 1
| 37| if any(i.islower() for i in password) == False:
| 38| count_error = count_error + 1
| 39| if any(i.isupper() for i in password) == False:
| 40| count_error = count_error + 1
| 41| if any(i in '!@#$%^&*()-+' for i in password) == False:
| 42| count_error = count_error + 1
| 43| return max(count_error, 6 - n)
#========================================================================================================================
# :: strings/delete_reoccurring.py ::
#========================================================================================================================
| 1| """
| 2| QUESTION: Given a string as your input, delete any reoccurring
| 3| character, and return the new string.
| 4|
| 5| This is a Google warmup interview question that was asked duirng phone screening
| 6| at my university.
| 7| """
| 8|
| 9| # time complexity O(n)
| 10| def delete_reoccurring_characters(string):
| 11| seen_characters = set()
| 12| output_string = ''
| 13| for char in string:
| 14| if char not in seen_characters:
| 15| seen_characters.add(char)
| 16| output_string += char
| 17| return output_string
| 18|
| 19|
#========================================================================================================================
# :: strings/strip_url_params.py ::
#========================================================================================================================
| 1| """
| 2| Write a function that does the following:
| 3| Removes any duplicate query string parameters from the url
| 4| Removes any query string parameters specified within the 2nd argument (optional array)
| 5|
| 6| An example:
| 7| www.saadbenn.com?a=1&b=2&a=2') // returns 'www.saadbenn.com?a=1&b=2'
| 8| """
| 9| from collections import defaultdict
| 10| import urllib
| 11| import urllib.parse
| 12|
| 13| # Here is a very non-pythonic grotesque solution
| 14| def strip_url_params1(url, params_to_strip=None):
| 15|
| 16| if not params_to_strip:
| 17| params_to_strip = []
| 18| if url:
| 19| result = '' # final result to be returned
| 20| tokens = url.split('?')
| 21| domain = tokens[0]
| 22| query_string = tokens[-1]
| 23| result += domain
| 24| # add the '?' to our result if it is in the url
| 25| if len(tokens) > 1:
| 26| result += '?'
| 27| if not query_string:
| 28| return url
| 29| else:
| 30| # logic for removing duplicate query strings
| 31| # build up the list by splitting the query_string using digits
| 32| key_value_string = []
| 33| string = ''
| 34| for char in query_string:
| 35| if char.isdigit():
| 36| key_value_string.append(string + char)
| 37| string = ''
| 38| else:
| 39| string += char
| 40| dict = defaultdict(int)
| 41| # logic for checking whether we should add the string to our result
| 42| for i in key_value_string:
| 43| _token = i.split('=')
| 44| if _token[0]:
| 45| length = len(_token[0])
| 46| if length == 1:
| 47| if _token and (not(_token[0] in dict)):
| 48| if params_to_strip:
| 49| if _token[0] != params_to_strip[0]:
| 50| dict[_token[0]] = _token[1]
| 51| result = result + _token[0] + '=' + _token[1]
| 52| else:
| 53| if not _token[0] in dict:
| 54| dict[_token[0]] = _token[1]
| 55| result = result + _token[0] + '=' + _token[1]
| 56| else:
| 57| check = _token[0]
| 58| letter = check[1]
| 59| if _token and (not(letter in dict)):
| 60| if params_to_strip:
| 61| if letter != params_to_strip[0]:
| 62| dict[letter] = _token[1]
| 63| result = result + _token[0] + '=' + _token[1]
| 64| else:
| 65| if not letter in dict:
| 66| dict[letter] = _token[1]
| 67| result = result + _token[0] + '=' + _token[1]
| 68| return result
| 69|
| 70| # A very friendly pythonic solution (easy to follow)
| 71| def strip_url_params2(url, param_to_strip=[]):
| 72| if '?' not in url:
| 73| return url
| 74|
| 75| queries = (url.split('?')[1]).split('&')
| 76| queries_obj = [query[0] for query in queries]
| 77| for i in range(len(queries_obj) - 1, 0, -1):
| 78| if queries_obj[i] in param_to_strip or queries_obj[i] in queries_obj[0:i]:
| 79| queries.pop(i)
| 80|
| 81| return url.split('?')[0] + '?' + '&'.join(queries)
| 82|
| 83|
| 84| # Here is my friend's solution using python's builtin libraries
| 85| def strip_url_params3(url, strip=None):
| 86| if not strip: strip = []
| 87|
| 88| parse = urllib.parse.urlparse(url)
| 89| query = urllib.parse.parse_qs(parse.query)
| 90|
| 91| query = {k: v[0] for k, v in query.items() if k not in strip}
| 92| query = urllib.parse.urlencode(query)
| 93| new = parse._replace(query=query)
| 94|
| 95| return new.geturl()
#========================================================================================================================
# :: strings/repeat_substring.py ::
#========================================================================================================================
| 1| """
| 2| Given a non-empty string check if it can be constructed by taking
| 3| a substring of it and appending multiple copies of the substring together.
| 4|
| 5| For example:
| 6| Input: "abab"
| 7| Output: True
| 8| Explanation: It's the substring "ab" twice.
| 9|
| 10| Input: "aba"
| 11| Output: False
| 12|
| 13| Input: "abcabcabcabc"
| 14| Output: True
| 15| Explanation: It's the substring "abc" four times.
| 16|
| 17| Reference: https://leetcode.com/problems/repeated-substring-pattern/description/
| 18| """
| 19| def repeat_substring(s):
| 20| """
| 21| :type s: str
| 22| :rtype: bool
| 23| """
| 24| str = (s + s)[1:-1]
| 25| return s in str
#========================================================================================================================
# :: strings/reverse_string.py ::
#========================================================================================================================
| 1| def recursive(s):
| 2| l = len(s)
| 3| if l < 2:
| 4| return s
| 5| return recursive(s[l//2:]) + recursive(s[:l//2])
| 6|
| 7| def iterative(s):
| 8| r = list(s)
| 9| i, j = 0, len(s) - 1
| 10| while i < j:
| 11| r[i], r[j] = r[j], r[i]
| 12| i += 1
| 13| j -= 1
| 14| return "".join(r)
| 15|
| 16| def pythonic(s):
| 17| r = list(reversed(s))
| 18| return "".join(r)
| 19|
| 20| def ultra_pythonic(s):
| 21| return s[::-1]
#========================================================================================================================
# :: strings/reverse_words.py ::
#========================================================================================================================
| 1|
| 2| def reverse(array, i, j):
| 3| while i < j:
| 4| array[i], array[j] = array[j], array[i]
| 5| i += 1
| 6| j -= 1
| 7|
| 8|
| 9| def reverse_words(string):
| 10| arr = string.strip().split() # arr is list of words
| 11| n = len(arr)
| 12| reverse(arr, 0, n-1)
| 13|
| 14| return " ".join(arr)
| 15|
| 16|
| 17| if __name__ == "__main__":
| 18| test = "I am keon kim and I like pizza"
| 19| print(test)
| 20| print(reverse_words(test))
#========================================================================================================================
# :: strings/roman_to_int.py ::
#========================================================================================================================
| 1| """
| 2| Given a roman numeral, convert it to an integer.
| 3| Input is guaranteed to be within the range from 1 to 3999.
| 4| """
| 5|
| 6|
| 7| def roman_to_int(s:"str")->"int":
| 8| number = 0
| 9| roman = {'M':1000, 'D':500, 'C': 100, 'L':50, 'X':10, 'V':5, 'I':1}
| 10| for i in range(len(s)-1):
| 11| if roman[s[i]] < roman[s[i+1]]:
| 12| number -= roman[s[i]]
| 13| else:
| 14| number += roman[s[i]]
| 15| return number + roman[s[-1]]
| 16|
| 17|
| 18| if __name__ == "__main__":
| 19| roman = "DCXXI"
| 20| print(roman_to_int(roman))
#========================================================================================================================
# :: strings/is_rotated.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings s1 and s2, determine if s2 is a rotated version of s1.
| 3| For example,
| 4| is_rotated("hello", "llohe") returns True
| 5| is_rotated("hello", "helol") returns False
| 6|
| 7| accepts two strings
| 8| returns bool
| 9| Reference: https://leetcode.com/problems/rotate-string/description/
| 10| """
| 11|
| 12| def is_rotated(s1, s2):
| 13| if len(s1) == len(s2):
| 14| return s2 in s1 + s1
| 15| else:
| 16| return False
| 17|
| 18| """
| 19| Another solution: brutal force
| 20| Complexity: O(N^2)
| 21| """
| 22| def is_rotated_v1(s1, s2):
| 23| if len(s1) != len(s2):
| 24| return False
| 25| if len(s1) == 0:
| 26| return True
| 27|
| 28| for c in range(len(s1)):
| 29| if all(s1[(c + i) % len(s1)] == s2[i] for i in range(len(s1))):
| 30| return True
| 31| return False
#========================================================================================================================
# :: strings/caesar_cipher.py ::
#========================================================================================================================
| 1|
| 2| """
| 3| Julius Caesar protected his confidential information by encrypting it using a cipher.
| 4| Caesar's cipher shifts each letter by a number of letters. If the shift takes you
| 5| past the end of the alphabet, just rotate back to the front of the alphabet.
| 6| In the case of a rotation by 3, w, x, y and z would map to z, a, b and c.
| 7| Original alphabet: abcdefghijklmnopqrstuvwxyz
| 8| Alphabet rotated +3: defghijklmnopqrstuvwxyzabc
| 9| """
| 10| def caesar_cipher(s, k):
| 11| result = ""
| 12| for char in s:
| 13| n = ord(char)
| 14| if 64 < n < 91:
| 15| n = ((n - 65 + k) % 26) + 65
| 16| if 96 < n < 123:
| 17| n = ((n - 97 + k) % 26) + 97
| 18| result = result + chr(n)
| 19| return result
#========================================================================================================================
# :: strings/is_palindrome.py ::
#========================================================================================================================
| 1| """
| 2| Given a string, determine if it is a palindrome,
| 3| considering only alphanumeric characters and ignoring cases.
| 4| For example,
| 5| "A man, a plan, a canal: Panama" is a palindrome.
| 6| "race a car" is not a palindrome.
| 7| Note:
| 8| Have you consider that the string might be empty?
| 9| This is a good question to ask during an interview.
| 10| For the purpose of this problem,
| 11| we define empty string as valid palindrome.
| 12| """
| 13| from string import ascii_letters
| 14|
| 15|
| 16| def is_palindrome(s):
| 17| """
| 18| :type s: str
| 19| :rtype: bool
| 20| """
| 21| i = 0
| 22| j = len(s)-1
| 23| while i < j:
| 24| while i < j and not s[i].isalnum():
| 25| i += 1
| 26| while i < j and not s[j].isalnum():
| 27| j -= 1
| 28| if s[i].lower() != s[j].lower():
| 29| return False
| 30| i, j = i+1, j-1
| 31| return True
| 32|
| 33| """
| 34| Here is a bunch of other variations of is_palindrome function.
| 35|
| 36| Variation 1:
| 37| Find the reverse of the string and compare it with the original string
| 38|
| 39| Variation 2:
| 40| Loop from the start to length/2 and check the first character and last character
| 41| and so on... for instance s[0] compared with s[n-1], s[1] == s[n-2]...
| 42|
| 43| Variation 3:
| 44| Using stack idea.
| 45|
| 46| Note: We are assuming that we are just checking a one word string. To check if a complete sentence
| 47| """
| 48| def remove_punctuation(s):
| 49| """
| 50| Remove punctuation, case sensitivity and spaces
| 51| """
| 52| return "".join(i.lower() for i in s if i in ascii_letters)
| 53|
| 54| # Variation 1
| 55| def string_reverse(s):
| 56| return s[::-1]
| 57|
| 58| def is_palindrome_reverse(s):
| 59| s = remove_punctuation(s)
| 60|
| 61| # can also get rid of the string_reverse function and just do this return s == s[::-1] in one line.
| 62| if (s == string_reverse(s)):
| 63| return True
| 64| return False
| 65|
| 66|
| 67| # Variation 2
| 68| def is_palindrome_two_pointer(s):
| 69| s = remove_punctuation(s)
| 70|
| 71| for i in range(0, len(s)//2):
| 72| if (s[i] != s[len(s) - i - 1]):
| 73| return False
| 74| return True
| 75|
| 76|
| 77| # Variation 3
| 78| def is_palindrome_stack(s):
| 79| stack = []
| 80| s = remove_punctuation(s)
| 81|
| 82| for i in range(len(s)//2, len(s)):
| 83| stack.append(s[i])
| 84| for i in range(0, len(s)//2):
| 85| if s[i] != stack.pop():
| 86| return False
| 87| return True
#========================================================================================================================
# :: strings/fizzbuzz.py ::
#========================================================================================================================
| 1| """
| 2| Wtite a function that returns an array containing the numbers from 1 to N,
| 3| where N is the parametered value. N will never be less than 1.
| 4|
| 5| Replace certain values however if any of the following conditions are met:
| 6|
| 7| If the value is a multiple of 3: use the value 'Fizz' instead
| 8| If the value is a multiple of 5: use the value 'Buzz' instead
| 9| If the value is a multiple of 3 & 5: use the value 'FizzBuzz' instead
| 10| """
| 11|
| 12| """
| 13| There is no fancy algorithm to solve fizz buzz.
| 14|
| 15| Iterate from 1 through n
| 16| Use the mod operator to determine if the current iteration is divisible by:
| 17| 3 and 5 -> 'FizzBuzz'
| 18| 3 -> 'Fizz'
| 19| 5 -> 'Buzz'
| 20| else -> string of current iteration
| 21| return the results
| 22| Complexity:
| 23|
| 24| Time: O(n)
| 25| Space: O(n)
| 26| """
| 27|
| 28| def fizzbuzz(n):
| 29|
| 30| # Validate the input
| 31| if n < 1:
| 32| raise ValueError('n cannot be less than one')
| 33| if n is None:
| 34| raise TypeError('n cannot be None')
| 35|
| 36| result = []
| 37|
| 38| for i in range(1, n+1):
| 39| if i%3 == 0 and i%5 == 0:
| 40| result.append('FizzBuzz')
| 41| elif i%3 == 0:
| 42| result.append('Fizz')
| 43| elif i%5 == 0:
| 44| result.append('Buzz')
| 45| else:
| 46| result.append(i)
| 47| return result
| 48|
| 49| # Alternative solution
| 50| def fizzbuzz_with_helper_func(n):
| 51| return [fb(m) for m in range(1,n+1)]
| 52|
| 53| def fb(m):
| 54| r = (m % 3 == 0) * "Fizz" + (m % 5 == 0) * "Buzz"
| 55| return r if r != "" else m
#========================================================================================================================
# :: strings/encode_decode.py ::
#========================================================================================================================
| 1| """ Design an algorithm to encode a list of strings to a string.
| 2| The encoded mystring is then sent over the network and is decoded
| 3| back to the original list of strings.
| 4| """
| 5|
| 6| # Implement the encode and decode methods.
| 7|
| 8| def encode(strs):
| 9| """Encodes a list of strings to a single string.
| 10| :type strs: List[str]
| 11| :rtype: str
| 12| """
| 13| res = ''
| 14| for string in strs.split():
| 15| res += str(len(string)) + ":" + string
| 16| return res
| 17|
| 18| def decode(s):
| 19| """Decodes a single string to a list of strings.
| 20| :type s: str
| 21| :rtype: List[str]
| 22| """
| 23| strs = []
| 24| i = 0
| 25| while i < len(s):
| 26| index = s.find(":", i)
| 27| size = int(s[i:index])
| 28| strs.append(s[index+1: index+1+size])
| 29| i = index+1+size
| 30| return strs
#========================================================================================================================
# :: strings/text_justification.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of words and a width maxWidth, format the text such that each line
| 3| has exactly maxWidth characters and is fully (left and right) justified.
| 4|
| 5| You should pack your words in a greedy approach; that is, pack as many words as
| 6| you can in each line. Pad extra spaces ' ' when necessary so that each line has
| 7| exactly maxWidth characters.
| 8|
| 9| Extra spaces between words should be distributed as evenly as possible. If the
| 10| number of spaces on a line do not divide evenly between words, the empty slots
| 11| on the left will be assigned more spaces than the slots on the right.
| 12|
| 13| For the last line of text, it should be left justified and no extra space is
| 14| inserted between words.
| 15|
| 16| Note:
| 17| A word is defined as a character sequence consisting of non-space characters only.
| 18| Each word's length is guaranteed to be greater than 0 and not exceed maxWidth.
| 19| The input array words contains at least one word.
| 20|
| 21| Example:
| 22| Input:
| 23| words = ["What","must","be","acknowledgment","shall","be"]
| 24| maxWidth = 16
| 25| Output:
| 26| [
| 27| "What must be",
| 28| "acknowledgment ",
| 29| "shall be "
| 30| ]
| 31| """
| 32|
| 33|
| 34| def text_justification(words, max_width):
| 35| '''
| 36| :type words: list
| 37| :type max_width: int
| 38| :rtype: list
| 39| '''
| 40| ret = [] # return value
| 41| row_len = 0 # current length of strs in a row
| 42| row_words = [] # current words in a row
| 43| index = 0 # the index of current word in words
| 44| is_first_word = True # is current word the first in a row
| 45| while index < len(words):
| 46| while row_len <= max_width and index < len(words):
| 47| if len(words[index]) > max_width:
| 48| raise ValueError("there exists word whose length is larger than max_width")
| 49| tmp = row_len
| 50| row_words.append(words[index])
| 51| tmp += len(words[index])
| 52| if not is_first_word:
| 53| tmp += 1 # except for the first word, each word should have at least a ' ' before it.
| 54| if tmp > max_width:
| 55| row_words.pop()
| 56| break
| 57| row_len = tmp
| 58| index += 1
| 59| is_first_word = False
| 60| # here we have already got a row of str , then we should supplement enough ' ' to make sure the length is max_width.
| 61| row = ""
| 62| # if the row is the last
| 63| if index == len(words):
| 64| for word in row_words:
| 65| row += (word + ' ')
| 66| row = row[:-1]
| 67| row += ' ' * (max_width - len(row))
| 68| # not the last row and more than one word
| 69| elif len(row_words) != 1:
| 70| space_num = max_width - row_len
| 71| space_num_of_each_interval = space_num // (len(row_words) - 1)
| 72| space_num_rest = space_num - space_num_of_each_interval * (len(row_words) - 1)
| 73| for j in range(len(row_words)):
| 74| row += row_words[j]
| 75| if j != len(row_words) - 1:
| 76| row += ' ' * (1 + space_num_of_each_interval)
| 77| if space_num_rest > 0:
| 78| row += ' '
| 79| space_num_rest -= 1
| 80| # row with only one word
| 81| else:
| 82| row += row_words[0]
| 83| row += ' ' * (max_width - len(row))
| 84| ret.append(row)
| 85| # after a row , reset those value
| 86| row_len = 0
| 87| row_words = []
| 88| is_first_word = True
| 89| return ret
#========================================================================================================================
# :: strings/domain_extractor.py ::
#========================================================================================================================
| 1| """
| 2| Write a function that when given a URL as a string, parses out just the domain name and returns it as a string.
| 3|
| 4| Examples:
| 5| domain_name("http://github.com/SaadBenn") == "github"
| 6| domain_name("http://www.zombie-bites.com") == "zombie-bites"
| 7| domain_name("https://www.cnet.com") == "cnet"
| 8|
| 9| Note: The idea is not to use any built-in libraries such as re (regular expression) or urlparse except .split() built-in function
| 10| """
| 11|
| 12| # Non pythonic way
| 13| def domain_name_1(url):
| 14| #grab only the non http(s) part
| 15| full_domain_name = url.split('//')[-1]
| 16| #grab the actual one depending on the len of the list
| 17| actual_domain = full_domain_name.split('.')
| 18|
| 19| # case when www is in the url
| 20| if (len(actual_domain) > 2):
| 21| return actual_domain[1]
| 22| # case when www is not in the url
| 23| return actual_domain[0]
| 24|
| 25|
| 26| # pythonic one liner
| 27| def domain_name_2(url):
| 28| return url.split("//")[-1].split("www.")[-1].split(".")[0]
| 29|
#========================================================================================================================
# :: strings/__init__.py ::
#========================================================================================================================
| 1| from .add_binary import *
| 2| from .breaking_bad import *
| 3| from .decode_string import *
| 4| from .delete_reoccurring import *
| 5| from .domain_extractor import *
| 6| from .encode_decode import *
| 7| from .group_anagrams import *
| 8| from .int_to_roman import *
| 9| from .is_palindrome import *
| 10| from .is_rotated import *
| 11| from .license_number import *
| 12| from .make_sentence import *
| 13| from .merge_string_checker import *
| 14| from .multiply_strings import *
| 15| from .one_edit_distance import *
| 16| from .rabin_karp import *
| 17| from .reverse_string import *
| 18| from .reverse_vowel import *
| 19| from .reverse_words import *
| 20| from .roman_to_int import *
| 21| from .strip_url_params import *
| 22| from .validate_coordinates import *
| 23| from .word_squares import *
| 24| from .unique_morse import *
| 25| from .judge_circle import *
| 26| from .strong_password import *
| 27| from .caesar_cipher import *
| 28| from .contain_string import *
| 29| from .count_binary_substring import *
| 30| from .repeat_string import *
| 31| from .text_justification import *
| 32| from .min_distance import *
| 33| from .longest_common_prefix import *
| 34| from .rotate import *
| 35| from .first_unique_char import *
| 36| from .repeat_substring import *
| 37| from .atbash_cipher import *
#========================================================================================================================
# :: strings/judge_circle.py ::
#========================================================================================================================
| 1| """
| 2| Initially, there is a Robot at position (0, 0). Given a sequence of its moves,
| 3| judge if this robot makes a circle, which means it moves back to the original place.
| 4|
| 5| The move sequence is represented by a string. And each move is represent by a
| 6| character. The valid robot moves are R (Right), L (Left), U (Up) and D (down).
| 7| The output should be true or false representing whether the robot makes a circle.
| 8|
| 9| Example 1:
| 10| Input: "UD"
| 11| Output: true
| 12| Example 2:
| 13| Input: "LL"
| 14| Output: false
| 15| """
| 16| def judge_circle(moves):
| 17| dict_moves = {
| 18| 'U' : 0,
| 19| 'D' : 0,
| 20| 'R' : 0,
| 21| 'L' : 0
| 22| }
| 23| for char in moves:
| 24| dict_moves[char] = dict_moves[char] + 1
| 25| return dict_moves['L'] == dict_moves['R'] and dict_moves['U'] == dict_moves['D']
#========================================================================================================================
# :: strings/rabin_karp.py ::
#========================================================================================================================
| 1| # Following program is the python implementation of
| 2| # Rabin Karp Algorithm
| 3|
| 4| class RollingHash:
| 5| def __init__(self, text, size_word):
| 6| self.text = text
| 7| self.hash = 0
| 8| self.size_word = size_word
| 9|
| 10| for i in range(0, size_word):
| 11| #ord maps the character to a number
| 12| #subtract out the ASCII value of "a" to start the indexing at zero
| 13| self.hash += (ord(self.text[i]) - ord("a")+1)*(26**(size_word - i -1))
| 14|
| 15| #start index of current window
| 16| self.window_start = 0
| 17| #end of index window
| 18| self.window_end = size_word
| 19|
| 20| def move_window(self):
| 21| if self.window_end <= len(self.text) - 1:
| 22| #remove left letter from hash value
| 23| self.hash -= (ord(self.text[self.window_start]) - ord("a")+1)*26**(self.size_word-1)
| 24| self.hash *= 26
| 25| self.hash += ord(self.text[self.window_end])- ord("a")+1
| 26| self.window_start += 1
| 27| self.window_end += 1
| 28|
| 29| def window_text(self):
| 30| return self.text[self.window_start:self.window_end]
| 31|
| 32| def rabin_karp(word, text):
| 33| if word == "" or text == "":
| 34| return None
| 35| if len(word) > len(text):
| 36| return None
| 37|
| 38| rolling_hash = RollingHash(text, len(word))
| 39| word_hash = RollingHash(word, len(word))
| 40| #word_hash.move_window()
| 41|
| 42| for i in range(len(text) - len(word) + 1):
| 43| if rolling_hash.hash == word_hash.hash:
| 44| if rolling_hash.window_text() == word:
| 45| return i
| 46| rolling_hash.move_window()
| 47| return None
| 48|
#========================================================================================================================
# :: strings/int_to_roman.py ::
#========================================================================================================================
| 1| """
| 2| Given an integer, convert it to a roman numeral.
| 3| Input is guaranteed to be within the range from 1 to 3999.
| 4| """
| 5|
| 6| def int_to_roman(num):
| 7| """
| 8| :type num: int
| 9| :rtype: str
| 10| """
| 11| m = ["", "M", "MM", "MMM"];
| 12| c = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"];
| 13| x = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
| 14| i = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
| 15| return m[num//1000] + c[(num%1000)//100] + x[(num%100)//10] + i[num%10];
#========================================================================================================================
# :: strings/atbash_cipher.py ::
#========================================================================================================================
| 1| """
| 2| Atbash cipher is mapping the alphabet to it's reverse.
| 3| So if we take "a" as it is the first letter, we change it to the last - z.
| 4|
| 5| Example:
| 6| Attack at dawn --> Zggzxp zg wzdm
| 7|
| 8| Complexity: O(n)
| 9| """
| 10|
| 11| def atbash(s):
| 12| translated = ""
| 13| for i in range(len(s)):
| 14| n = ord(s[i])
| 15|
| 16| if s[i].isalpha():
| 17|
| 18| if s[i].isupper():
| 19| x = n - ord('A')
| 20| translated += chr(ord('Z') - x)
| 21|
| 22| if s[i].islower():
| 23| x = n - ord('a')
| 24| translated += chr(ord('z') - x)
| 25| else:
| 26| translated += s[i]
| 27| return translated
#========================================================================================================================
# :: strings/word_squares.py ::
#========================================================================================================================
| 1| # Given a set of words (without duplicates),
| 2| # find all word squares you can build from them.
| 3|
| 4| # A sequence of words forms a valid word square
| 5| # if the kth row and column read the exact same string,
| 6| # where 0 ≤ k < max(numRows, numColumns).
| 7|
| 8| # For example, the word sequence ["ball","area","lead","lady"] forms
| 9| # a word square because each word reads the same both horizontally
| 10| # and vertically.
| 11|
| 12| # b a l l
| 13| # a r e a
| 14| # l e a d
| 15| # l a d y
| 16| # Note:
| 17| # There are at least 1 and at most 1000 words.
| 18| # All words will have the exact same length.
| 19| # Word length is at least 1 and at most 5.
| 20| # Each word contains only lowercase English alphabet a-z.
| 21|
| 22| # Example 1:
| 23|
| 24| # Input:
| 25| # ["area","lead","wall","lady","ball"]
| 26|
| 27| # Output:
| 28| # [
| 29| # [ "wall",
| 30| # "area",
| 31| # "lead",
| 32| # "lady"
| 33| # ],
| 34| # [ "ball",
| 35| # "area",
| 36| # "lead",
| 37| # "lady"
| 38| # ]
| 39| # ]
| 40|
| 41| # Explanation:
| 42| # The output consists of two word squares. The order of output does not matter
| 43| # (just the order of words in each word square matters).
| 44|
| 45| import collections
| 46|
| 47| def word_squares(words):
| 48| n = len(words[0])
| 49| fulls = collections.defaultdict(list)
| 50| for word in words:
| 51| for i in range(n):
| 52| fulls[word[:i]].append(word)
| 53|
| 54| def build(square):
| 55| if len(square) == n:
| 56| squares.append(square)
| 57| return
| 58| prefix = ""
| 59| for k in range(len(square)):
| 60| prefix += square[k][len(square)]
| 61| for word in fulls[prefix]:
| 62| build(square + [word])
| 63| squares = []
| 64| for word in words:
| 65| build([word])
| 66| return squares
| 67|
#========================================================================================================================
# :: strings/longest_common_prefix.py ::
#========================================================================================================================
| 1| """
| 2| Write a function to find the longest common prefix string amongst an array of strings.
| 3|
| 4| If there is no common prefix, return an empty string "".
| 5|
| 6| Example 1:
| 7| Input: ["flower","flow","flight"]
| 8| Output: "fl"
| 9|
| 10| Example 2:
| 11| Input: ["dog","racecar","car"]
| 12| Output: ""
| 13| Explanation: There is no common prefix among the input strings.
| 14|
| 15| Reference: https://leetcode.com/problems/longest-common-prefix/description/
| 16| """
| 17|
| 18| """
| 19| First solution: Horizontal scanning
| 20| """
| 21| def common_prefix(s1, s2):
| 22| "Return prefix common of 2 strings"
| 23| if not s1 or not s2:
| 24| return ""
| 25| k = 0
| 26| while s1[k] == s2[k]:
| 27| k = k + 1
| 28| if k >= len(s1) or k >= len(s2):
| 29| return s1[0:k]
| 30| return s1[0:k]
| 31|
| 32| def longest_common_prefix_v1(strs):
| 33| if not strs:
| 34| return ""
| 35| result = strs[0]
| 36| for i in range(len(strs)):
| 37| result = common_prefix(result, strs[i])
| 38| return result
| 39|
| 40| """
| 41| Second solution: Vertical scanning
| 42| """
| 43| def longest_common_prefix_v2(strs):
| 44| if not strs:
| 45| return ""
| 46| for i in range(len(strs[0])):
| 47| for string in strs[1:]:
| 48| if i == len(string) or string[i] != strs[0][i]:
| 49| return strs[0][0:i]
| 50| return strs[0]
| 51|
| 52| """
| 53| Third solution: Divide and Conquer
| 54| """
| 55| def longest_common_prefix_v3(strs):
| 56| if not strs:
| 57| return ""
| 58| return longest_common(strs, 0, len(strs) -1)
| 59|
| 60| def longest_common(strs, left, right):
| 61| if left == right:
| 62| return strs[left]
| 63| mid = (left + right) // 2
| 64| lcp_left = longest_common(strs, left, mid)
| 65| lcp_right = longest_common(strs, mid + 1, right)
| 66| return common_prefix(lcp_left, lcp_right)
#========================================================================================================================
# :: strings/validate_coordinates.py ::
#========================================================================================================================
| 1| """"
| 2| Create a function that will validate if given parameters are valid geographical coordinates.
| 3| Valid coordinates look like the following: "23.32353342, -32.543534534". The return value should be either true or false.
| 4| Latitude (which is first float) can be between 0 and 90, positive or negative. Longitude (which is second float) can be between 0 and 180, positive or negative.
| 5| Coordinates can only contain digits, or one of the following symbols (including space after comma) -, .
| 6| There should be no space between the minus "-" sign and the digit after it.
| 7|
| 8| Here are some valid coordinates:
| 9| -23, 25
| 10| 43.91343345, 143
| 11| 4, -3
| 12|
| 13| And some invalid ones:
| 14| 23.234, - 23.4234
| 15| N23.43345, E32.6457
| 16| 6.325624, 43.34345.345
| 17| 0, 1,2
| 18|
| 19| """
| 20| # I'll be adding my attempt as well as my friend's solution (took us ~ 1 hour)
| 21|
| 22| # my attempt
| 23| import re
| 24| def is_valid_coordinates_0(coordinates):
| 25| for char in coordinates:
| 26| if not (char.isdigit() or char in ['-', '.', ',', ' ']):
| 27| return False
| 28| l = coordinates.split(", ")
| 29| if len(l) != 2:
| 30| return False
| 31| try:
| 32| latitude = float(l[0])
| 33| longitude = float(l[1])
| 34| except:
| 35| return False
| 36| return -90 <= latitude <= 90 and -180 <= longitude <= 180
| 37|
| 38| # friends solutions
| 39| def is_valid_coordinates_1(coordinates):
| 40| try:
| 41| lat, lng = [abs(float(c)) for c in coordinates.split(',') if 'e' not in c]
| 42| except ValueError:
| 43| return False
| 44|
| 45| return lat <= 90 and lng <= 180
| 46|
| 47| # using regular expression
| 48| def is_valid_coordinates_regular_expression(coordinates):
| 49| return bool(re.match("-?(\d|[1-8]\d|90)\.?\d*, -?(\d|[1-9]\d|1[0-7]\d|180)\.?\d*$", coordinates))
#========================================================================================================================
# :: strings/make_sentence.py ::
#========================================================================================================================
| 1| """
| 2| For a given string and dictionary, how many sentences can you make from the
| 3| string, such that all the words are contained in the dictionary.
| 4|
| 5| eg: for given string -> "appletablet"
| 6| "apple", "tablet"
| 7| "applet", "able", "t"
| 8| "apple", "table", "t"
| 9| "app", "let", "able", "t"
| 10|
| 11| "applet", {app, let, apple, t, applet} => 3
| 12| "thing", {"thing"} -> 1
| 13| """
| 14|
| 15| count = 0
| 16|
| 17|
| 18| def make_sentence(str_piece, dictionaries):
| 19| global count
| 20| if len(str_piece) == 0:
| 21| return True
| 22| for i in range(0, len(str_piece)):
| 23| prefix, suffix = str_piece[0:i], str_piece[i:]
| 24| if prefix in dictionaries:
| 25| if suffix in dictionaries or make_sentence(suffix, dictionaries):
| 26| count += 1
| 27| return True
#========================================================================================================================
# :: strings/contain_string.py ::
#========================================================================================================================
| 1| """
| 2| Implement strStr().
| 3|
| 4| Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
| 5|
| 6| Example 1:
| 7| Input: haystack = "hello", needle = "ll"
| 8| Output: 2
| 9|
| 10| Example 2:
| 11| Input: haystack = "aaaaa", needle = "bba"
| 12| Output: -1
| 13| Reference: https://leetcode.com/problems/implement-strstr/description/
| 14| """
| 15| def contain_string(haystack, needle):
| 16| if len(needle) == 0:
| 17| return 0
| 18| if len(needle) > len(haystack):
| 19| return -1
| 20| for i in range(len(haystack)):
| 21| if len(haystack) - i < len(needle):
| 22| return -1
| 23| if haystack[i:i+len(needle)] == needle:
| 24| return i
| 25| return -1
#========================================================================================================================
# :: strings/count_binary_substring.py ::
#========================================================================================================================
| 1| """
| 2| Give a string s, count the number of non-empty (contiguous) substrings that have
| 3| the same number of 0's and 1's, and all the 0's and all the 1's in these substrings are grouped consecutively.
| 4|
| 5| Substrings that occur multiple times are counted the number of times they occur.
| 6| Example 1:
| 7| Input: "00110011"
| 8| Output: 6
| 9| Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
| 10|
| 11| Notice that some of these substrings repeat and are counted the number of times they occur.
| 12|
| 13| Also, "00110011" is not a valid substring because all the 0's (and 1's) are not grouped together.
| 14|
| 15| Example 2:
| 16| Input: "10101"
| 17| Output: 4
| 18| Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's.
| 19| Reference: https://leetcode.com/problems/count-binary-substrings/description/
| 20| """
| 21| def count_binary_substring(s):
| 22| cur = 1
| 23| pre = 0
| 24| count = 0
| 25| for i in range(1, len(s)):
| 26| if s[i] != s[i - 1]:
| 27| count = count + min(pre, cur)
| 28| pre = cur
| 29| cur = 1
| 30| else:
| 31| cur = cur + 1
| 32| count = count + min(pre, cur)
| 33| return count
#========================================================================================================================
# :: strings/first_unique_char.py ::
#========================================================================================================================
| 1| """
| 2| Given a string, find the first non-repeating character in it and return it's
| 3| index. If it doesn't exist, return -1.
| 4|
| 5| For example:
| 6| s = "leetcode"
| 7| return 0.
| 8|
| 9| s = "loveleetcode",
| 10| return 2.
| 11|
| 12| Reference: https://leetcode.com/problems/first-unique-character-in-a-string/description/
| 13| """
| 14| def first_unique_char(s):
| 15| """
| 16| :type s: str
| 17| :rtype: int
| 18| """
| 19| if (len(s) == 1):
| 20| return 0
| 21| ban = []
| 22| for i in range(len(s)):
| 23| if all(s[i] != s[k] for k in range(i + 1, len(s))) == True and s[i] not in ban:
| 24| return i
| 25| else:
| 26| ban.append(s[i])
| 27| return -1
#========================================================================================================================
# :: strings/repeat_string.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1.
| 3|
| 4| For example, with A = "abcd" and B = "cdabcdab".
| 5|
| 6| Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times ("abcdabcd").
| 7|
| 8| Note:
| 9| The length of A and B will be between 1 and 10000.
| 10|
| 11| Reference: https://leetcode.com/problems/repeated-string-match/description/
| 12| """
| 13| def repeat_string(A, B):
| 14| count = 1
| 15| tmp = A
| 16| max_count = (len(B) / len(A)) + 1
| 17| while not(B in tmp):
| 18| tmp = tmp + A
| 19| if (count > max_count):
| 20| count = -1
| 21| break
| 22| count = count + 1
| 23|
| 24| return count
#========================================================================================================================
# :: strings/multiply_strings.py ::
#========================================================================================================================
| 1| """
| 2| Given two non-negative integers num1 and num2 represented as strings,
| 3| return the product of num1 and num2.
| 4|
| 5| Note:
| 6|
| 7| The length of both num1 and num2 is < 110.
| 8| Both num1 and num2 contains only digits 0-9.
| 9| Both num1 and num2 does not contain any leading zero.
| 10| You must not use any built-in BigInteger library or convert
| 11| the inputs to integer directly.
| 12| """
| 13|
| 14|
| 15| def multiply(num1:"str", num2:"str")->"str":
| 16| carry = 1
| 17| interm = []
| 18| zero = ord('0')
| 19| i_pos = 1
| 20| for i in reversed(num1):
| 21| j_pos = 1
| 22| add = 0
| 23| for j in reversed(num2):
| 24| mult = (ord(i)-zero) * (ord(j)-zero) * j_pos * i_pos
| 25| j_pos *= 10
| 26| add += mult
| 27| i_pos *= 10
| 28| interm.append(add)
| 29| return str(sum(interm))
| 30|
| 31|
| 32| if __name__ == "__main__":
| 33| print(multiply("1", "23"))
| 34| print(multiply("23", "23"))
| 35| print(multiply("100", "23"))
| 36| print(multiply("100", "10000"))
#========================================================================================================================
# :: strings/unique_morse.py ::
#========================================================================================================================
| 1| """
| 2| International Morse Code defines a standard encoding where each letter is mapped to
| 3| a series of dots and dashes, as follows: "a" maps to ".-", "b" maps to "-...", "c"
| 4| maps to "-.-.", and so on.
| 5|
| 6| For convenience, the full table for the 26 letters of the English alphabet is given below:
| 7| 'a':".-",
| 8| 'b':"-...",
| 9| 'c':"-.-.",
| 10| 'd': "-..",
| 11| 'e':".",
| 12| 'f':"..-.",
| 13| 'g':"--.",
| 14| 'h':"....",
| 15| 'i':"..",
| 16| 'j':".---",
| 17| 'k':"-.-",
| 18| 'l':".-..",
| 19| 'm':"--",
| 20| 'n':"-.",
| 21| 'o':"---",
| 22| 'p':".--.",
| 23| 'q':"--.-",
| 24| 'r':".-.",
| 25| 's':"...",
| 26| 't':"-",
| 27| 'u':"..-",
| 28| 'v':"...-",
| 29| 'w':".--",
| 30| 'x':"-..-",
| 31| 'y':"-.--",
| 32| 'z':"--.."
| 33|
| 34| Now, given a list of words, each word can be written as a concatenation of the
| 35| Morse code of each letter. For example, "cab" can be written as "-.-.-....-",
| 36| (which is the concatenation "-.-." + "-..." + ".-"). We'll call such a
| 37| concatenation, the transformation of a word.
| 38|
| 39| Return the number of different transformations among all words we have.
| 40| Example:
| 41| Input: words = ["gin", "zen", "gig", "msg"]
| 42| Output: 2
| 43| Explanation:
| 44| The transformation of each word is:
| 45| "gin" -> "--...-."
| 46| "zen" -> "--...-."
| 47| "gig" -> "--...--."
| 48| "msg" -> "--...--."
| 49|
| 50| There are 2 different transformations, "--...-." and "--...--.".
| 51| """
| 52|
| 53| morse_code = {
| 54| 'a':".-",
| 55| 'b':"-...",
| 56| 'c':"-.-.",
| 57| 'd': "-..",
| 58| 'e':".",
| 59| 'f':"..-.",
| 60| 'g':"--.",
| 61| 'h':"....",
| 62| 'i':"..",
| 63| 'j':".---",
| 64| 'k':"-.-",
| 65| 'l':".-..",
| 66| 'm':"--",
| 67| 'n':"-.",
| 68| 'o':"---",
| 69| 'p':".--.",
| 70| 'q':"--.-",
| 71| 'r':".-.",
| 72| 's':"...",
| 73| 't':"-",
| 74| 'u':"..-",
| 75| 'v':"...-",
| 76| 'w':".--",
| 77| 'x':"-..-",
| 78| 'y':"-.--",
| 79| 'z':"--.."
| 80| }
| 81| def convert_morse_word(word):
| 82| morse_word = ""
| 83| word = word.lower()
| 84| for char in word:
| 85| morse_word = morse_word + morse_code[char]
| 86| return morse_word
| 87|
| 88| def unique_morse(words):
| 89| unique_morse_word = []
| 90| for word in words:
| 91| morse_word = convert_morse_word(word)
| 92| if morse_word not in unique_morse_word:
| 93| unique_morse_word.append(morse_word)
| 94| return len(unique_morse_word)
#========================================================================================================================
# :: strings/one_edit_distance.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings S and T, determine if they are both one edit distance apart.
| 3| """
| 4|
| 5|
| 6| def is_one_edit(s, t):
| 7| """
| 8| :type s: str
| 9| :type t: str
| 10| :rtype: bool
| 11| """
| 12| if len(s) > len(t):
| 13| return is_one_edit(t, s)
| 14| if len(t) - len(s) > 1 or t == s:
| 15| return False
| 16| for i in range(len(s)):
| 17| if s[i] != t[i]:
| 18| return s[i+1:] == t[i+1:] or s[i:] == t[i+1:]
| 19| return True
| 20|
| 21|
| 22| def is_one_edit2(s, t):
| 23| l1, l2 = len(s), len(t)
| 24| if l1 > l2:
| 25| return is_one_edit2(t, s)
| 26| if len(t) - len(s) > 1 or t == s:
| 27| return False
| 28| for i in range(len(s)):
| 29| if s[i] != t[i]:
| 30| if l1 == l2:
| 31| s = s[:i]+t[i]+s[i+1:] # modify
| 32| else:
| 33| s = s[:i]+t[i]+s[i:] # insertion
| 34| break
| 35| return s == t or s == t[:-1]
#========================================================================================================================
# :: strings/group_anagrams.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of strings, group anagrams together.
| 3|
| 4| For example, given: ["eat", "tea", "tan", "ate", "nat", "bat"],
| 5| Return:
| 6|
| 7| [
| 8| ["ate", "eat","tea"],
| 9| ["nat","tan"],
| 10| ["bat"]
| 11| ]
| 12| """
| 13|
| 14|
| 15| def group_anagrams(strs):
| 16| d = {}
| 17| ans = []
| 18| k = 0
| 19| for str in strs:
| 20| sstr = ''.join(sorted(str))
| 21| if sstr not in d:
| 22| d[sstr] = k
| 23| k += 1
| 24| ans.append([])
| 25| ans[-1].append(str)
| 26| else:
| 27| ans[d[sstr]].append(str)
| 28| return ans
#========================================================================================================================
# :: strings/rotate.py ::
#========================================================================================================================
| 1| """
| 2| Given a strings s and int k, return a string that rotates k times
| 3|
| 4| For example,
| 5| rotate("hello", 2) return "llohe"
| 6| rotate("hello", 5) return "hello"
| 7| rotate("hello", 6) return "elloh"
| 8| rotate("hello", 7) return "llohe"
| 9|
| 10| accepts two strings
| 11| returns bool
| 12| """
| 13| def rotate(s, k):
| 14| double_s = s + s
| 15| if k <= len(s):
| 16| return double_s[k:k + len(s)]
| 17| else:
| 18| return double_s[k-len(s):k]
#========================================================================================================================
# :: strings/decode_string.py ::
#========================================================================================================================
| 1| # Given an encoded string, return it's decoded string.
| 2|
| 3| # The encoding rule is: k[encoded_string], where the encoded_string
| 4| # inside the square brackets is being repeated exactly k times.
| 5| # Note that k is guaranteed to be a positive integer.
| 6|
| 7| # You may assume that the input string is always valid; No extra white spaces,
| 8| # square brackets are well-formed, etc.
| 9|
| 10| # Furthermore, you may assume that the original data does not contain any
| 11| # digits and that digits are only for those repeat numbers, k.
| 12| # For example, there won't be input like 3a or 2[4].
| 13|
| 14| # Examples:
| 15|
| 16| # s = "3[a]2[bc]", return "aaabcbc".
| 17| # s = "3[a2[c]]", return "accaccacc".
| 18| # s = "2[abc]3[cd]ef", return "abcabccdcdcdef".
| 19|
| 20| def decode_string(s):
| 21| """
| 22| :type s: str
| 23| :rtype: str
| 24| """
| 25| stack = []; cur_num = 0; cur_string = ''
| 26| for c in s:
| 27| if c == '[':
| 28| stack.append((cur_string, cur_num))
| 29| cur_string = ''
| 30| cur_num = 0
| 31| elif c == ']':
| 32| prev_string, num = stack.pop()
| 33| cur_string = prev_string + num * cur_string
| 34| elif c.isdigit():
| 35| cur_num = cur_num*10 + int(c)
| 36| else:
| 37| cur_string += c
| 38| return cur_string
#========================================================================================================================
# :: strings/merge_string_checker.py ::
#========================================================================================================================
| 1| """
| 2| At a job interview, you are challenged to write an algorithm to check if a
| 3| given string, s, can be formed from two other strings, part1 and part2.
| 4| The restriction is that the characters in part1 and part2 are in the same
| 5| order as in s. The interviewer gives you the following example and tells
| 6| you to figure out the rest from the given test cases.
| 7| 'codewars' is a merge from 'cdw' and 'oears':
| 8| s: c o d e w a r s = codewars
| 9| part1: c d w = cdw
| 10| part2: o e a r s = oears
| 11| """
| 12|
| 13|
| 14| # Recursive Solution
| 15| def is_merge_recursive(s, part1, part2):
| 16| if not part1:
| 17| return s == part2
| 18| if not part2:
| 19| return s == part1
| 20| if not s:
| 21| return part1 + part2 == ''
| 22| if s[0] == part1[0] and is_merge_recursive(s[1:], part1[1:], part2):
| 23| return True
| 24| if s[0] == part2[0] and is_merge_recursive(s[1:], part1, part2[1:]):
| 25| return True
| 26| return False
| 27|
| 28|
| 29| # An iterative approach
| 30| def is_merge_iterative(s, part1, part2):
| 31| tuple_list = [(s, part1, part2)]
| 32| while tuple_list:
| 33| string, p1, p2 = tuple_list.pop()
| 34| if string:
| 35| if p1 and string[0] == p1[0]:
| 36| tuple_list.append((string[1:], p1[1:], p2))
| 37| if p2 and string[0] == p2[0]:
| 38| tuple_list.append((string[1:], p1, p2[1:]))
| 39| else:
| 40| if not p1 and not p2:
| 41| return True
| 42| return False
#========================================================================================================================
# :: strings/add_binary.py ::
#========================================================================================================================
| 1| """
| 2| Given two binary strings,
| 3| return their sum (also a binary string).
| 4|
| 5| For example,
| 6| a = "11"
| 7| b = "1"
| 8| Return "100".
| 9| """
| 10|
| 11|
| 12| def add_binary(a, b):
| 13| s = ""
| 14| c, i, j = 0, len(a)-1, len(b)-1
| 15| zero = ord('0')
| 16| while (i >= 0 or j >= 0 or c == 1):
| 17| if (i >= 0):
| 18| c += ord(a[i]) - zero
| 19| i -= 1
| 20| if (j >= 0):
| 21| c += ord(b[j]) - zero
| 22| j -= 1
| 23| s = chr(c % 2 + zero) + s
| 24| c //= 2
| 25|
| 26| return s
#========================================================================================================================
# :: strings/min_distance.py ::
#========================================================================================================================
| 1| """
| 2| Given two words word1 and word2, find the minimum number of steps required to
| 3| make word1 and word2 the same, where in each step you can delete one character
| 4| in either string.
| 5|
| 6| For example:
| 7| Input: "sea", "eat"
| 8| Output: 2
| 9| Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".
| 10|
| 11| Reference: https://leetcode.com/problems/delete-operation-for-two-strings/description/
| 12| """
| 13|
| 14| def min_distance(word1, word2):
| 15| return len(word1) + len(word2) - 2 * lcs(word1, word2, len(word1), len(word2))
| 16|
| 17| def lcs(s1, s2, i, j):
| 18| """
| 19| The length of longest common subsequence among the two given strings s1 and s2
| 20| """
| 21| if i == 0 or j == 0:
| 22| return 0
| 23| elif s1[i - 1] == s2[j - 1]:
| 24| return 1 + lcs(s1, s2, i - 1, j - 1)
| 25| else:
| 26| return max(lcs(s1, s2, i - 1, j), lcs(s1, s2, i, j - 1))
| 27|
| 28| # TODO: Using dynamic programming
#========================================================================================================================
# :: strings/reverse_vowel.py ::
#========================================================================================================================
| 1|
| 2| def reverse_vowel(s):
| 3| vowels = "AEIOUaeiou"
| 4| i, j = 0, len(s)-1
| 5| s = list(s)
| 6| while i < j:
| 7| while i < j and s[i] not in vowels:
| 8| i += 1
| 9| while i < j and s[j] not in vowels:
| 10| j -= 1
| 11| s[i], s[j] = s[j], s[i]
| 12| i, j = i + 1, j - 1
| 13| return "".join(s)
#========================================================================================================================
# :: bit/flip_bit_longest_sequence.py ::
#========================================================================================================================
| 1| """
| 2| You have an integer and you can flip exactly one bit from a 0 to 1.
| 3| Write code to find the length of the longest sequence of 1s you could create.
| 4| For example:
| 5| Input: 1775 ( or: 11011101111)
| 6| Output: 8
| 7| """
| 8|
| 9|
| 10| def flip_bit_longest_seq(num):
| 11|
| 12| curr_len = 0
| 13| prev_len = 0
| 14| max_len = 0
| 15|
| 16| while num:
| 17| if num & 1 == 1: # last digit is 1
| 18| curr_len += 1
| 19|
| 20| elif num & 1 == 0: # last digit is 0
| 21| if num & 2 == 0: # second last digit is 0
| 22| prev_len = 0
| 23| else:
| 24| prev_len = curr_len
| 25| curr_len = 0
| 26|
| 27| max_len = max(max_len, prev_len + curr_len)
| 28| num = num >> 1 # right shift num
| 29|
| 30| return max_len + 1
#========================================================================================================================
# :: bit/count_ones.py ::
#========================================================================================================================
| 1| """
| 2| Write a function that takes an unsigned integer and
| 3| returns the number of '1' bits it has
| 4| (also known as the Hamming weight).
| 5|
| 6| For example, the 32-bit integer '11' has binary
| 7| representation 00000000000000000000000000001011,
| 8| so the function should return 3.
| 9|
| 10| T(n)- O(k) : k is the number of 1s present in binary representation.
| 11| NOTE: this complexity is better than O(log n).
| 12| e.g. for n = 00010100000000000000000000000000
| 13| only 2 iterations are required.
| 14|
| 15| Number of loops is
| 16| equal to the number of 1s in the binary representation."""
| 17| def count_ones_recur(n):
| 18| """Using Brian Kernighan's Algorithm. (Recursive Approach)"""
| 19|
| 20| if not n:
| 21| return 0
| 22| return 1 + count_ones_recur(n & (n-1))
| 23|
| 24|
| 25| def count_ones_iter(n):
| 26| """Using Brian Kernighan's Algorithm. (Iterative Approach)"""
| 27|
| 28| count = 0
| 29| while n:
| 30| n &= (n-1)
| 31| count += 1
| 32| return count
#========================================================================================================================
# :: bit/binary_gap.py ::
#========================================================================================================================
| 1| """
| 2| Given a positive integer N, find and return the longest distance between two
| 3| consecutive 1' in the binary representation of N.
| 4| If there are not two consecutive 1's, return 0
| 5|
| 6| For example:
| 7| Input: 22
| 8| Output: 2
| 9| Explanation:
| 10| 22 in binary is 10110
| 11| In the binary representation of 22, there are three ones, and two consecutive pairs of 1's.
| 12| The first consecutive pair of 1's have distance 2.
| 13| The second consecutive pair of 1's have distance 1.
| 14| The answer is the largest of these two distances, which is 2
| 15| """
| 16|
| 17|
| 18| # 原方法为 binary_gap,但通过实验发现算法有误,不论是什么数,输出值最多为2。
| 19| # 改进方法为binary_gap_improved。
| 20| # The original method is binary_gap,
| 21| # but the experimental results show that the algorithm seems to be wrong,
| 22| # regardless of the number, the output value is up to 2.
| 23| # The improved method is binary_gap_improved.
| 24| def binary_gap(N):
| 25| last = None
| 26| ans = 0
| 27| index = 0
| 28| while N != 0:
| 29| if N & 1:
| 30| if last is not None:
| 31| ans = max(ans, index - last)
| 32| last = index
| 33| index = index + 1
| 34| N = N >> 1
| 35| return ans
| 36|
| 37|
| 38| def binary_gap_improved(N):
| 39| last = None
| 40| ans = 0
| 41| index = 0
| 42| while N != 0:
| 43| tes = N & 1
| 44| if tes:
| 45| if last is not None:
| 46| ans = max(ans, index - last + 1)
| 47| else:
| 48| last = index
| 49| else:
| 50| last = index + 1
| 51| index = index + 1
| 52| N = N >> 1
| 53| return ans
| 54|
| 55|
| 56| print(binary_gap(111))
| 57| print(binary_gap_improved(111))
#========================================================================================================================
# :: bit/remove_bit.py ::
#========================================================================================================================
| 1| """
| 2| Remove_bit(num, i): remove a bit at specific position.
| 3| For example:
| 4|
| 5| Input: num = 10101 (21)
| 6| remove_bit(num, 2): output = 1001 (9)
| 7| remove_bit(num, 4): output = 101 (5)
| 8| remove_bit(num, 0): output = 1010 (10)
| 9| """
| 10|
| 11| def remove_bit(num, i):
| 12| mask = num >> (i + 1)
| 13| mask = mask << i
| 14| right = ((1 << i) - 1) & num
| 15| return mask | right
#========================================================================================================================
# :: bit/reverse_bits.py ::
#========================================================================================================================
| 1| """
| 2| Reverse bits of a given 32 bits unsigned integer.
| 3|
| 4| For example, given input 43261596
| 5| (represented in binary as 00000010100101000001111010011100),
| 6| return 964176192
| 7| (represented in binary as 00111001011110000010100101000000).
| 8| """
| 9| def reverse_bits(n):
| 10| m = 0
| 11| i = 0
| 12| while i < 32:
| 13| m = (m << 1) + (n & 1)
| 14| n >>= 1
| 15| i += 1
| 16| return m
#========================================================================================================================
# :: bit/swap_pair.py ::
#========================================================================================================================
| 1| """
| 2| Swap_pair: A function swap odd and even bits in an integer with as few instructions
| 3| as possible (Ex bit and bit 1 are swapped, bit 2 and bit 3 are swapped)
| 4|
| 5| For example:
| 6| 22: 010110 --> 41: 101001
| 7| 10: 1010 --> 5 : 0101
| 8| """
| 9|
| 10| """
| 11| We can approach this as operating on the odds bit first, and then the even bits.
| 12| We can mask all odd bits with 10101010 in binary ('AA') then shift them right by 1
| 13| Similarly, we mask all even bit with 01010101 in binary ('55') then shift them left
| 14| by 1. Finally, we merge these two values by OR operation.
| 15| """
| 16| def swap_pair(num):
| 17| # odd bit arithmetic right shift 1 bit
| 18| odd = (num & int('AAAAAAAA', 16)) >> 1
| 19| # even bit left shift 1 bit
| 20| even = (num & int('55555555', 16)) << 1
| 21| return odd | even
#========================================================================================================================
# :: bit/bit_operation.py ::
#========================================================================================================================
| 1| """
| 2| Fundamental bit operation:
| 3| get_bit(num, i): get an exact bit at specific index
| 4| set_bit(num, i): set a bit at specific index
| 5| clear_bit(num, i): clear a bit at specific index
| 6| update_bit(num, i, bit): update a bit at specific index
| 7| """
| 8|
| 9| """
| 10| This function shifts 1 over by i bits, creating a value being like 0001000. By
| 11| performing an AND with num, we clear all bits other than the bit at bit i.
| 12| Finally we compare that to 0
| 13| """
| 14| def get_bit(num, i):
| 15| return (num & (1 << i)) != 0
| 16|
| 17| """
| 18| This function shifts 1 over by i bits, creating a value being like 0001000. By
| 19| performing an OR with num, only value at bit i will change.
| 20| """
| 21| def set_bit(num, i):
| 22| return num | (1 << i)
| 23|
| 24| """
| 25| This method operates in almost the reverse of set_bit
| 26| """
| 27| def clear_bit(num, i):
| 28| mask = ~(1 << i)
| 29| return num & mask
| 30|
| 31| """
| 32| To set the ith bit to value, we first clear the bit at position i by using a
| 33| mask. Then, we shift the intended value. Finally we OR these two numbers
| 34| """
| 35| def update_bit(num, i, bit):
| 36| mask = ~(1 << i)
| 37| return (num & mask) | (bit << i)
#========================================================================================================================
# :: bit/single_number3.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of numbers nums,
| 3| in which exactly two elements appear only once
| 4| and all the other elements appear exactly twice.
| 5| Find the two elements that appear only once.
| 6| Limitation: Time Complexity: O(N) and Space Complexity O(1)
| 7|
| 8| For example:
| 9|
| 10| Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].
| 11|
| 12| Note:
| 13| The order of the result is not important.
| 14| So in the above example, [5, 3] is also correct.
| 15|
| 16|
| 17| Solution:
| 18| 1. Use XOR to cancel out the pairs and isolate A^B
| 19| 2. It is guaranteed that at least 1 bit exists in A^B since
| 20| A and B are different numbers. ex) 010 ^ 111 = 101
| 21| 3. Single out one bit R (right most bit in this solution) to use it as a pivot
| 22| 4. Divide all numbers into two groups.
| 23| One group with a bit in the position R
| 24| One group without a bit in the position R
| 25| 5. Use the same strategy we used in step 1 to isolate A and B from each group.
| 26| """
| 27|
| 28|
| 29| def single_number3(nums):
| 30| """
| 31| :type nums: List[int]
| 32| :rtype: List[int]
| 33| """
| 34| # isolate a^b from pairs using XOR
| 35| ab = 0
| 36| for n in nums:
| 37| ab ^= n
| 38|
| 39| # isolate right most bit from a^b
| 40| right_most = ab & (-ab)
| 41|
| 42| # isolate a and b from a^b
| 43| a, b = 0, 0
| 44| for n in nums:
| 45| if n & right_most:
| 46| a ^= n
| 47| else:
| 48| b ^= n
| 49| return [a, b]
#========================================================================================================================
# :: bit/single_number2.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of integers, every element appears
| 3| three times except for one, which appears exactly once.
| 4| Find that single one.
| 5|
| 6| Note:
| 7| Your algorithm should have a linear runtime complexity.
| 8| Could you implement it without using extra memory?
| 9|
| 10|
| 11| Solution:
| 12| 32 bits for each integer.
| 13| Consider 1 bit in it, the sum of each integer's corresponding bit
| 14| (except for the single number)
| 15| should be 0 if mod by 3. Hence, we sum the bits of all
| 16| integers and mod by 3,
| 17| the remaining should be the exact bit of the single number.
| 18| In this way, you get the 32 bits of the single number.
| 19| """
| 20|
| 21| # Another awesome answer
| 22| def single_number2(nums):
| 23| ones, twos = 0, 0
| 24| for i in range(len(nums)):
| 25| ones = (ones ^ nums[i]) & ~twos
| 26| twos = (twos ^ nums[i]) & ~ones
| 27| return ones
#========================================================================================================================
# :: bit/__init__.py ::
#========================================================================================================================
| 1| from .add_bitwise_operator import *
| 2| from .count_ones import *
| 3| from .find_missing_number import *
| 4| from .power_of_two import *
| 5| from .reverse_bits import *
| 6| from .single_number import *
| 7| from .single_number2 import *
| 8| from .single_number3 import *
| 9| from .subsets import *
| 10| from .bit_operation import *
| 11| from .swap_pair import *
| 12| from .find_difference import *
| 13| from .has_alternative_bit import *
| 14| from .insert_bit import *
| 15| from .remove_bit import *
| 16| from .count_flips_to_convert import *
| 17| from .flip_bit_longest_sequence import *
| 18| from .binary_gap import *
| 19| from .bytes_int_conversion import *
#========================================================================================================================
# :: bit/insert_bit.py ::
#========================================================================================================================
| 1| """
| 2| Insertion:
| 3|
| 4| insert_one_bit(num, bit, i): insert exact one bit at specific position
| 5| For example:
| 6|
| 7| Input: num = 10101 (21)
| 8| insert_one_bit(num, 1, 2): 101101 (45)
| 9| insert_one_bit(num, 0, 2): 101001 (41)
| 10| insert_one_bit(num, 1, 5): 110101 (53)
| 11| insert_one_bit(num, 1, 0): 101011 (43)
| 12|
| 13| insert_mult_bits(num, bits, len, i): insert multiple bits with len at specific position
| 14| For example:
| 15|
| 16| Input: num = 101 (5)
| 17| insert_mult_bits(num, 7, 3, 1): 101111 (47)
| 18| insert_mult_bits(num, 7, 3, 0): 101111 (47)
| 19| insert_mult_bits(num, 7, 3, 3): 111101 (61)
| 20| """
| 21|
| 22| """
| 23| Insert exact one bit at specific position
| 24|
| 25| Algorithm:
| 26| 1. Create a mask having bit from i to the most significant bit, and append the new bit at 0 position
| 27| 2. Keep the bit from 0 position to i position ( like 000...001111)
| 28| 3. Merge mask and num
| 29| """
| 30| def insert_one_bit(num, bit, i):
| 31| # Create mask
| 32| mask = num >> i
| 33| mask = (mask << 1) | bit
| 34| mask = mask << i
| 35| # Keep the bit from 0 position to i position
| 36| right = ((1 << i) - 1) & num
| 37| return right | mask
| 38|
| 39| def insert_mult_bits(num, bits, len, i):
| 40| mask = num >> i
| 41| mask = (mask << len) | bits
| 42| mask = mask << i
| 43| right = ((1 << i) - 1) & num
| 44| return right | mask
#========================================================================================================================
# :: bit/count_flips_to_convert.py ::
#========================================================================================================================
| 1| """
| 2| Write a function to determine the minimal number of bits you would need to
| 3| flip to convert integer A to integer B.
| 4| For example:
| 5| Input: 29 (or: 11101), 15 (or: 01111)
| 6| Output: 2
| 7| """
| 8|
| 9|
| 10| def count_flips_to_convert(a, b):
| 11|
| 12| diff = a ^ b
| 13|
| 14| # count number of ones in diff
| 15| count = 0
| 16| while diff:
| 17| diff &= (diff - 1)
| 18| count += 1
| 19| return count
#========================================================================================================================
# :: bit/power_of_two.py ::
#========================================================================================================================
| 1| """
| 2| given an integer, write a function to determine if it is a power of two
| 3| """
| 4| def is_power_of_two(n):
| 5| """
| 6| :type n: int
| 7| :rtype: bool
| 8| """
| 9| return n > 0 and not n & (n-1)
#========================================================================================================================
# :: bit/single_number.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of integers, every element appears
| 3| twice except for one. Find that single one.
| 4|
| 5| NOTE: This also works for finding a number occurring odd
| 6| number of times, where all the other numbers appear
| 7| even number of times.
| 8|
| 9| Note:
| 10| Your algorithm should have a linear runtime complexity.
| 11| Could you implement it without using extra memory?
| 12| """
| 13| def single_number(nums):
| 14| """
| 15| Returns single number, if found.
| 16| Else if all numbers appear twice, returns 0.
| 17| :type nums: List[int]
| 18| :rtype: int
| 19| """
| 20| i = 0
| 21| for num in nums:
| 22| i ^= num
| 23| return i
#========================================================================================================================
# :: bit/add_bitwise_operator.py ::
#========================================================================================================================
| 1| """
| 2| The following code adds two positive integers without using the '+' operator.
| 3| The code uses bitwise operations to add two numbers.
| 4|
| 5| Input: 2 3
| 6| Output: 5
| 7| """
| 8| def add_bitwise_operator(x, y):
| 9|
| 10| while y:
| 11| carry = x & y
| 12| x = x ^ y
| 13| y = carry << 1
| 14| return x
#========================================================================================================================
# :: bit/subsets.py ::
#========================================================================================================================
| 1| """
| 2| Given a set of distinct integers, nums,
| 3| return all possible subsets.
| 4|
| 5| Note: The solution set must not contain duplicate subsets.
| 6|
| 7| For example,
| 8| If nums = [1,2,3], a solution is:
| 9|
| 10| {
| 11| (1, 2),
| 12| (1, 3),
| 13| (1,),
| 14| (2,),
| 15| (3,),
| 16| (1, 2, 3),
| 17| (),
| 18| (2, 3)
| 19| }
| 20| """
| 21| def subsets(nums):
| 22| """
| 23| :param nums: List[int]
| 24| :return: Set[tuple]
| 25| """
| 26| n = len(nums)
| 27| total = 1 << n
| 28| res = set()
| 29|
| 30| for i in range(total):
| 31| subset = tuple(num for j, num in enumerate(nums) if i & 1 << j)
| 32| res.add(subset)
| 33|
| 34| return res
| 35| """
| 36| this explanation is from leet_nik @ leetcode
| 37| This is an amazing solution. Learnt a lot.
| 38|
| 39| Number of subsets for {1 , 2 , 3 } = 2^3 .
| 40| why ?
| 41| case possible outcomes for the set of subsets
| 42| 1 -> Take or dont take = 2
| 43| 2 -> Take or dont take = 2
| 44| 3 -> Take or dont take = 2
| 45|
| 46| therefore,
| 47| total = 2*2*2 = 2^3 = {{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}
| 48|
| 49| Lets assign bits to each outcome ->
| 50| First bit to 1 , Second bit to 2 and third bit to 3
| 51| Take = 1
| 52| Dont take = 0
| 53|
| 54| 0) 0 0 0 -> Dont take 3 , Dont take 2 , Dont take 1 = { }
| 55| 1) 0 0 1 -> Dont take 3 , Dont take 2 , take 1 = { 1 }
| 56| 2) 0 1 0 -> Dont take 3 , take 2 , Dont take 1 = { 2 }
| 57| 3) 0 1 1 -> Dont take 3 , take 2 , take 1 = { 1 , 2 }
| 58| 4) 1 0 0 -> take 3 , Dont take 2 , Dont take 1 = { 3 }
| 59| 5) 1 0 1 -> take 3 , Dont take 2 , take 1 = { 1 , 3 }
| 60| 6) 1 1 0 -> take 3 , take 2 , Dont take 1 = { 2 , 3 }
| 61| 7) 1 1 1 -> take 3 , take 2 , take 1 = { 1 , 2 , 3 }
| 62|
| 63| In the above logic ,Insert S[i] only if (j>>i)&1 ==true
| 64| { j E { 0,1,2,3,4,5,6,7 } i = ith element in the input array }
| 65|
| 66| element 1 is inserted only into those places where 1st bit of j is 1
| 67| if( j >> 0 &1 ) ==> for above above eg.
| 68| this is true for sl.no.( j )= 1 , 3 , 5 , 7
| 69|
| 70| element 2 is inserted only into those places where 2nd bit of j is 1
| 71| if( j >> 1 &1 ) == for above above eg.
| 72| this is true for sl.no.( j ) = 2 , 3 , 6 , 7
| 73|
| 74| element 3 is inserted only into those places where 3rd bit of j is 1
| 75| if( j >> 2 & 1 ) == for above above eg.
| 76| this is true for sl.no.( j ) = 4 , 5 , 6 , 7
| 77|
| 78| Time complexity : O(n*2^n) , for every input element loop traverses
| 79| the whole solution set length i.e. 2^n
| 80| """
#========================================================================================================================
# :: bit/bytes_int_conversion.py ::
#========================================================================================================================
| 1| from collections import deque
| 2|
| 3|
| 4| def int_to_bytes_big_endian(num):
| 5| bytestr = deque()
| 6| while num > 0:
| 7| # list.insert(0, ...) is inefficient
| 8| bytestr.appendleft(num & 0xff)
| 9| num >>= 8
| 10| return bytes(bytestr)
| 11|
| 12|
| 13| def int_to_bytes_little_endian(num):
| 14| bytestr = []
| 15| while num > 0:
| 16| bytestr.append(num & 0xff)
| 17| num >>= 8
| 18| return bytes(bytestr)
| 19|
| 20|
| 21| def bytes_big_endian_to_int(bytestr):
| 22| num = 0
| 23| for b in bytestr:
| 24| num <<= 8
| 25| num += b
| 26| return num
| 27|
| 28|
| 29| def bytes_little_endian_to_int(bytestr):
| 30| num = 0
| 31| e = 0
| 32| for b in bytestr:
| 33| num += b << e
| 34| e += 8
| 35| return num
#========================================================================================================================
# :: bit/find_difference.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings s and t which consist of only lowercase letters.
| 3| String t is generated by random shuffling string s and then add one more letter
| 4| at a random position. Find the letter that was added in t.
| 5|
| 6| For example:
| 7| Input:
| 8| s = "abcd"
| 9| t = "abecd"
| 10| Output: 'e'
| 11|
| 12| Explanation:
| 13| 'e' is the letter that was added.
| 14| """
| 15|
| 16| """
| 17| We use the characteristic equation of XOR.
| 18| A xor B xor C = A xor C xor B
| 19| If A == C, then A xor C = 0
| 20| and then, B xor 0 = B
| 21| """
| 22| def find_difference(s, t):
| 23| ret = 0
| 24| for ch in s + t:
| 25| # ord(ch) return an integer representing the Unicode code point of that character
| 26| ret = ret ^ ord(ch)
| 27| # chr(i) Return the string representing a character whose Unicode code point is the integer i
| 28| return chr(ret)
#========================================================================================================================
# :: bit/find_missing_number.py ::
#========================================================================================================================
| 1| """
| 2| Returns the missing number from a sequence of unique integers
| 3| in range [0..n] in O(n) time and space. The difference between
| 4| consecutive integers cannot be more than 1. If the sequence is
| 5| already complete, the next integer in the sequence will be returned.
| 6|
| 7| For example:
| 8| Input: nums = [4, 1, 3, 0, 6, 5, 2]
| 9| Output: 7
| 10| """
| 11| def find_missing_number(nums):
| 12|
| 13| missing = 0
| 14| for i, num in enumerate(nums):
| 15| missing ^= num
| 16| missing ^= i + 1
| 17|
| 18| return missing
| 19|
| 20|
| 21| def find_missing_number2(nums):
| 22|
| 23| num_sum = sum(nums)
| 24| n = len(nums)
| 25| total_sum = n*(n+1) // 2
| 26| missing = total_sum - num_sum
| 27| return missing
#========================================================================================================================
# :: bit/has_alternative_bit.py ::
#========================================================================================================================
| 1| """
| 2| Given a positive integer, check whether it has alternating bits: namely,
| 3| if two adjacent bits will always have different values.
| 4|
| 5| For example:
| 6| Input: 5
| 7| Output: True because the binary representation of 5 is: 101.
| 8|
| 9| Input: 7
| 10| Output: False because the binary representation of 7 is: 111.
| 11|
| 12| Input: 11
| 13| Output: False because the binary representation of 11 is: 1011.
| 14|
| 15| Input: 10
| 16| Output: True because The binary representation of 10 is: 1010.
| 17| """
| 18|
| 19| # Time Complexity - O(number of bits in n)
| 20| def has_alternative_bit(n):
| 21| first_bit = 0
| 22| second_bit = 0
| 23| while n:
| 24| first_bit = n & 1
| 25| if n >> 1:
| 26| second_bit = (n >> 1) & 1
| 27| if not first_bit ^ second_bit:
| 28| return False
| 29| else:
| 30| return True
| 31| n = n >> 1
| 32| return True
| 33|
| 34| # Time Complexity - O(1)
| 35| def has_alternative_bit_fast(n):
| 36| mask1 = int('aaaaaaaa', 16) # for bits ending with zero (...1010)
| 37| mask2 = int('55555555', 16) # for bits ending with one (...0101)
| 38| return mask1 == (n + (n ^ mask1)) or mask2 == (n + (n ^ mask2))
#========================================================================================================================
# :: calculator/math_parser.py ::
#========================================================================================================================
| 1| """
| 2| Contributed by izanbf1803.
| 3|
| 4| Example:
| 5| -------------------------------------------------------------------------------------------------
| 6| Code:
| 7| | exp = "2452 * (3 * 6.5 + 1) * 6 / 235"
| 8| | print("Expression:", exp)
| 9| | print("Parsed expression:", mp.parse(exp))
| 10| | print("Evaluation result:", mp.evaluate(exp))
| 11|
| 12| Output:
| 13| | Expression: 2452 * (3 * 6 + 1) * 6 / 235
| 14| | Parsed expression: ['2452', '*', '(', '3', '*', '6', '+', '1', ')', '*', '6', '/', '235']
| 15| | Evaluation result: 1189.4808510638297
| 16| -------------------------------------------------------------------------------------------------
| 17|
| 18| Now added '^' operator for exponents. (by @goswami-rahul)
| 19| """
| 20|
| 21| from collections import deque
| 22| import re
| 23|
| 24| numeric_value = re.compile('\d+(\.\d+)?')
| 25|
| 26| __operators__ = "+-/*^"
| 27| __parenthesis__ = "()"
| 28| __priority__ = {
| 29| '+': 0,
| 30| '-': 0,
| 31| '*': 1,
| 32| '/': 1,
| 33| '^': 2
| 34| }
| 35|
| 36| def is_operator(token):
| 37| """
| 38| Check if token it's a operator
| 39|
| 40| token Char: Token
| 41| """
| 42| return token in __operators__
| 43|
| 44| def higher_priority(op1, op2):
| 45| """
| 46| Check if op1 have higher priority than op2
| 47|
| 48| op1 Char: Operation Token 1
| 49| op2 Char: Operation Token 2
| 50| """
| 51| return __priority__[op1] >= __priority__[op2]
| 52|
| 53| def calc(n2, n1, operator):
| 54| """
| 55| Calculate operation result
| 56|
| 57| n2 Number: Number 2
| 58| n1 Number: Number 1
| 59| operator Char: Operation to calculate
| 60| """
| 61| if operator == '-': return n1 - n2
| 62| elif operator == '+': return n1 + n2
| 63| elif operator == '*': return n1 * n2
| 64| elif operator == '/': return n1 / n2
| 65| elif operator == '^': return n1 ** n2
| 66| return 0
| 67|
| 68| def apply_operation(op_stack, out_stack):
| 69| """
| 70| Apply operation to the first 2 items of the output queue
| 71|
| 72| op_stack Deque (reference)
| 73| out_stack Deque (reference)
| 74| """
| 75| out_stack.append(calc(out_stack.pop(), out_stack.pop(), op_stack.pop()))
| 76|
| 77| def parse(expression):
| 78| """
| 79| Return array of parsed tokens in the expression
| 80|
| 81| expression String: Math expression to parse in infix notation
| 82| """
| 83| result = []
| 84| current = ""
| 85| for i in expression:
| 86| if i.isdigit() or i == '.':
| 87| current += i
| 88| else:
| 89| if len(current) > 0:
| 90| result.append(current)
| 91| current = ""
| 92| if i in __operators__ or i in __parenthesis__:
| 93| result.append(i)
| 94| else:
| 95| raise Exception("invalid syntax " + i)
| 96|
| 97| if len(current) > 0:
| 98| result.append(current)
| 99| return result
|100|
|101| def evaluate(expression):
|102| """
|103| Calculate result of expression
|104|
|105| expression String: The expression
|106| type Type (optional): Number type [int, float]
|107| """
|108| op_stack = deque() # operator stack
|109| out_stack = deque() # output stack (values)
|110| tokens = parse(expression) # calls the function only once!
|111| for token in tokens:
|112| if numeric_value.match(token):
|113| out_stack.append(float(token))
|114| elif token == '(':
|115| op_stack.append(token)
|116| elif token == ')':
|117| while len(op_stack) > 0 and op_stack[-1] != '(':
|118| apply_operation(op_stack, out_stack)
|119| op_stack.pop() # Remove remaining '('
|120| else: # is_operator(token)
|121| while len(op_stack) > 0 and is_operator(op_stack[-1]) and higher_priority(op_stack[-1], token):
|122| apply_operation(op_stack, out_stack)
|123| op_stack.append(token)
|124|
|125| while len(op_stack) > 0:
|126| apply_operation(op_stack, out_stack)
|127|
|128| return out_stack[-1]
|129|
|130|
|131| def main():
|132| """
|133| simple user-interface
|134| """
|135| print("\t\tCalculator\n\n")
|136| while True:
|137| user_input = input("expression or exit: ")
|138| if user_input == "exit":
|139| break
|140| try:
|141| print("The result is {0}".format(evaluate(user_input)))
|142| except Exception:
|143| print("invalid syntax!")
|144| user_input = input("expression or exit: ")
|145| print("program end")
|146|
|147|
|148| if __name__ == "__main__":
|149| main()
#========================================================================================================================
# :: dfs/count_islands.py ::
#========================================================================================================================
| 1| """
| 2| Given a 2d grid map of '1's (land) and '0's (water),
| 3| count the number of islands.
| 4| An island is surrounded by water and is formed by
| 5| connecting adjacent lands horizontally or vertically.
| 6| You may assume all four edges of the grid are all surrounded by water.
| 7|
| 8| Example 1:
| 9|
| 10| 11110
| 11| 11010
| 12| 11000
| 13| 00000
| 14| Answer: 1
| 15|
| 16| Example 2:
| 17|
| 18| 11000
| 19| 11000
| 20| 00100
| 21| 00011
| 22| Answer: 3
| 23| """
| 24|
| 25| def num_islands(grid):
| 26| count = 0
| 27| for i in range(len(grid)):
| 28| for j, col in enumerate(grid[i]):
| 29| if col == 1:
| 30| dfs(grid, i, j)
| 31| count += 1
| 32| return count
| 33|
| 34|
| 35| def dfs(grid, i, j):
| 36| if (i < 0 or i >= len(grid)) or (j < 0 or j >= len(grid[0])):
| 37| return
| 38| if grid[i][j] != 1:
| 39| return
| 40| grid[i][j] = 0
| 41| dfs(grid, i+1, j)
| 42| dfs(grid, i-1, j)
| 43| dfs(grid, i, j+1)
| 44| dfs(grid, i, j-1)
#========================================================================================================================
# :: dfs/__init__.py ::
#========================================================================================================================
| 1| from .all_factors import *
| 2| from .count_islands import *
| 3| from .pacific_atlantic import *
| 4| from .sudoku_solver import *
| 5| from .walls_and_gates import *
#========================================================================================================================
# :: dfs/pacific_atlantic.py ::
#========================================================================================================================
| 1| # Given an m x n matrix of non-negative integers representing
| 2| # the height of each unit cell in a continent,
| 3| # the "Pacific ocean" touches the left and top edges of the matrix
| 4| # and the "Atlantic ocean" touches the right and bottom edges.
| 5|
| 6| # Water can only flow in four directions (up, down, left, or right)
| 7| # from a cell to another one with height equal or lower.
| 8|
| 9| # Find the list of grid coordinates where water can flow to both the
| 10| # Pacific and Atlantic ocean.
| 11|
| 12| # Note:
| 13| # The order of returned grid coordinates does not matter.
| 14| # Both m and n are less than 150.
| 15| # Example:
| 16|
| 17| # Given the following 5x5 matrix:
| 18|
| 19| # Pacific ~ ~ ~ ~ ~
| 20| # ~ 1 2 2 3 (5) *
| 21| # ~ 3 2 3 (4) (4) *
| 22| # ~ 2 4 (5) 3 1 *
| 23| # ~ (6) (7) 1 4 5 *
| 24| # ~ (5) 1 1 2 4 *
| 25| # * * * * * Atlantic
| 26|
| 27| # Return:
| 28|
| 29| # [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]
| 30| # (positions with parentheses in above matrix).
| 31|
| 32| def pacific_atlantic(matrix):
| 33| """
| 34| :type matrix: List[List[int]]
| 35| :rtype: List[List[int]]
| 36| """
| 37| n = len(matrix)
| 38| if not n: return []
| 39| m = len(matrix[0])
| 40| if not m: return []
| 41| res = []
| 42| atlantic = [[False for _ in range (n)] for _ in range(m)]
| 43| pacific = [[False for _ in range (n)] for _ in range(m)]
| 44| for i in range(n):
| 45| dfs(pacific, matrix, float("-inf"), i, 0)
| 46| dfs(atlantic, matrix, float("-inf"), i, m-1)
| 47| for i in range(m):
| 48| dfs(pacific, matrix, float("-inf"), 0, i)
| 49| dfs(atlantic, matrix, float("-inf"), n-1, i)
| 50| for i in range(n):
| 51| for j in range(m):
| 52| if pacific[i][j] and atlantic[i][j]:
| 53| res.append([i, j])
| 54| return res
| 55|
| 56| def dfs(grid, matrix, height, i, j):
| 57| if i < 0 or i >= len(matrix) or j < 0 or j >= len(matrix[0]):
| 58| return
| 59| if grid[i][j] or matrix[i][j] < height:
| 60| return
| 61| grid[i][j] = True
| 62| dfs(grid, matrix, matrix[i][j], i-1, j)
| 63| dfs(grid, matrix, matrix[i][j], i+1, j)
| 64| dfs(grid, matrix, matrix[i][j], i, j-1)
| 65| dfs(grid, matrix, matrix[i][j], i, j+1)
#========================================================================================================================
# :: dfs/all_factors.py ::
#========================================================================================================================
| 1| """
| 2| Numbers can be regarded as product of its factors. For example,
| 3| 8 = 2 x 2 x 2;
| 4| = 2 x 4.
| 5|
| 6|
| 7| Write a function that takes an integer n and return all possible combinations
| 8| of its factors.Numbers can be regarded as product of its factors. For example,
| 9| 8 = 2 x 2 x 2;
| 10| = 2 x 4.
| 11|
| 12| Examples:
| 13| input: 1
| 14| output:
| 15| []
| 16|
| 17|
| 18| input: 37
| 19| output:
| 20| []
| 21|
| 22| input: 32
| 23| output:
| 24| [
| 25| [2, 16],
| 26| [2, 2, 8],
| 27| [2, 2, 2, 4],
| 28| [2, 2, 2, 2, 2],
| 29| """
| 30| def get_factors(n):
| 31| """[summary]
| 32|
| 33| Arguments:
| 34| n {[int]} -- [to analysed number]
| 35|
| 36| Returns:
| 37| [list of lists] -- [all factors of the number n]
| 38| """
| 39|
| 40| def factor(n, i, combi, res):
| 41| """[summary]
| 42| helper function
| 43|
| 44| Arguments:
| 45| n {[int]} -- [number]
| 46| i {[int]} -- [to tested divisor]
| 47| combi {[list]} -- [catch divisors]
| 48| res {[list]} -- [all factors of the number n]
| 49|
| 50| Returns:
| 51| [list] -- [res]
| 52| """
| 53|
| 54| while i * i <= n:
| 55| if n % i == 0:
| 56| res += combi + [i, int(n/i)],
| 57| factor(n/i, i, combi+[i], res)
| 58| i += 1
| 59| return res
| 60| return factor(n, 2, [], [])
| 61|
| 62|
| 63| def get_factors_iterative1(n):
| 64| """[summary]
| 65| Computes all factors of n.
| 66| Translated the function get_factors(...) in
| 67| a call-stack modell.
| 68|
| 69| Arguments:
| 70| n {[int]} -- [to analysed number]
| 71|
| 72| Returns:
| 73| [list of lists] -- [all factors]
| 74| """
| 75|
| 76| todo, res = [(n, 2, [])], []
| 77| while todo:
| 78| n, i, combi = todo.pop()
| 79| while i * i <= n:
| 80| if n % i == 0:
| 81| res += combi + [i, n//i],
| 82| todo.append((n//i, i, combi+[i])),
| 83| i += 1
| 84| return res
| 85|
| 86|
| 87| def get_factors_iterative2(n):
| 88| """[summary]
| 89| analog as above
| 90|
| 91| Arguments:
| 92| n {[int]} -- [description]
| 93|
| 94| Returns:
| 95| [list of lists] -- [all factors of n]
| 96| """
| 97|
| 98| ans, stack, x = [], [], 2
| 99| while True:
|100| if x > n // x:
|101| if not stack:
|102| return ans
|103| ans.append(stack + [n])
|104| x = stack.pop()
|105| n *= x
|106| x += 1
|107| elif n % x == 0:
|108| stack.append(x)
|109| n //= x
|110| else:
|111| x += 1
#========================================================================================================================
# :: dfs/walls_and_gates.py ::
#========================================================================================================================
| 1| """
| 2| You are given a m x n 2D grid initialized with these three possible values:
| 3| -1: A wall or an obstacle.
| 4| 0: A gate.
| 5| INF: Infinity means an empty room. We use the value 2^31 - 1 = 2147483647 to represent INF
| 6| as you may assume that the distance to a gate is less than 2147483647.
| 7| Fill the empty room with distance to its nearest gate.
| 8| If it is impossible to reach a gate, it should be filled with INF.
| 9|
| 10| For example, given the 2D grid:
| 11| INF -1 0 INF
| 12| INF INF INF -1
| 13| INF -1 INF -1
| 14| 0 -1 INF INF
| 15| After running your function, the 2D grid should be:
| 16| 3 -1 0 1
| 17| 2 2 1 -1
| 18| 1 -1 2 -1
| 19| 0 -1 3 4
| 20| """
| 21|
| 22| def walls_and_gates(rooms):
| 23| for i in range(len(rooms)):
| 24| for j in range(len(rooms[0])):
| 25| if rooms[i][j] == 0:
| 26| dfs(rooms, i, j, 0)
| 27|
| 28|
| 29| def dfs(rooms, i, j, depth):
| 30| if (i < 0 or i >= len(rooms)) or (j < 0 or j >= len(rooms[0])):
| 31| return # out of bounds
| 32| if rooms[i][j] < depth:
| 33| return # crossed
| 34| rooms[i][j] = depth
| 35| dfs(rooms, i+1, j, depth+1)
| 36| dfs(rooms, i-1, j, depth+1)
| 37| dfs(rooms, i, j+1, depth+1)
| 38| dfs(rooms, i, j-1, depth+1)
#========================================================================================================================
# :: dfs/sudoku_solver.py ::
#========================================================================================================================
| 1| """
| 2| It's similar to how human solve Sudoku.
| 3|
| 4| create a hash table (dictionary) val to store possible values in every location.
| 5| Each time, start from the location with fewest possible values, choose one value
| 6| from it and then update the board and possible values at other locations.
| 7| If this update is valid, keep solving (DFS). If this update is invalid (leaving
| 8| zero possible values at some locations) or this value doesn't lead to the
| 9| solution, undo the updates and then choose the next value.
| 10| Since we calculated val at the beginning and start filling the board from the
| 11| location with fewest possible values, the amount of calculation and thus the
| 12| runtime can be significantly reduced:
| 13|
| 14|
| 15| The run time is 48-68 ms on LeetCode OJ, which seems to be among the fastest
| 16| python solutions here.
| 17|
| 18|
| 19| The PossibleVals function may be further simplified/optimized, but it works just
| 20| fine for now. (it would look less lengthy if we are allowed to use numpy array
| 21| for the board lol).
| 22| """
| 23| class Sudoku:
| 24| def __init__ (self, board, row, col):
| 25| self.board = board
| 26| self.row = row
| 27| self.col = col
| 28| self.val = self.possible_values()
| 29|
| 30| def possible_values(self):
| 31| a = "123456789"
| 32| d, val = {}, {}
| 33| for i in range(self.row):
| 34| for j in range(self.col):
| 35| ele = self.board[i][j]
| 36| if ele != ".":
| 37| d[("r", i)] = d.get(("r", i), []) + [ele]
| 38| d[("c", j)] = d.get(("c", j), []) + [ele]
| 39| d[(i//3, j//3)] = d.get((i//3, j//3), []) + [ele]
| 40| else:
| 41| val[(i,j)] = []
| 42| for (i,j) in val.keys():
| 43| inval = d.get(("r",i),[])+d.get(("c",j),[])+d.get((i/3,j/3),[])
| 44| val[(i,j)] = [n for n in a if n not in inval ]
| 45| return val
| 46|
| 47| def solve(self):
| 48| if len(self.val)==0:
| 49| return True
| 50| kee = min(self.val.keys(), key=lambda x: len(self.val[x]))
| 51| nums = self.val[kee]
| 52| for n in nums:
| 53| update = {kee:self.val[kee]}
| 54| if self.valid_one(n, kee, update): # valid choice
| 55| if self.solve(): # keep solving
| 56| return True
| 57| self.undo(kee, update) # invalid choice or didn't solve it => undo
| 58| return False
| 59|
| 60| def valid_one(self, n, kee, update):
| 61| self.board[kee[0]][kee[1]] = n
| 62| del self.val[kee]
| 63| i, j = kee
| 64| for ind in self.val.keys():
| 65| if n in self.val[ind]:
| 66| if ind[0]==i or ind[1]==j or (ind[0]/3,ind[1]/3)==(i/3,j/3):
| 67| update[ind] = n
| 68| self.val[ind].remove(n)
| 69| if len(self.val[ind])==0:
| 70| return False
| 71| return True
| 72|
| 73| def undo(self, kee, update):
| 74| self.board[kee[0]][kee[1]]="."
| 75| for k in update:
| 76| if k not in self.val:
| 77| self.val[k]= update[k]
| 78| else:
| 79| self.val[k].append(update[k])
| 80| return None
| 81|
| 82| def __str__(self):
| 83| """[summary]
| 84| Generates a board representation as string.
| 85|
| 86| Returns:
| 87| [str] -- [board representation]
| 88| """
| 89|
| 90| resp = ""
| 91| for i in range(self.row):
| 92| for j in range(self.col):
| 93| resp += " {0} ".format(self.board[i][j])
| 94| resp += "\n"
| 95| return resp
#========================================================================================================================
# :: sort/shell_sort.py ::
#========================================================================================================================
| 1| def shell_sort(arr):
| 2| ''' Shell Sort
| 3| Complexity: O(n^2)
| 4| '''
| 5| n = len(arr)
| 6| # Initialize size of the gap
| 7| gap = n//2
| 8|
| 9| while gap > 0:
| 10| y_index = gap
| 11| while y_index < len(arr):
| 12| y = arr[y_index]
| 13| x_index = y_index - gap
| 14| while x_index >= 0 and y < arr[x_index]:
| 15| arr[x_index + gap] = arr[x_index]
| 16| x_index = x_index - gap
| 17| arr[x_index + gap] = y
| 18| y_index = y_index + 1
| 19| gap = gap//2
| 20|
| 21| return arr
#========================================================================================================================
# :: sort/bucket_sort.py ::
#========================================================================================================================
| 1| def bucket_sort(arr):
| 2| ''' Bucket Sort
| 3| Complexity: O(n^2)
| 4| The complexity is dominated by nextSort
| 5| '''
| 6| # The number of buckets and make buckets
| 7| num_buckets = len(arr)
| 8| buckets = [[] for bucket in range(num_buckets)]
| 9| # Assign values into bucket_sort
| 10| for value in arr:
| 11| index = value * num_buckets // (max(arr) + 1)
| 12| buckets[index].append(value)
| 13| # Sort
| 14| sorted_list = []
| 15| for i in range(num_buckets):
| 16| sorted_list.extend(next_sort(buckets[i]))
| 17| return sorted_list
| 18|
| 19| def next_sort(arr):
| 20| # We will use insertion sort here.
| 21| for i in range(1, len(arr)):
| 22| j = i - 1
| 23| key = arr[i]
| 24| while arr[j] > key and j >= 0:
| 25| arr[j+1] = arr[j]
| 26| j = j - 1
| 27| arr[j + 1] = key
| 28| return arr
#========================================================================================================================
# :: sort/quick_sort.py ::
#========================================================================================================================
| 1| def quick_sort(arr, simulation=False):
| 2| """ Quick sort
| 3| Complexity: best O(n log(n)) avg O(n log(n)), worst O(N^2)
| 4| """
| 5|
| 6| iteration = 0
| 7| if simulation:
| 8| print("iteration",iteration,":",*arr)
| 9| arr, _ = quick_sort_recur(arr, 0, len(arr) - 1, iteration, simulation)
| 10| return arr
| 11|
| 12| def quick_sort_recur(arr, first, last, iteration, simulation):
| 13| if first < last:
| 14| pos = partition(arr, first, last)
| 15| # Start our two recursive calls
| 16| if simulation:
| 17| iteration = iteration + 1
| 18| print("iteration",iteration,":",*arr)
| 19|
| 20| _, iteration = quick_sort_recur(arr, first, pos - 1, iteration, simulation)
| 21| _, iteration = quick_sort_recur(arr, pos + 1, last, iteration, simulation)
| 22|
| 23| return arr, iteration
| 24|
| 25| def partition(arr, first, last):
| 26| wall = first
| 27| for pos in range(first, last):
| 28| if arr[pos] < arr[last]: # last is the pivot
| 29| arr[pos], arr[wall] = arr[wall], arr[pos]
| 30| wall += 1
| 31| arr[wall], arr[last] = arr[last], arr[wall]
| 32| return wall
#========================================================================================================================
# :: sort/merge_sort.py ::
#========================================================================================================================
| 1| def merge_sort(arr):
| 2| """ Merge Sort
| 3| Complexity: O(n log(n))
| 4| """
| 5| # Our recursive base case
| 6| if len(arr) <= 1:
| 7| return arr
| 8| mid = len(arr) // 2
| 9| # Perform merge_sort recursively on both halves
| 10| left, right = merge_sort(arr[:mid]), merge_sort(arr[mid:])
| 11|
| 12| # Merge each side together
| 13| return merge(left, right, arr.copy())
| 14|
| 15|
| 16| def merge(left, right, merged):
| 17| """ Merge helper
| 18| Complexity: O(n)
| 19| """
| 20|
| 21| left_cursor, right_cursor = 0, 0
| 22| while left_cursor < len(left) and right_cursor < len(right):
| 23| # Sort each one and place into the result
| 24| if left[left_cursor] <= right[right_cursor]:
| 25| merged[left_cursor+right_cursor]=left[left_cursor]
| 26| left_cursor += 1
| 27| else:
| 28| merged[left_cursor + right_cursor] = right[right_cursor]
| 29| right_cursor += 1
| 30| # Add the left overs if there's any left to the result
| 31| for left_cursor in range(left_cursor, len(left)):
| 32| merged[left_cursor + right_cursor] = left[left_cursor]
| 33| # Add the left overs if there's any left to the result
| 34| for right_cursor in range(right_cursor, len(right)):
| 35| merged[left_cursor + right_cursor] = right[right_cursor]
| 36|
| 37| # Return result
| 38| return merged
#========================================================================================================================
# :: sort/cycle_sort.py ::
#========================================================================================================================
| 1| def cycle_sort(arr):
| 2| """
| 3| cycle_sort
| 4| This is based on the idea that the permutations to be sorted
| 5| can be decomposed into cycles,
| 6| and the results can be individually sorted by cycling.
| 7|
| 8| reference: https://en.wikipedia.org/wiki/Cycle_sort
| 9|
| 10| Average time complexity : O(N^2)
| 11| Worst case time complexity : O(N^2)
| 12| """
| 13| len_arr = len(arr)
| 14| # Finding cycle to rotate.
| 15| for cur in range(len_arr - 1):
| 16| item = arr[cur]
| 17|
| 18| # Finding an indx to put items in.
| 19| index = cur
| 20| for i in range(cur + 1, len_arr):
| 21| if arr[i] < item:
| 22| index += 1
| 23|
| 24| # Case of there is not a cycle
| 25| if index == cur:
| 26| continue
| 27|
| 28| # Putting the item immediately right after the duplicate item or on the right.
| 29| while item == arr[index]:
| 30| index += 1
| 31| arr[index], item = item, arr[index]
| 32|
| 33| # Rotating the remaining cycle.
| 34| while index != cur:
| 35|
| 36| # Finding where to put the item.
| 37| index = cur
| 38| for i in range(cur + 1, len_arr):
| 39| if arr[i] < item:
| 40| index += 1
| 41|
| 42| # After item is duplicated, put it in place or put it there.
| 43| while item == arr[index]:
| 44| index += 1
| 45| arr[index], item = item, arr[index]
| 46| return arr
#========================================================================================================================
# :: sort/top_sort.py ::
#========================================================================================================================
| 1| GRAY, BLACK = 0, 1
| 2|
| 3| def top_sort_recursive(graph):
| 4| """ Time complexity is the same as DFS, which is O(V + E)
| 5| Space complexity: O(V)
| 6| """
| 7| order, enter, state = [], set(graph), {}
| 8|
| 9| def dfs(node):
| 10| state[node] = GRAY
| 11| #print(node)
| 12| for k in graph.get(node, ()):
| 13| sk = state.get(k, None)
| 14| if sk == GRAY:
| 15| raise ValueError("cycle")
| 16| if sk == BLACK:
| 17| continue
| 18| enter.discard(k)
| 19| dfs(k)
| 20| order.append(node)
| 21| state[node] = BLACK
| 22|
| 23| while enter: dfs(enter.pop())
| 24| return order
| 25|
| 26| def top_sort(graph):
| 27| """ Time complexity is the same as DFS, which is O(V + E)
| 28| Space complexity: O(V)
| 29| """
| 30| order, enter, state = [], set(graph), {}
| 31|
| 32| def is_ready(node):
| 33| lst = graph.get(node, ())
| 34| if len(lst) == 0:
| 35| return True
| 36| for k in lst:
| 37| sk = state.get(k, None)
| 38| if sk == GRAY:
| 39| raise ValueError("cycle")
| 40| if sk != BLACK:
| 41| return False
| 42| return True
| 43|
| 44| while enter:
| 45| node = enter.pop()
| 46| stack = []
| 47| while True:
| 48| state[node] = GRAY
| 49| stack.append(node)
| 50| for k in graph.get(node, ()):
| 51| sk = state.get(k, None)
| 52| if sk == GRAY:
| 53| raise ValueError("cycle")
| 54| if sk == BLACK:
| 55| continue
| 56| enter.discard(k)
| 57| stack.append(k)
| 58| while stack and is_ready(stack[-1]):
| 59| node = stack.pop()
| 60| order.append(node)
| 61| state[node] = BLACK
| 62| if len(stack) == 0:
| 63| break
| 64| node = stack.pop()
| 65|
| 66| return order
#========================================================================================================================
# :: sort/pancake_sort.py ::
#========================================================================================================================
| 1| def pancake_sort(arr):
| 2| """
| 3| Pancake_sort
| 4| Sorting a given array
| 5| mutation of selection sort
| 6|
| 7| reference: https://www.geeksforgeeks.org/pancake-sorting/
| 8|
| 9| Overall time complexity : O(N^2)
| 10| """
| 11|
| 12| len_arr = len(arr)
| 13| if len_arr <= 1:
| 14| return arr
| 15| for cur in range(len(arr), 1, -1):
| 16| #Finding index of maximum number in arr
| 17| index_max = arr.index(max(arr[0:cur]))
| 18| if index_max+1 != cur:
| 19| #Needs moving
| 20| if index_max != 0:
| 21| #reverse from 0 to index_max
| 22| arr[:index_max+1] = reversed(arr[:index_max+1])
| 23| # Reverse list
| 24| arr[:cur] = reversed(arr[:cur])
| 25| return arr
#========================================================================================================================
# :: sort/insertion_sort.py ::
#========================================================================================================================
| 1| def insertion_sort(arr, simulation=False):
| 2| """ Insertion Sort
| 3| Complexity: O(n^2)
| 4| """
| 5|
| 6| iteration = 0
| 7| if simulation:
| 8| print("iteration",iteration,":",*arr)
| 9|
| 10| for i in range(len(arr)):
| 11| cursor = arr[i]
| 12| pos = i
| 13|
| 14| while pos > 0 and arr[pos - 1] > cursor:
| 15| # Swap the number down the list
| 16| arr[pos] = arr[pos - 1]
| 17| pos = pos - 1
| 18| # Break and do the final swap
| 19| arr[pos] = cursor
| 20|
| 21| if simulation:
| 22| iteration = iteration + 1
| 23| print("iteration",iteration,":",*arr)
| 24|
| 25| return arr
#========================================================================================================================
# :: sort/gnome_sort.py ::
#========================================================================================================================
| 1| """
| 2|
| 3| Gnome Sort
| 4| Best case performance is O(n)
| 5| Worst case performance is O(n^2)
| 6|
| 7| """
| 8|
| 9|
| 10| def gnome_sort(arr):
| 11| n = len(arr)
| 12| index = 0
| 13| while index < n:
| 14| if index == 0 or arr[index] >= arr[index-1]:
| 15| index = index + 1
| 16| else:
| 17| arr[index], arr[index-1] = arr[index-1], arr[index]
| 18| index = index - 1
| 19| return arr
#========================================================================================================================
# :: sort/bubble_sort.py ::
#========================================================================================================================
| 1| """
| 2|
| 3| https://en.wikipedia.org/wiki/Bubble_sort
| 4|
| 5| Worst-case performance: O(N^2)
| 6|
| 7| If you call bubble_sort(arr,True), you can see the process of the sort
| 8| Default is simulation = False
| 9|
| 10| """
| 11|
| 12|
| 13| def bubble_sort(arr, simulation=False):
| 14| def swap(i, j):
| 15| arr[i], arr[j] = arr[j], arr[i]
| 16|
| 17| n = len(arr)
| 18| swapped = True
| 19|
| 20| iteration = 0
| 21| if simulation:
| 22| print("iteration",iteration,":",*arr)
| 23| x = -1
| 24| while swapped:
| 25| swapped = False
| 26| x = x + 1
| 27| for i in range(1, n-x):
| 28| if arr[i - 1] > arr[i]:
| 29| swap(i - 1, i)
| 30| swapped = True
| 31| if simulation:
| 32| iteration = iteration + 1
| 33| print("iteration",iteration,":",*arr)
| 34|
| 35| return arr
#========================================================================================================================
# :: sort/__init__.py ::
#========================================================================================================================
| 1| from .bitonic_sort import *
| 2| from .bogo_sort import *
| 3| from .bubble_sort import *
| 4| from .comb_sort import *
| 5| from .counting_sort import *
| 6| from .cycle_sort import *
| 7| from .heap_sort import *
| 8| from .insertion_sort import *
| 9| from .merge_sort import *
| 10| from .pancake_sort import *
| 11| from .quick_sort import *
| 12| from .selection_sort import *
| 13| from .top_sort import *
| 14| from .bucket_sort import *
| 15| from .shell_sort import *
| 16| from .radix_sort import *
| 17| from .gnome_sort import *
| 18| from .cocktail_shaker_sort import *
#========================================================================================================================
# :: sort/selection_sort.py ::
#========================================================================================================================
| 1| def selection_sort(arr, simulation=False):
| 2| """ Selection Sort
| 3| Complexity: O(n^2)
| 4| """
| 5| iteration = 0
| 6| if simulation:
| 7| print("iteration",iteration,":",*arr)
| 8|
| 9| for i in range(len(arr)):
| 10| minimum = i
| 11|
| 12| for j in range(i + 1, len(arr)):
| 13| # "Select" the correct value
| 14| if arr[j] < arr[minimum]:
| 15| minimum = j
| 16|
| 17| arr[minimum], arr[i] = arr[i], arr[minimum]
| 18|
| 19| if simulation:
| 20| iteration = iteration + 1
| 21| print("iteration",iteration,":",*arr)
| 22|
| 23| return arr
#========================================================================================================================
# :: sort/bogo_sort.py ::
#========================================================================================================================
| 1| import random
| 2|
| 3| def bogo_sort(arr, simulation=False):
| 4| """Bogo Sort
| 5| Best Case Complexity: O(n)
| 6| Worst Case Complexity: O(∞)
| 7| Average Case Complexity: O(n(n-1)!)
| 8| """
| 9|
| 10| iteration = 0
| 11| if simulation:
| 12| print("iteration",iteration,":",*arr)
| 13|
| 14| def is_sorted(arr):
| 15| #check the array is inorder
| 16| i = 0
| 17| arr_len = len(arr)
| 18| while i+1 < arr_len:
| 19| if arr[i] > arr[i+1]:
| 20| return False
| 21| i += 1
| 22|
| 23|
| 24| return True
| 25| while not is_sorted(arr):
| 26| random.shuffle(arr)
| 27|
| 28| if simulation:
| 29| iteration = iteration + 1
| 30| print("iteration",iteration,":",*arr)
| 31|
| 32| return arr
#========================================================================================================================
# :: sort/radix_sort.py ::
#========================================================================================================================
| 1| """
| 2| radix sort
| 3| complexity: O(nk + n) . n is the size of input list and k is the digit length of the number
| 4| """
| 5| def radix_sort(arr, simulation=False):
| 6| position = 1
| 7| max_number = max(arr)
| 8|
| 9| iteration = 0
| 10| if simulation:
| 11| print("iteration", iteration, ":", *arr)
| 12|
| 13| while position <= max_number:
| 14| queue_list = [list() for _ in range(10)]
| 15|
| 16| for num in arr:
| 17| digit_number = num // position % 10
| 18| queue_list[digit_number].append(num)
| 19|
| 20| index = 0
| 21| for numbers in queue_list:
| 22| for num in numbers:
| 23| arr[index] = num
| 24| index += 1
| 25|
| 26| if simulation:
| 27| iteration = iteration + 1
| 28| print("iteration", iteration, ":", *arr)
| 29|
| 30| position *= 10
| 31| return arr
| 32|
#========================================================================================================================
# :: sort/counting_sort.py ::
#========================================================================================================================
| 1| def counting_sort(arr):
| 2| """
| 3| Counting_sort
| 4| Sorting a array which has no element greater than k
| 5| Creating a new temp_arr,where temp_arr[i] contain the number of
| 6| element less than or equal to i in the arr
| 7| Then placing the number i into a correct position in the result_arr
| 8| return the result_arr
| 9| Complexity: 0(n)
| 10| """
| 11|
| 12| m = min(arr)
| 13| # in case there are negative elements, change the array to all positive element
| 14| different = 0
| 15| if m < 0:
| 16| # save the change, so that we can convert the array back to all positive number
| 17| different = -m
| 18| for i in range(len(arr)):
| 19| arr[i] += -m
| 20| k = max(arr)
| 21| temp_arr = [0] * (k + 1)
| 22| for i in range(0, len(arr)):
| 23| temp_arr[arr[i]] = temp_arr[arr[i]] + 1
| 24| # temp_array[i] contain the times the number i appear in arr
| 25|
| 26| for i in range(1, k + 1):
| 27| temp_arr[i] = temp_arr[i] + temp_arr[i - 1]
| 28| # temp_array[i] contain the number of element less than or equal i in arr
| 29|
| 30| result_arr = arr.copy()
| 31| # creating a result_arr an put the element in a correct positon
| 32| for i in range(len(arr) - 1, -1, -1):
| 33| result_arr[temp_arr[arr[i]] - 1] = arr[i] - different
| 34| temp_arr[arr[i]] = temp_arr[arr[i]] - 1
| 35|
| 36| return result_arr
#========================================================================================================================
# :: sort/wiggle_sort.py ::
#========================================================================================================================
| 1| """
| 2| Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]....
| 3| """
| 4| def wiggle_sort(nums):
| 5| for i in range(len(nums)):
| 6| if (i % 2 == 1) == (nums[i-1] > nums[i]):
| 7| nums[i-1], nums[i] = nums[i], nums[i-1]
| 8|
| 9| if __name__ == "__main__":
| 10| array = [3, 5, 2, 1, 6, 4]
| 11|
| 12| print(array)
| 13| wiggle_sort(array)
| 14| print(array)
| 15|
| 16|
#========================================================================================================================
# :: sort/sort_colors.py ::
#========================================================================================================================
| 1| """
| 2| Given an array with n objects colored red,
| 3| white or blue, sort them so that objects of the same color
| 4| are adjacent, with the colors in the order red, white and blue.
| 5|
| 6| Here, we will use the integers 0, 1, and 2 to represent
| 7| the color red, white, and blue respectively.
| 8|
| 9| Note:
| 10| You are not suppose to use the library's sort function for this problem.
| 11| """
| 12|
| 13|
| 14| def sort_colors(nums):
| 15| i = j = 0
| 16| for k in range(len(nums)):
| 17| v = nums[k]
| 18| nums[k] = 2
| 19| if v < 2:
| 20| nums[j] = 1
| 21| j += 1
| 22| if v == 0:
| 23| nums[i] = 0
| 24| i += 1
| 25|
| 26|
| 27| if __name__ == "__main__":
| 28| nums = [0, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, 2]
| 29| sort_colors(nums)
| 30| print(nums)
#========================================================================================================================
# :: sort/heap_sort.py ::
#========================================================================================================================
| 1| def max_heap_sort(arr, simulation=False):
| 2| """ Heap Sort that uses a max heap to sort an array in ascending order
| 3| Complexity: O(n log(n))
| 4| """
| 5| iteration = 0
| 6| if simulation:
| 7| print("iteration",iteration,":",*arr)
| 8|
| 9| for i in range(len(arr) - 1, 0, -1):
| 10| iteration = max_heapify(arr, i, simulation, iteration)
| 11|
| 12| if simulation:
| 13| iteration = iteration + 1
| 14| print("iteration",iteration,":",*arr)
| 15| return arr
| 16|
| 17|
| 18| def max_heapify(arr, end, simulation, iteration):
| 19| """ Max heapify helper for max_heap_sort
| 20| """
| 21| last_parent = (end - 1) // 2
| 22|
| 23| # Iterate from last parent to first
| 24| for parent in range(last_parent, -1, -1):
| 25| current_parent = parent
| 26|
| 27| # Iterate from current_parent to last_parent
| 28| while current_parent <= last_parent:
| 29| # Find greatest child of current_parent
| 30| child = 2 * current_parent + 1
| 31| if child + 1 <= end and arr[child] < arr[child + 1]:
| 32| child = child + 1
| 33|
| 34| # Swap if child is greater than parent
| 35| if arr[child] > arr[current_parent]:
| 36| arr[current_parent], arr[child] = arr[child], arr[current_parent]
| 37| current_parent = child
| 38| if simulation:
| 39| iteration = iteration + 1
| 40| print("iteration",iteration,":",*arr)
| 41| # If no swap occured, no need to keep iterating
| 42| else:
| 43| break
| 44| arr[0], arr[end] = arr[end], arr[0]
| 45| return iteration
| 46|
| 47| def min_heap_sort(arr, simulation=False):
| 48| """ Heap Sort that uses a min heap to sort an array in ascending order
| 49| Complexity: O(n log(n))
| 50| """
| 51| iteration = 0
| 52| if simulation:
| 53| print("iteration",iteration,":",*arr)
| 54|
| 55| for i in range(0, len(arr) - 1):
| 56| iteration = min_heapify(arr, i, simulation, iteration)
| 57|
| 58| return arr
| 59|
| 60|
| 61| def min_heapify(arr, start, simulation, iteration):
| 62| """ Min heapify helper for min_heap_sort
| 63| """
| 64| # Offset last_parent by the start (last_parent calculated as if start index was 0)
| 65| # All array accesses need to be offset by start
| 66| end = len(arr) - 1
| 67| last_parent = (end - start - 1) // 2
| 68|
| 69| # Iterate from last parent to first
| 70| for parent in range(last_parent, -1, -1):
| 71| current_parent = parent
| 72|
| 73| # Iterate from current_parent to last_parent
| 74| while current_parent <= last_parent:
| 75| # Find lesser child of current_parent
| 76| child = 2 * current_parent + 1
| 77| if child + 1 <= end - start and arr[child + start] > arr[
| 78| child + 1 + start]:
| 79| child = child + 1
| 80|
| 81| # Swap if child is less than parent
| 82| if arr[child + start] < arr[current_parent + start]:
| 83| arr[current_parent + start], arr[child + start] = \
| 84| arr[child + start], arr[current_parent + start]
| 85| current_parent = child
| 86| if simulation:
| 87| iteration = iteration + 1
| 88| print("iteration",iteration,":",*arr)
| 89| # If no swap occured, no need to keep iterating
| 90| else:
| 91| break
| 92| return iteration
#========================================================================================================================
# :: sort/cocktail_shaker_sort.py ::
#========================================================================================================================
| 1| def cocktail_shaker_sort(arr):
| 2| """
| 3| Cocktail_shaker_sort
| 4| Sorting a given array
| 5| mutation of bubble sort
| 6|
| 7| reference: https://en.wikipedia.org/wiki/Cocktail_shaker_sort
| 8|
| 9| Worst-case performance: O(N^2)
| 10| """
| 11|
| 12| def swap(i, j):
| 13| arr[i], arr[j] = arr[j], arr[i]
| 14|
| 15| n = len(arr)
| 16| swapped = True
| 17| while swapped:
| 18| swapped = False
| 19| for i in range(1, n):
| 20| if arr[i - 1] > arr[i]:
| 21| swap(i - 1, i)
| 22| swapped = True
| 23| if swapped == False:
| 24| return arr
| 25| swapped = False
| 26| for i in range(n-1,0,-1):
| 27| if arr[i - 1] > arr[i]:
| 28| swap(i - 1, i)
| 29| swapped = True
| 30| return arr
#========================================================================================================================
# :: sort/comb_sort.py ::
#========================================================================================================================
| 1| """
| 2|
| 3| https://en.wikipedia.org/wiki/Comb_sort
| 4|
| 5| Worst-case performance: O(N^2)
| 6|
| 7| """
| 8|
| 9|
| 10| def comb_sort(arr):
| 11| def swap(i, j):
| 12| arr[i], arr[j] = arr[j], arr[i]
| 13|
| 14| n = len(arr)
| 15| gap = n
| 16| shrink = 1.3
| 17| sorted = False
| 18| while not sorted:
| 19| gap = int(gap / shrink)
| 20| if gap > 1:
| 21| sorted = False
| 22| else:
| 23| gap = 1
| 24| sorted = True
| 25|
| 26| i = 0
| 27| while i + gap < n:
| 28| if arr[i] > arr[i + gap]:
| 29| swap(i, i + gap)
| 30| sorted = False
| 31| i = i + 1
| 32| return arr
#========================================================================================================================
# :: sort/meeting_rooms.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of meeting time intervals consisting of
| 3| start and end times [[s1,e1],[s2,e2],...] (si < ei),
| 4| determine if a person could attend all meetings.
| 5|
| 6| For example,
| 7| Given [[0, 30],[5, 10],[15, 20]],
| 8| return false.
| 9| """
| 10|
| 11|
| 12| def can_attend_meetings(intervals):
| 13| """
| 14| :type intervals: List[Interval]
| 15| :rtype: bool
| 16| """
| 17| intervals = sorted(intervals, key=lambda x: x.start)
| 18| for i in range(1, len(intervals)):
| 19| if intervals[i].start < intervals[i - 1].end:
| 20| return False
| 21| return True
#========================================================================================================================
# :: sort/bitonic_sort.py ::
#========================================================================================================================
| 1| def bitonic_sort(arr, reverse=False):
| 2| """
| 3| bitonic sort is sorting algorithm to use multiple process, but this code not containing parallel process
| 4| It can sort only array that sizes power of 2
| 5| It can sort array in both increasing order and decreasing order by giving argument true(increasing) and false(decreasing)
| 6|
| 7| Worst-case in parallel: O(log(n)^2)
| 8| Worst-case in non-parallel: O(nlog(n)^2)
| 9|
| 10| reference: https://en.wikipedia.org/wiki/Bitonic_sorter
| 11| """
| 12| def compare(arr, reverse):
| 13| n = len(arr)//2
| 14| for i in range(n):
| 15| if reverse != (arr[i] > arr[i+n]):
| 16| arr[i], arr[i+n] = arr[i+n], arr[i]
| 17| return arr
| 18|
| 19| def bitonic_merge(arr, reverse):
| 20| n = len(arr)
| 21|
| 22| if n <= 1:
| 23| return arr
| 24|
| 25| arr = compare(arr, reverse)
| 26| left = bitonic_merge(arr[:n // 2], reverse)
| 27| right = bitonic_merge(arr[n // 2:], reverse)
| 28| return left + right
| 29|
| 30| #end of function(compare and bitionic_merge) definition
| 31| n = len(arr)
| 32| if n <= 1:
| 33| return arr
| 34| # checks if n is power of two
| 35| if not (n and (not(n & (n - 1))) ):
| 36| raise ValueError("the size of input should be power of two")
| 37|
| 38| left = bitonic_sort(arr[:n // 2], True)
| 39| right = bitonic_sort(arr[n // 2:], False)
| 40|
| 41| arr = bitonic_merge(left + right, reverse)
| 42|
| 43| return arr
#========================================================================================================================
# :: backtrack/find_words.py ::
#========================================================================================================================
| 1| '''
| 2| Given a matrix of words and a list of words to search,
| 3| return a list of words that exists in the board
| 4| This is Word Search II on LeetCode
| 5|
| 6| board = [
| 7| ['o','a','a','n'],
| 8| ['e','t','a','e'],
| 9| ['i','h','k','r'],
| 10| ['i','f','l','v']
| 11| ]
| 12|
| 13| words = ["oath","pea","eat","rain"]
| 14| '''
| 15|
| 16|
| 17| def find_words(board, words):
| 18|
| 19| def backtrack(board, i, j, trie, pre, used, result):
| 20| '''
| 21| backtrack tries to build each words from
| 22| the board and return all words found
| 23|
| 24| @param: board, the passed in board of characters
| 25| @param: i, the row index
| 26| @param: j, the column index
| 27| @param: trie, a trie of the passed in words
| 28| @param: pre, a buffer of currently build string that differs
| 29| by recursion stack
| 30| @param: used, a replica of the board except in booleans
| 31| to state whether a character has been used
| 32| @param: result, the resulting set that contains all words found
| 33|
| 34| @return: list of words found
| 35| '''
| 36|
| 37| if '#' in trie:
| 38| result.add(pre)
| 39|
| 40| if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]):
| 41| return
| 42|
| 43| if not used[i][j] and board[i][j] in trie:
| 44| used[i][j] = True
| 45| backtrack(board, i+1, j, trie[board[i][j]],
| 46| pre+board[i][j], used, result)
| 47| backtrack(board, i, j+1, trie[board[i][j]],
| 48| pre+board[i][j], used, result)
| 49| backtrack(board, i-1, j, trie[board[i][j]],
| 50| pre+board[i][j], used, result)
| 51| backtrack(board, i, j-1, trie[board[i][j]],
| 52| pre+board[i][j], used, result)
| 53| used[i][j] = False
| 54|
| 55| # make a trie structure that is essentially dictionaries of dictionaries
| 56| # that map each character to a potential next character
| 57| trie = {}
| 58| for word in words:
| 59| curr_trie = trie
| 60| for char in word:
| 61| if char not in curr_trie:
| 62| curr_trie[char] = {}
| 63| curr_trie = curr_trie[char]
| 64| curr_trie['#'] = '#'
| 65|
| 66| # result is a set of found words since we do not want repeats
| 67| result = set()
| 68| used = [[False]*len(board[0]) for _ in range(len(board))]
| 69|
| 70| for i in range(len(board)):
| 71| for j in range(len(board[0])):
| 72| backtrack(board, i, j, trie, '', used, result)
| 73| return list(result)
#========================================================================================================================
# :: backtrack/permute.py ::
#========================================================================================================================
| 1| """
| 2| Given a collection of distinct numbers, return all possible permutations.
| 3|
| 4| For example,
| 5| [1,2,3] have the following permutations:
| 6| [
| 7| [1,2,3],
| 8| [1,3,2],
| 9| [2,1,3],
| 10| [2,3,1],
| 11| [3,1,2],
| 12| [3,2,1]
| 13| ]
| 14| """
| 15|
| 16|
| 17| def permute(elements):
| 18| """
| 19| returns a list with the permuations.
| 20| """
| 21| if len(elements) <= 1:
| 22| return [elements]
| 23| else:
| 24| tmp = []
| 25| for perm in permute(elements[1:]):
| 26| for i in range(len(elements)):
| 27| tmp.append(perm[:i] + elements[0:1] + perm[i:])
| 28| return tmp
| 29|
| 30|
| 31| def permute_iter(elements):
| 32| """
| 33| iterator: returns a perumation by each call.
| 34| """
| 35| if len(elements) <= 1:
| 36| yield elements
| 37| else:
| 38| for perm in permute_iter(elements[1:]):
| 39| for i in range(len(elements)):
| 40| yield perm[:i] + elements[0:1] + perm[i:]
| 41|
| 42|
| 43| # DFS Version
| 44| def permute_recursive(nums):
| 45| def dfs(res, nums, path):
| 46| if not nums:
| 47| res.append(path)
| 48| for i in range(len(nums)):
| 49| print(nums[:i]+nums[i+1:])
| 50| dfs(res, nums[:i]+nums[i+1:], path+[nums[i]])
| 51|
| 52| res = []
| 53| dfs(res, nums, [])
| 54| return res
#========================================================================================================================
# :: backtrack/subsets_unique.py ::
#========================================================================================================================
| 1| """
| 2| Given a collection of integers that might contain duplicates, nums,
| 3| return all possible subsets.
| 4|
| 5| Note: The solution set must not contain duplicate subsets.
| 6|
| 7| For example,
| 8| If nums = [1,2,2], a solution is:
| 9|
| 10| [
| 11| [2],
| 12| [1],
| 13| [1,2,2],
| 14| [2,2],
| 15| [1,2],
| 16| []
| 17| ]
| 18| """
| 19|
| 20|
| 21| def subsets_unique(nums):
| 22|
| 23| def backtrack(res, nums, stack, pos):
| 24| if pos == len(nums):
| 25| res.add(tuple(stack))
| 26| else:
| 27| # take
| 28| stack.append(nums[pos])
| 29| backtrack(res, nums, stack, pos+1)
| 30| stack.pop()
| 31|
| 32| # don't take
| 33| backtrack(res, nums, stack, pos+1)
| 34|
| 35| res = set()
| 36| backtrack(res, nums, [], 0)
| 37| return list(res)
#========================================================================================================================
# :: backtrack/array_sum_combinations.py ::
#========================================================================================================================
| 1| """
| 2| WAP to take one element from each of the array add it to the target sum.
| 3| Print all those three-element combinations.
| 4|
| 5| /*
| 6| A = [1, 2, 3, 3]
| 7| B = [2, 3, 3, 4]
| 8| C = [2, 3, 3, 4]
| 9| target = 7
| 10| */
| 11|
| 12| Result:
| 13| [[1, 2, 4], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 4, 2],
| 14| [2, 2, 3], [2, 2, 3], [2, 3, 2], [2, 3, 2], [3, 2, 2], [3, 2, 2]]
| 15| """
| 16| import itertools
| 17| from functools import partial
| 18|
| 19|
| 20| def array_sum_combinations(A, B, C, target):
| 21|
| 22| def over(constructed_sofar):
| 23| sum = 0
| 24| to_stop, reached_target = False, False
| 25| for elem in constructed_sofar:
| 26| sum += elem
| 27| if sum >= target or len(constructed_sofar) >= 3:
| 28| to_stop = True
| 29| if sum == target and 3 == len(constructed_sofar):
| 30| reached_target = True
| 31| return to_stop, reached_target
| 32|
| 33| def construct_candidates(constructed_sofar):
| 34| array = A
| 35| if 1 == len(constructed_sofar):
| 36| array = B
| 37| elif 2 == len(constructed_sofar):
| 38| array = C
| 39| return array
| 40|
| 41| def backtrack(constructed_sofar=[], res=[]):
| 42| to_stop, reached_target = over(constructed_sofar)
| 43| if to_stop:
| 44| if reached_target:
| 45| res.append(constructed_sofar)
| 46| return
| 47| candidates = construct_candidates(constructed_sofar)
| 48|
| 49| for candidate in candidates:
| 50| constructed_sofar.append(candidate)
| 51| backtrack(constructed_sofar[:], res)
| 52| constructed_sofar.pop()
| 53|
| 54| res = []
| 55| backtrack([], res)
| 56| return res
| 57|
| 58|
| 59| def unique_array_sum_combinations(A, B, C, target):
| 60| """
| 61| 1. Sort all the arrays - a,b,c. - This improves average time complexity.
| 62| 2. If c[i] < Sum, then look for Sum - c[i] in array a and b.
| 63| When pair found, insert c[i], a[j] & b[k] into the result list.
| 64| This can be done in O(n).
| 65| 3. Keep on doing the above procedure while going through complete c array.
| 66|
| 67| Complexity: O(n(m+p))
| 68| """
| 69| def check_sum(n, *nums):
| 70| if sum(x for x in nums) == n:
| 71| return (True, nums)
| 72| else:
| 73| return (False, nums)
| 74|
| 75| pro = itertools.product(A, B, C)
| 76| func = partial(check_sum, target)
| 77| sums = list(itertools.starmap(func, pro))
| 78|
| 79| res = set()
| 80| for s in sums:
| 81| if s[0] is True and s[1] not in res:
| 82| res.add(s[1])
| 83|
| 84| return list(res)
#========================================================================================================================
# :: backtrack/anagram.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings, determine if they are equal after reordering.
| 3|
| 4| Examples:
| 5| "apple", "pleap" -> True
| 6| "apple", "cherry" -> False
| 7| """
| 8|
| 9|
| 10| def anagram(s1, s2):
| 11| c1 = [0] * 26
| 12| c2 = [0] * 26
| 13|
| 14| for c in s1:
| 15| pos = ord(c)-ord('a')
| 16| c1[pos] = c1[pos] + 1
| 17|
| 18| for c in s2:
| 19| pos = ord(c)-ord('a')
| 20| c2[pos] = c2[pos] + 1
| 21|
| 22| return c1 == c2
#========================================================================================================================
# :: backtrack/__init__.py ::
#========================================================================================================================
| 1| from .add_operators import *
| 2| from .anagram import *
| 3| from .array_sum_combinations import *
| 4| from .combination_sum import *
| 5| from .factor_combinations import *
| 6| from .find_words import *
| 7| from .generate_abbreviations import *
| 8| from .generate_parenthesis import *
| 9| from .letter_combination import *
| 10| from .palindrome_partitioning import *
| 11| from .pattern_match import *
| 12| from .permute_unique import *
| 13| from .permute import *
| 14| from .subsets_unique import *
| 15| from .subsets import *
#========================================================================================================================
# :: backtrack/permute_unique.py ::
#========================================================================================================================
| 1| """
| 2| Given a collection of numbers that might contain duplicates,
| 3| return all possible unique permutations.
| 4|
| 5| For example,
| 6| [1,1,2] have the following unique permutations:
| 7| [
| 8| [1,1,2],
| 9| [1,2,1],
| 10| [2,1,1]
| 11| ]
| 12| """
| 13|
| 14|
| 15| def permute_unique(nums):
| 16| perms = [[]]
| 17| for n in nums:
| 18| new_perms = []
| 19| for l in perms:
| 20| for i in range(len(l)+1):
| 21| new_perms.append(l[:i]+[n]+l[i:])
| 22| if i < len(l) and l[i] == n:
| 23| break # handles duplication
| 24| perms = new_perms
| 25| return perms
#========================================================================================================================
# :: backtrack/letter_combination.py ::
#========================================================================================================================
| 1| """
| 2| Given a digit string, return all possible letter
| 3| combinations that the number could represent.
| 4|
| 5| A mapping of digit to letters (just like on the telephone buttons) is given below:
| 6| 2: "abc"
| 7| 3: "def"
| 8| 4: "ghi"
| 9| 5: "jkl"
| 10| 6: "mno"
| 11| 7: "pqrs"
| 12| 8: "tuv"
| 13| 9: "wxyz"
| 14|
| 15| Input:Digit string "23"
| 16| Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
| 17| """
| 18|
| 19|
| 20| def letter_combinations(digits):
| 21| if digits == "":
| 22| return []
| 23| kmaps = {
| 24| "2": "abc",
| 25| "3": "def",
| 26| "4": "ghi",
| 27| "5": "jkl",
| 28| "6": "mno",
| 29| "7": "pqrs",
| 30| "8": "tuv",
| 31| "9": "wxyz"
| 32| }
| 33| ans = [""]
| 34| for num in digits:
| 35| tmp = []
| 36| for an in ans:
| 37| for char in kmaps[num]:
| 38| tmp.append(an + char)
| 39| ans = tmp
| 40| return ans
#========================================================================================================================
# :: backtrack/factor_combinations.py ::
#========================================================================================================================
| 1| """
| 2| Numbers can be regarded as product of its factors. For example,
| 3|
| 4| 8 = 2 x 2 x 2;
| 5| = 2 x 4.
| 6| Write a function that takes an integer n
| 7| and return all possible combinations of its factors.
| 8|
| 9| Note:
| 10| You may assume that n is always positive.
| 11| Factors should be greater than 1 and less than n.
| 12| Examples:
| 13| input: 1
| 14| output:
| 15| []
| 16| input: 37
| 17| output:
| 18| []
| 19| input: 12
| 20| output:
| 21| [
| 22| [2, 6],
| 23| [2, 2, 3],
| 24| [3, 4]
| 25| ]
| 26| input: 32
| 27| output:
| 28| [
| 29| [2, 16],
| 30| [2, 2, 8],
| 31| [2, 2, 2, 4],
| 32| [2, 2, 2, 2, 2],
| 33| [2, 4, 4],
| 34| [4, 8]
| 35| ]
| 36| """
| 37|
| 38|
| 39| # Iterative:
| 40| def get_factors(n):
| 41| todo, combis = [(n, 2, [])], []
| 42| while todo:
| 43| n, i, combi = todo.pop()
| 44| while i * i <= n:
| 45| if n % i == 0:
| 46| combis.append(combi + [i, n//i])
| 47| todo.append((n//i, i, combi+[i]))
| 48| i += 1
| 49| return combis
| 50|
| 51|
| 52| # Recursive:
| 53| def recursive_get_factors(n):
| 54|
| 55| def factor(n, i, combi, combis):
| 56| while i * i <= n:
| 57| if n % i == 0:
| 58| combis.append(combi + [i, n//i]),
| 59| factor(n//i, i, combi+[i], combis)
| 60| i += 1
| 61| return combis
| 62|
| 63| return factor(n, 2, [], [])
#========================================================================================================================
# :: backtrack/add_operators.py ::
#========================================================================================================================
| 1| """
| 2| Given a string that contains only digits 0-9 and a target value,
| 3| return all possibilities to add binary operators (not unary) +, -, or *
| 4| between the digits so they prevuate to the target value.
| 5|
| 6| Examples:
| 7| "123", 6 -> ["1+2+3", "1*2*3"]
| 8| "232", 8 -> ["2*3+2", "2+3*2"]
| 9| "105", 5 -> ["1*0+5","10-5"]
| 10| "00", 0 -> ["0+0", "0-0", "0*0"]
| 11| "3456237490", 9191 -> []
| 12| """
| 13|
| 14|
| 15| def add_operators(num, target):
| 16| """
| 17| :type num: str
| 18| :type target: int
| 19| :rtype: List[str]
| 20| """
| 21|
| 22| def dfs(res, path, num, target, pos, prev, multed):
| 23| if pos == len(num):
| 24| if target == prev:
| 25| res.append(path)
| 26| return
| 27| for i in range(pos, len(num)):
| 28| if i != pos and num[pos] == '0': # all digits have to be used
| 29| break
| 30| cur = int(num[pos:i+1])
| 31| if pos == 0:
| 32| dfs(res, path + str(cur), num, target, i+1, cur, cur)
| 33| else:
| 34| dfs(res, path + "+" + str(cur), num, target,
| 35| i+1, prev + cur, cur)
| 36| dfs(res, path + "-" + str(cur), num, target,
| 37| i+1, prev - cur, -cur)
| 38| dfs(res, path + "*" + str(cur), num, target,
| 39| i+1, prev - multed + multed * cur, multed * cur)
| 40|
| 41| res = []
| 42| if not num:
| 43| return res
| 44| dfs(res, "", num, target, 0, 0, 0)
| 45| return res
#========================================================================================================================
# :: backtrack/subsets.py ::
#========================================================================================================================
| 1| """
| 2| Given a set of distinct integers, nums, return all possible subsets.
| 3|
| 4| Note: The solution set must not contain duplicate subsets.
| 5|
| 6| For example,
| 7| If nums = [1,2,3], a solution is:
| 8|
| 9| [
| 10| [3],
| 11| [1],
| 12| [2],
| 13| [1,2,3],
| 14| [1,3],
| 15| [2,3],
| 16| [1,2],
| 17| []
| 18| ]
| 19| """
| 20|
| 21|
| 22| def subsets(nums):
| 23| """
| 24| O(2**n)
| 25| """
| 26| def backtrack(res, nums, stack, pos):
| 27| if pos == len(nums):
| 28| res.append(list(stack))
| 29| else:
| 30| # take nums[pos]
| 31| stack.append(nums[pos])
| 32| backtrack(res, nums, stack, pos+1)
| 33| stack.pop()
| 34| # dont take nums[pos]
| 35| backtrack(res, nums, stack, pos+1)
| 36|
| 37| res = []
| 38| backtrack(res, nums, [], 0)
| 39| return res
| 40|
| 41|
| 42| """
| 43| simplified backtrack
| 44|
| 45| def backtrack(res, nums, cur, pos):
| 46| if pos >= len(nums):
| 47| res.append(cur)
| 48| else:
| 49| backtrack(res, nums, cur+[nums[pos]], pos+1)
| 50| backtrack(res, nums, cur, pos+1)
| 51| """
| 52|
| 53|
| 54| # Iteratively
| 55| def subsets_v2(nums):
| 56| res = [[]]
| 57| for num in sorted(nums):
| 58| res += [item+[num] for item in res]
| 59| return res
#========================================================================================================================
# :: backtrack/generate_parenthesis.py ::
#========================================================================================================================
| 1| """
| 2| Given n pairs of parentheses, write a function to generate
| 3| all combinations of well-formed parentheses.
| 4|
| 5| For example, given n = 3, a solution set is:
| 6|
| 7| [
| 8| "((()))",
| 9| "(()())",
| 10| "(())()",
| 11| "()(())",
| 12| "()()()"
| 13| ]
| 14| """
| 15|
| 16|
| 17| def generate_parenthesis_v1(n):
| 18| def add_pair(res, s, left, right):
| 19| if left == 0 and right == 0:
| 20| res.append(s)
| 21| return
| 22| if right > 0:
| 23| add_pair(res, s + ")", left, right - 1)
| 24| if left > 0:
| 25| add_pair(res, s + "(", left - 1, right + 1)
| 26|
| 27| res = []
| 28| add_pair(res, "", n, 0)
| 29| return res
| 30|
| 31|
| 32| def generate_parenthesis_v2(n):
| 33| def add_pair(res, s, left, right):
| 34| if left == 0 and right == 0:
| 35| res.append(s)
| 36| if left > 0:
| 37| add_pair(res, s + "(", left - 1, right)
| 38| if right > 0 and left < right:
| 39| add_pair(res, s + ")", left, right - 1)
| 40|
| 41| res = []
| 42| add_pair(res, "", n, n)
| 43| return res
#========================================================================================================================
# :: backtrack/generate_abbreviations.py ::
#========================================================================================================================
| 1| """
| 2| given input word, return the list of abbreviations.
| 3| ex)
| 4| word => ['word', 'wor1', 'wo1d', 'wo2', 'w1rd', 'w1r1', 'w2d', 'w3', '1ord', '1or1', '1o1d', '1o2', '2rd', '2r1', '3d', '4']
| 5| """
| 6|
| 7|
| 8| def generate_abbreviations(word):
| 9|
| 10| def backtrack(result, word, pos, count, cur):
| 11| if pos == len(word):
| 12| if count > 0:
| 13| cur += str(count)
| 14| result.append(cur)
| 15| return
| 16|
| 17| if count > 0: # add the current word
| 18| backtrack(result, word, pos+1, 0, cur+str(count)+word[pos])
| 19| else:
| 20| backtrack(result, word, pos+1, 0, cur+word[pos])
| 21| # skip the current word
| 22| backtrack(result, word, pos+1, count+1, cur)
| 23|
| 24| result = []
| 25| backtrack(result, word, 0, 0, "")
| 26| return result
#========================================================================================================================
# :: backtrack/pattern_match.py ::
#========================================================================================================================
| 1| """
| 2| Given a pattern and a string str,
| 3| find if str follows the same pattern.
| 4|
| 5| Here follow means a full match, such that there is a bijection between
| 6| a letter in pattern and a non-empty substring in str.
| 7|
| 8| Examples:
| 9| pattern = "abab", str = "redblueredblue" should return true.
| 10| pattern = "aaaa", str = "asdasdasdasd" should return true.
| 11| pattern = "aabb", str = "xyzabcxzyabc" should return false.
| 12| Notes:
| 13| You may assume both pattern and str contains only lowercase letters.
| 14| """
| 15|
| 16|
| 17| def pattern_match(pattern, string):
| 18| """
| 19| :type pattern: str
| 20| :type string: str
| 21| :rtype: bool
| 22| """
| 23| def backtrack(pattern, string, dic):
| 24|
| 25| if len(pattern) == 0 and len(string) > 0:
| 26| return False
| 27|
| 28| if len(pattern) == len(string) == 0:
| 29| return True
| 30|
| 31| for end in range(1, len(string)-len(pattern)+2):
| 32| if pattern[0] not in dic and string[:end] not in dic.values():
| 33| dic[pattern[0]] = string[:end]
| 34| if backtrack(pattern[1:], string[end:], dic):
| 35| return True
| 36| del dic[pattern[0]]
| 37| elif pattern[0] in dic and dic[pattern[0]] == string[:end]:
| 38| if backtrack(pattern[1:], string[end:], dic):
| 39| return True
| 40| return False
| 41|
| 42| return backtrack(pattern, string, {})
#========================================================================================================================
# :: backtrack/palindrome_partitioning.py ::
#========================================================================================================================
| 1| """ It looks like you need to be looking not for all palindromic substrings,
| 2| but rather for all the ways you can divide the input string
| 3| up into palindromic substrings.
| 4| (There's always at least one way,
| 5| since one-character substrings are always palindromes.)
| 6|
| 7| ex)
| 8| 'abcbab' => [['abcba', 'b'], ['a', 'bcb', 'a', 'b'], ['a', 'b', 'c', 'bab'], ['a', 'b', 'c', 'b', 'a', 'b']]
| 9| """
| 10|
| 11|
| 12| def palindromic_substrings(s):
| 13| if not s:
| 14| return [[]]
| 15| results = []
| 16| for i in range(len(s), 0, -1):
| 17| sub = s[:i]
| 18| if sub == sub[::-1]:
| 19| for rest in palindromic_substrings(s[i:]):
| 20| results.append([sub] + rest)
| 21| return results
| 22|
| 23|
| 24| """
| 25| There's two loops.
| 26| The outer loop checks each length of initial substring
| 27| (in descending length order) to see if it is a palindrome.
| 28| If so, it recurses on the rest of the string and loops over the returned
| 29| values, adding the initial substring to
| 30| each item before adding it to the results.
| 31| """
| 32|
| 33|
| 34| def palindromic_substrings_iter(s):
| 35| """
| 36| A slightly more Pythonic approach with a recursive generator
| 37| """
| 38| if not s:
| 39| yield []
| 40| return
| 41| for i in range(len(s), 0, -1):
| 42| sub = s[:i]
| 43| if sub == sub[::-1]:
| 44| for rest in palindromic_substrings_iter(s[i:]):
| 45| yield [sub] + rest
#========================================================================================================================
# :: backtrack/combination_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given a set of candidate numbers (C) (without duplicates) and a target number
| 3| (T), find all unique combinations in C where the candidate numbers sums to T.
| 4|
| 5| The same repeated number may be chosen from C unlimited number of times.
| 6|
| 7| Note:
| 8| All numbers (including target) will be positive integers.
| 9| The solution set must not contain duplicate combinations.
| 10| For example, given candidate set [2, 3, 6, 7] and target 7,
| 11| A solution set is:
| 12| [
| 13| [7],
| 14| [2, 2, 3]
| 15| ]
| 16| """
| 17|
| 18|
| 19| def combination_sum(candidates, target):
| 20|
| 21| def dfs(nums, target, index, path, res):
| 22| if target < 0:
| 23| return # backtracking
| 24| if target == 0:
| 25| res.append(path)
| 26| return
| 27| for i in range(index, len(nums)):
| 28| dfs(nums, target-nums[i], i, path+[nums[i]], res)
| 29|
| 30| res = []
| 31| candidates.sort()
| 32| dfs(candidates, target, 0, [], res)
| 33| return res
#========================================================================================================================
# :: linkedlist/rotate_list.py ::
#========================================================================================================================
| 1| """
| 2| Given a list, rotate the list to the right by k places,
| 3| where k is non-negative.
| 4|
| 5| For example:
| 6| Given 1->2->3->4->5->NULL and k = 2,
| 7| return 4->5->1->2->3->NULL.
| 8| """
| 9|
| 10| # Definition for singly-linked list.
| 11| # class ListNode(object):
| 12| # def __init__(self, x):
| 13| # self.val = x
| 14| # self.next = None
| 15|
| 16|
| 17| def rotate_right(head, k):
| 18| """
| 19| :type head: ListNode
| 20| :type k: int
| 21| :rtype: ListNode
| 22| """
| 23| if not head or not head.next:
| 24| return head
| 25| current = head
| 26| length = 1
| 27| # count length of the list
| 28| while current.next:
| 29| current = current.next
| 30| length += 1
| 31| # make it circular
| 32| current.next = head
| 33| k = k % length
| 34| # rotate until length-k
| 35| for i in range(length-k):
| 36| current = current.next
| 37| head = current.next
| 38| current.next = None
| 39| return head
#========================================================================================================================
# :: linkedlist/partition.py ::
#========================================================================================================================
| 1| """
| 2| Write code to partition a linked list around a value x, such that all nodes less
| 3| than x come before all nodes greater than or equal to x. If x is contained
| 4| within the list, the values of x only need to be after the elements less than x.
| 5| The partition element x can appear anywhere in the "right partition";
| 6| it does not need to appear between the left and right partitions.
| 7|
| 8| 3 -> 5 -> 8 -> 5 -> 10 -> 2 -> 1 [partition=5]
| 9| 3 -> 1 -> 2 -> 10 -> 5 -> 5 -> 8
| 10|
| 11| We assume the values of all linked list nodes are int and that x in an int.
| 12| """
| 13|
| 14|
| 15| class Node():
| 16| def __init__(self, val=None):
| 17| self.val = int(val)
| 18| self.next = None
| 19|
| 20|
| 21| def print_linked_list(head):
| 22| string = ""
| 23| while head.next:
| 24| string += str(head.val) + " -> "
| 25| head = head.next
| 26| string += str(head.val)
| 27| print(string)
| 28|
| 29|
| 30| def partition(head, x):
| 31| left = None
| 32| right = None
| 33| prev = None
| 34| current = head
| 35| while current:
| 36| if int(current.val) >= x:
| 37| if not right:
| 38| right = current
| 39| else:
| 40| if not left:
| 41| left = current
| 42| else:
| 43| prev.next = current.next
| 44| left.next = current
| 45| left = current
| 46| left.next = right
| 47| if prev and prev.next is None:
| 48| break
| 49| # cache previous value in case it needs to be pointed elsewhere
| 50| prev = current
| 51| current = current.next
| 52|
| 53|
| 54| def test():
| 55| a = Node("3")
| 56| b = Node("5")
| 57| c = Node("8")
| 58| d = Node("5")
| 59| e = Node("10")
| 60| f = Node("2")
| 61| g = Node("1")
| 62|
| 63| a.next = b
| 64| b.next = c
| 65| c.next = d
| 66| d.next = e
| 67| e.next = f
| 68| f.next = g
| 69|
| 70| print_linked_list(a)
| 71| partition(a, 5)
| 72| print_linked_list(a)
| 73|
| 74|
| 75| if __name__ == '__main__':
| 76| test()
#========================================================================================================================
# :: linkedlist/reverse.py ::
#========================================================================================================================
| 1| """
| 2| Reverse a singly linked list. For example:
| 3|
| 4| 1 --> 2 --> 3 --> 4
| 5| After reverse:
| 6| 4 --> 3 --> 2 --> 1
| 7| """
| 8| #
| 9| # Iterative solution
| 10| # T(n)- O(n)
| 11| #
| 12| def reverse_list(head):
| 13| """
| 14| :type head: ListNode
| 15| :rtype: ListNode
| 16| """
| 17| if not head or not head.next:
| 18| return head
| 19| prev = None
| 20| while head:
| 21| current = head
| 22| head = head.next
| 23| current.next = prev
| 24| prev = current
| 25| return prev
| 26|
| 27|
| 28| #
| 29| # Recursive solution
| 30| # T(n)- O(n)
| 31| #
| 32| def reverse_list_recursive(head):
| 33| """
| 34| :type head: ListNode
| 35| :rtype: ListNode
| 36| """
| 37| if head is None or head.next is None:
| 38| return head
| 39| p = head.next
| 40| head.next = None
| 41| revrest = reverse_list_recursive(p)
| 42| p.next = head
| 43| return revrest
#========================================================================================================================
# :: linkedlist/merge_two_list.py ::
#========================================================================================================================
| 1| """
| 2| Merge two sorted linked lists and return it as a new list. The new list should
| 3| be made by splicing together the nodes of the first two lists.
| 4|
| 5| For example:
| 6| Input: 1->2->4, 1->3->4
| 7| Output: 1->1->2->3->4->4
| 8| """
| 9| class Node:
| 10|
| 11| def __init__(self, x):
| 12| self.val = x
| 13| self.next = None
| 14|
| 15| def merge_two_list(l1, l2):
| 16| ret = cur = Node(0)
| 17| while l1 and l2:
| 18| if l1.val < l2.val:
| 19| cur.next = l1
| 20| l1 = l1.next
| 21| else:
| 22| cur.next = l2
| 23| l2 = l2.next
| 24| cur = cur.next
| 25| cur.next = l1 or l2
| 26| return ret.next
| 27|
| 28| # recursively
| 29| def merge_two_list_recur(l1, l2):
| 30| if not l1 or not l2:
| 31| return l1 or l2
| 32| if l1.val < l2.val:
| 33| l1.next = merge_two_list_recur(l1.next, l2)
| 34| return l1
| 35| else:
| 36| l2.next = merge_two_list_recur(l1, l2.next)
| 37| return l2
#========================================================================================================================
# :: linkedlist/delete_node.py ::
#========================================================================================================================
| 1| """
| 2| Write a function to delete a node (except the tail)
| 3| in a singly linked list, given only access to that node.
| 4|
| 5| Supposed the linked list is 1 -> 2 -> 3 -> 4 and
| 6| you are given the third node with value 3,
| 7| the linked list should become 1 -> 2 -> 4 after calling your function.
| 8| """
| 9| import unittest
| 10|
| 11|
| 12| class Node:
| 13| def __init__(self, x):
| 14| self.val = x
| 15| self.next = None
| 16|
| 17|
| 18| def delete_node(node):
| 19| if node is None or node.next is None:
| 20| raise ValueError
| 21| node.val = node.next.val
| 22| node.next = node.next.next
| 23|
| 24|
| 25| class TestSuite(unittest.TestCase):
| 26|
| 27| def test_delete_node(self):
| 28|
| 29| # make linkedlist 1 -> 2 -> 3 -> 4
| 30| head = Node(1)
| 31| curr = head
| 32| for i in range(2, 6):
| 33| curr.next = Node(i)
| 34| curr = curr.next
| 35|
| 36| # node3 = 3
| 37| node3 = head.next.next
| 38|
| 39| # after delete_node => 1 -> 2 -> 4
| 40| delete_node(node3)
| 41|
| 42| curr = head
| 43| self.assertEqual(1, curr.val)
| 44|
| 45| curr = curr.next
| 46| self.assertEqual(2, curr.val)
| 47|
| 48| curr = curr.next
| 49| self.assertEqual(4, curr.val)
| 50|
| 51| curr = curr.next
| 52| self.assertEqual(5, curr.val)
| 53|
| 54| tail = curr
| 55| self.assertIsNone(tail.next)
| 56|
| 57| self.assertRaises(ValueError, delete_node, tail)
| 58| self.assertRaises(ValueError, delete_node, tail.next)
| 59|
| 60|
| 61| if __name__ == '__main__':
| 62|
| 63| unittest.main()
#========================================================================================================================
# :: linkedlist/is_palindrome.py ::
#========================================================================================================================
| 1| def is_palindrome(head):
| 2| if not head:
| 3| return True
| 4| # split the list to two parts
| 5| fast, slow = head.next, head
| 6| while fast and fast.next:
| 7| fast = fast.next.next
| 8| slow = slow.next
| 9| second = slow.next
| 10| slow.next = None # Don't forget here! But forget still works!
| 11| # reverse the second part
| 12| node = None
| 13| while second:
| 14| nxt = second.next
| 15| second.next = node
| 16| node = second
| 17| second = nxt
| 18| # compare two parts
| 19| # second part has the same or one less node
| 20| while node:
| 21| if node.val != head.val:
| 22| return False
| 23| node = node.next
| 24| head = head.next
| 25| return True
| 26|
| 27|
| 28| def is_palindrome_stack(head):
| 29| if not head or not head.next:
| 30| return True
| 31|
| 32| # 1. Get the midpoint (slow)
| 33| slow = fast = cur = head
| 34| while fast and fast.next:
| 35| fast, slow = fast.next.next, slow.next
| 36|
| 37| # 2. Push the second half into the stack
| 38| stack = [slow.val]
| 39| while slow.next:
| 40| slow = slow.next
| 41| stack.append(slow.val)
| 42|
| 43| # 3. Comparison
| 44| while stack:
| 45| if stack.pop() != cur.val:
| 46| return False
| 47| cur = cur.next
| 48|
| 49| return True
| 50|
| 51|
| 52| def is_palindrome_dict(head):
| 53| """
| 54| This function builds up a dictionary where the keys are the values of the list,
| 55| and the values are the positions at which these values occur in the list.
| 56| We then iterate over the dict and if there is more than one key with an odd
| 57| number of occurrences, bail out and return False.
| 58| Otherwise, we want to ensure that the positions of occurrence sum to the
| 59| value of the length of the list - 1, working from the outside of the list inward.
| 60| For example:
| 61| Input: 1 -> 1 -> 2 -> 3 -> 2 -> 1 -> 1
| 62| d = {1: [0,1,5,6], 2: [2,4], 3: [3]}
| 63| '3' is the middle outlier, 2+4=6, 0+6=6 and 5+1=6 so we have a palindrome.
| 64| """
| 65| if not head or not head.next:
| 66| return True
| 67| d = {}
| 68| pos = 0
| 69| while head:
| 70| if head.val in d.keys():
| 71| d[head.val].append(pos)
| 72| else:
| 73| d[head.val] = [pos]
| 74| head = head.next
| 75| pos += 1
| 76| checksum = pos - 1
| 77| middle = 0
| 78| for v in d.values():
| 79| if len(v) % 2 != 0:
| 80| middle += 1
| 81| else:
| 82| step = 0
| 83| for i in range(0, len(v)):
| 84| if v[i] + v[len(v) - 1 - step] != checksum:
| 85| return False
| 86| step += 1
| 87| if middle > 1:
| 88| return False
| 89| return True
#========================================================================================================================
# :: linkedlist/linkedlist.py ::
#========================================================================================================================
| 1| # Pros
| 2| # Linked Lists have constant-time insertions and deletions in any position,
| 3| # in comparison, arrays require O(n) time to do the same thing.
| 4| # Linked lists can continue to expand without having to specify
| 5| # their size ahead of time (remember our lectures on Array sizing
| 6| # form the Array Sequence section of the course!)
| 7|
| 8| # Cons
| 9| # To access an element in a linked list, you need to take O(k) time
| 10| # to go from the head of the list to the kth element.
| 11| # In contrast, arrays have constant time operations to access
| 12| # elements in an array.
| 13|
| 14| class DoublyLinkedListNode(object):
| 15| def __init__(self, value):
| 16| self.value = value
| 17| self.next = None
| 18| self.prev = None
| 19|
| 20|
| 21| class SinglyLinkedListNode(object):
| 22| def __init__(self, value):
| 23| self.value = value
| 24| self.next = None
#========================================================================================================================
# :: linkedlist/__init__.py ::
#========================================================================================================================
| 1| from .reverse import *
| 2| from .is_sorted import *
| 3| from .remove_range import *
| 4| from .swap_in_pairs import *
| 5| from .rotate_list import *
| 6| from .is_cyclic import *
| 7| from .merge_two_list import *
| 8| from .is_palindrome import *
| 9| from .copy_random_pointer import *
#========================================================================================================================
# :: linkedlist/remove_range.py ::
#========================================================================================================================
| 1| """
| 2| Given a linked list, remove_range function accepts a starting and ending index
| 3| as parameters and removes the elements at those indexes (inclusive) from the list
| 4|
| 5| For example:
| 6| List: [8, 13, 17, 4, 9, 12, 98, 41, 7, 23, 0, 92]
| 7| remove_range(list, 3, 8);
| 8| List becomes: [8, 13, 17, 23, 0, 92]
| 9|
| 10| legal range of the list (0 < start index < end index < size of list).
| 11| """
| 12| def remove_range(head, start, end):
| 13| assert(start <= end)
| 14| # Case: remove node at head
| 15| if start == 0:
| 16| for i in range(0, end+1):
| 17| if head != None:
| 18| head = head.next
| 19| else:
| 20| current = head
| 21| # Move pointer to start position
| 22| for i in range(0,start-1):
| 23| current = current.next
| 24| # Remove data until the end
| 25| for i in range(0, end-start + 1):
| 26| if current != None and current.next != None:
| 27| current.next = current.next.next
| 28| return head
#========================================================================================================================
# :: linkedlist/add_two_numbers.py ::
#========================================================================================================================
| 1| """
| 2| You are given two non-empty linked lists representing
| 3| two non-negative integers. The digits are stored in reverse order
| 4| and each of their nodes contain a single digit.
| 5| Add the two numbers and return it as a linked list.
| 6|
| 7| You may assume the two numbers do not contain any leading zero,
| 8| except the number 0 itself.
| 9|
| 10| Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
| 11| Output: 7 -> 0 -> 8
| 12| """
| 13|
| 14| import unittest
| 15|
| 16|
| 17| class Node:
| 18| def __init__(self, x):
| 19| self.val = x
| 20| self.next = None
| 21|
| 22|
| 23| def add_two_numbers(left: Node, right: Node) -> Node:
| 24| head = Node(0)
| 25| current = head
| 26| sum = 0
| 27| while left or right:
| 28| print("adding: ", left.val, right.val)
| 29| sum //= 10
| 30| if left:
| 31| sum += left.val
| 32| left = left.next
| 33| if right:
| 34| sum += right.val
| 35| right = right.next
| 36| current.next = Node(sum % 10)
| 37| current = current.next
| 38| if sum // 10 == 1:
| 39| current.next = Node(1)
| 40| return head.next
| 41|
| 42|
| 43| def convert_to_list(number: int) -> Node:
| 44| """
| 45| converts a positive integer into a (reversed) linked list.
| 46| for example: give 112
| 47| result 2 -> 1 -> 1
| 48| """
| 49| if number >= 0:
| 50| head = Node(0)
| 51| current = head
| 52| remainder = number % 10
| 53| quotient = number // 10
| 54|
| 55| while quotient != 0:
| 56| current.next = Node(remainder)
| 57| current = current.next
| 58| remainder = quotient % 10
| 59| quotient //= 10
| 60| current.next = Node(remainder)
| 61| return head.next
| 62| else:
| 63| print("number must be positive!")
| 64|
| 65|
| 66| def convert_to_str(l: Node) -> str:
| 67| """
| 68| converts the non-negative number list into a string.
| 69| """
| 70| result = ""
| 71| while l:
| 72| result += str(l.val)
| 73| l = l.next
| 74| return result
| 75|
| 76|
| 77| class TestSuite(unittest.TestCase):
| 78| """
| 79| testsuite for the linked list structure and
| 80| the adding function, above.
| 81| """
| 82|
| 83| def test_convert_to_str(self):
| 84| number1 = Node(2)
| 85| number1.next = Node(4)
| 86| number1.next.next = Node(3)
| 87| self.assertEqual("243", convert_to_str(number1))
| 88|
| 89| def test_add_two_numbers(self):
| 90| # 1. test case
| 91| number1 = Node(2)
| 92| number1.next = Node(4)
| 93| number1.next.next = Node(3)
| 94| number2 = Node(5)
| 95| number2.next = Node(6)
| 96| number2.next.next = Node(4)
| 97| result = convert_to_str(add_two_numbers(number1, number2))
| 98| self.assertEqual("708", result)
| 99|
|100| # 2. test case
|101| number3 = Node(1)
|102| number3.next = Node(1)
|103| number3.next.next = Node(9)
|104| number4 = Node(1)
|105| number4.next = Node(0)
|106| number4.next.next = Node(1)
|107| result = convert_to_str(add_two_numbers(number3, number4))
|108| self.assertEqual("2101", result)
|109|
|110| # 3. test case
|111| number5 = Node(1)
|112| number6 = Node(0)
|113| result = convert_to_str(add_two_numbers(number5, number6))
|114| self.assertEqual("1", result)
|115|
|116| # 4. test case
|117| number7 = Node(9)
|118| number7.next = Node(1)
|119| number7.next.next = Node(1)
|120| number8 = Node(1)
|121| number8.next = Node(0)
|122| number8.next.next = Node(1)
|123| result = convert_to_str(add_two_numbers(number7, number8))
|124| self.assertEqual("022", result)
|125|
|126| def test_convert_to_list(self):
|127| result = convert_to_str(convert_to_list(112))
|128| self.assertEqual("211", result)
|129|
|130|
|131| if __name__ == "__main__":
|132| unittest.main()
#========================================================================================================================
# :: linkedlist/is_cyclic.py ::
#========================================================================================================================
| 1| """
| 2| Given a linked list, determine if it has a cycle in it.
| 3|
| 4| Follow up:
| 5| Can you solve it without using extra space?
| 6| """
| 7| class Node:
| 8|
| 9| def __init__(self, x):
| 10| self.val = x
| 11| self.next = None
| 12|
| 13| def is_cyclic(head):
| 14| """
| 15| :type head: Node
| 16| :rtype: bool
| 17| """
| 18| if not head:
| 19| return False
| 20| runner = head
| 21| walker = head
| 22| while runner.next and runner.next.next:
| 23| runner = runner.next.next
| 24| walker = walker.next
| 25| if runner == walker:
| 26| return True
| 27| return False
#========================================================================================================================
# :: linkedlist/copy_random_pointer.py ::
#========================================================================================================================
| 1| """
| 2| A linked list is given such that each node contains an additional random
| 3| pointer which could point to any node in the list or null.
| 4|
| 5| Return a deep copy of the list.
| 6| """
| 7| from collections import defaultdict
| 8|
| 9|
| 10| class RandomListNode(object):
| 11| def __init__(self, label):
| 12| self.label = label
| 13| self.next = None
| 14| self.random = None
| 15|
| 16|
| 17| def copy_random_pointer_v1(head):
| 18| """
| 19| :type head: RandomListNode
| 20| :rtype: RandomListNode
| 21| """
| 22| dic = dict()
| 23| m = n = head
| 24| while m:
| 25| dic[m] = RandomListNode(m.label)
| 26| m = m.next
| 27| while n:
| 28| dic[n].next = dic.get(n.next)
| 29| dic[n].random = dic.get(n.random)
| 30| n = n.next
| 31| return dic.get(head)
| 32|
| 33|
| 34| # O(n)
| 35| def copy_random_pointer_v2(head):
| 36| """
| 37| :type head: RandomListNode
| 38| :rtype: RandomListNode
| 39| """
| 40| copy = defaultdict(lambda: RandomListNode(0))
| 41| copy[None] = None
| 42| node = head
| 43| while node:
| 44| copy[node].label = node.label
| 45| copy[node].next = copy[node.next]
| 46| copy[node].random = copy[node.random]
| 47| node = node.next
| 48| return copy[head]
#========================================================================================================================
# :: linkedlist/kth_to_last.py ::
#========================================================================================================================
| 1| from typing import Union
| 2|
| 3|
| 4| class Node():
| 5| def __init__(self, val=None):
| 6| self.val = val
| 7| self.next = None
| 8|
| 9|
| 10| def kth_to_last_eval(head, k):
| 11| """
| 12| This is a suboptimal, hacky method using eval(), which is not
| 13| safe for user input. We guard against danger by ensuring k in an int
| 14| """
| 15| if not isinstance(k, int) or not head.val:
| 16| return False
| 17|
| 18| nexts = '.'.join(['next' for n in range(1, k+1)])
| 19| seeker = str('.'.join(['head', nexts]))
| 20|
| 21| while head:
| 22| if eval(seeker) is None:
| 23| return head
| 24| else:
| 25| head = head.next
| 26|
| 27| return False
| 28|
| 29|
| 30| def kth_to_last_dict(head, k):
| 31| """
| 32| This is a brute force method where we keep a dict the size of the list
| 33| Then we check it for the value we need. If the key is not in the dict,
| 34| our and statement will short circuit and return False
| 35| """
| 36| if not (head and k > -1):
| 37| return False
| 38| d = dict()
| 39| count = 0
| 40| while head:
| 41| d[count] = head
| 42| head = head.next
| 43| count += 1
| 44| return len(d)-k in d and d[len(d)-k]
| 45|
| 46|
| 47| def kth_to_last(head, k):
| 48| """
| 49| This is an optimal method using iteration.
| 50| We move p1 k steps ahead into the list.
| 51| Then we move p1 and p2 together until p1 hits the end.
| 52| """
| 53| if not (head or k > -1):
| 54| return False
| 55| p1 = head
| 56| p2 = head
| 57| for i in range(1, k+1):
| 58| if p1 is None:
| 59| # Went too far, k is not valid
| 60| raise IndexError
| 61| p1 = p1.next
| 62| while p1:
| 63| p1 = p1.next
| 64| p2 = p2.next
| 65| return p2
| 66|
| 67|
| 68| def kth_to_last_recursive(head: Node, nth_to_last: int) -> Node:
| 69| """
| 70| Look ahead exactly `nth_to_last` nodes from the head,
| 71| if that was the last node, then head is the answer,
| 72| else change head to the next node, then look ahead again ... and so on.
| 73| """
| 74|
| 75| def look_ahead(node: Union[Node, None], steps: int) -> bool:
| 76| """recursively look `steps` steps ahead of the current `node` """
| 77| if steps == 0:
| 78| # True if reached the last node exactly at `steps` steps
| 79| # else False if end of steps but it is not the last Node
| 80| return True if node is None else False
| 81| if node is None and steps > 0: raise ValueError("kth is larger than list size!")
| 82| return look_ahead(node.next, steps - 1)
| 83|
| 84| while head:
| 85| # for edge-case (when kth = 0) i.e. head is last node already
| 86| if head.next is None: return head
| 87|
| 88| # If `nth_to_last` steps ahead form the current Node is None:
| 89| # then current is the answer
| 90| if look_ahead(head, steps=nth_to_last):
| 91| return head
| 92| head = head.next
| 93|
| 94| raise ValueError("No node found!")
| 95|
| 96| def print_linked_list(head):
| 97| string = ""
| 98| while head.next:
| 99| string += head.val + " -> "
|100| head = head.next
|101| string += head.val
|102| print(string)
|103|
|104|
|105| def test():
|106| # def make_test_li
|107| # A A B C D C F G
|108| a1 = Node("A")
|109| a2 = Node("A")
|110| b = Node("B")
|111| c1 = Node("C")
|112| d = Node("D")
|113| c2 = Node("C")
|114| f = Node("F")
|115| g = Node("G")
|116| a1.next = a2
|117| a2.next = b
|118| b.next = c1
|119| c1.next = d
|120| d.next = c2
|121| c2.next = f
|122| f.next = g
|123| print_linked_list(a1)
|124|
|125| # test kth_to_last_eval
|126| kth = kth_to_last_eval(a1, 4)
|127| try:
|128| assert kth.val == "D"
|129| except AssertionError as e:
|130| e.args += ("Expecting D, got %s" % kth.val,)
|131| raise
|132|
|133| # test kth_to_last_dict
|134| kth = kth_to_last_dict(a1, 4)
|135| try:
|136| assert kth.val == "D"
|137| except AssertionError as e:
|138| e.args += ("Expecting D, got %s" % kth.val,)
|139| raise
|140|
|141| # test kth_to_last
|142| kth = kth_to_last(a1, 4)
|143| try:
|144| assert kth.val == "D"
|145| except AssertionError as e:
|146| e.args += ("Expecting D, got %s" % kth.val,)
|147| raise
|148|
|149| # test kth_to_last_recursive
|150| kth = kth_to_last_recursive(a1, 4)
|151| try:
|152| assert kth.val == "D"
|153| except AssertionError as e:
|154| e.args += ("Expecting D, got %s" % kth.val,)
|155| raise
|156| print("all passed.")
|157|
|158| if __name__ == '__main__':
|159| test()
#========================================================================================================================
# :: linkedlist/intersection.py ::
#========================================================================================================================
| 1| """
| 2| This function takes two lists and returns the node they have in common, if any.
| 3| In this example:
| 4| 1 -> 3 -> 5
| 5| \
| 6| 7 -> 9 -> 11
| 7| /
| 8| 2 -> 4 -> 6
| 9| ...we would return 7.
| 10| Note that the node itself is the unique identifier, not the value of the node.
| 11| """
| 12| import unittest
| 13|
| 14|
| 15| class Node(object):
| 16| def __init__(self, val=None):
| 17| self.val = val
| 18| self.next = None
| 19|
| 20|
| 21| def intersection(h1, h2):
| 22|
| 23| count = 0
| 24| flag = None
| 25| h1_orig = h1
| 26| h2_orig = h2
| 27|
| 28| while h1 or h2:
| 29| count += 1
| 30|
| 31| if not flag and (h1.next is None or h2.next is None):
| 32| # We hit the end of one of the lists, set a flag for this
| 33| flag = (count, h1.next, h2.next)
| 34|
| 35| if h1:
| 36| h1 = h1.next
| 37| if h2:
| 38| h2 = h2.next
| 39|
| 40| long_len = count # Mark the length of the longer of the two lists
| 41| short_len = flag[0]
| 42|
| 43| if flag[1] is None:
| 44| shorter = h1_orig
| 45| longer = h2_orig
| 46| elif flag[2] is None:
| 47| shorter = h2_orig
| 48| longer = h1_orig
| 49|
| 50| while longer and shorter:
| 51|
| 52| while long_len > short_len:
| 53| # force the longer of the two lists to "catch up"
| 54| longer = longer.next
| 55| long_len -= 1
| 56|
| 57| if longer == shorter:
| 58| # The nodes match, return the node
| 59| return longer
| 60| else:
| 61| longer = longer.next
| 62| shorter = shorter.next
| 63|
| 64| return None
| 65|
| 66|
| 67| class TestSuite(unittest.TestCase):
| 68|
| 69| def test_intersection(self):
| 70|
| 71| # create linked list as:
| 72| # 1 -> 3 -> 5
| 73| # \
| 74| # 7 -> 9 -> 11
| 75| # /
| 76| # 2 -> 4 -> 6
| 77| a1 = Node(1)
| 78| b1 = Node(3)
| 79| c1 = Node(5)
| 80| d = Node(7)
| 81| a2 = Node(2)
| 82| b2 = Node(4)
| 83| c2 = Node(6)
| 84| e = Node(9)
| 85| f = Node(11)
| 86|
| 87| a1.next = b1
| 88| b1.next = c1
| 89| c1.next = d
| 90| a2.next = b2
| 91| b2.next = c2
| 92| c2.next = d
| 93| d.next = e
| 94| e.next = f
| 95|
| 96| self.assertEqual(7, intersection(a1, a2).val)
| 97|
| 98|
| 99| if __name__ == '__main__':
|100|
|101| unittest.main()
#========================================================================================================================
# :: linkedlist/first_cyclic_node.py ::
#========================================================================================================================
| 1| """
| 2| Given a linked list, find the first node of a cycle in it.
| 3| 1 -> 2 -> 3 -> 4 -> 5 -> 1 => 1
| 4| A -> B -> C -> D -> E -> C => C
| 5|
| 6| Note: The solution is a direct implementation
| 7| Floyd's cycle-finding algorithm (Floyd's Tortoise and Hare).
| 8| """
| 9| import unittest
| 10|
| 11|
| 12| class Node:
| 13|
| 14| def __init__(self, x):
| 15| self.val = x
| 16| self.next = None
| 17|
| 18|
| 19| def first_cyclic_node(head):
| 20| """
| 21| :type head: Node
| 22| :rtype: Node
| 23| """
| 24| runner = walker = head
| 25| while runner and runner.next:
| 26| runner = runner.next.next
| 27| walker = walker.next
| 28| if runner is walker:
| 29| break
| 30|
| 31| if runner is None or runner.next is None:
| 32| return None
| 33|
| 34| walker = head
| 35| while runner is not walker:
| 36| runner, walker = runner.next, walker.next
| 37| return runner
| 38|
| 39|
| 40| class TestSuite(unittest.TestCase):
| 41|
| 42| def test_first_cyclic_node(self):
| 43|
| 44| # create linked list => A -> B -> C -> D -> E -> C
| 45| head = Node('A')
| 46| head.next = Node('B')
| 47| curr = head.next
| 48|
| 49| cyclic_node = Node('C')
| 50| curr.next = cyclic_node
| 51|
| 52| curr = curr.next
| 53| curr.next = Node('D')
| 54| curr = curr.next
| 55| curr.next = Node('E')
| 56| curr = curr.next
| 57| curr.next = cyclic_node
| 58|
| 59| self.assertEqual('C', first_cyclic_node(head).val)
| 60|
| 61|
| 62| if __name__ == '__main__':
| 63|
| 64| unittest.main()
#========================================================================================================================
# :: linkedlist/remove_duplicates.py ::
#========================================================================================================================
| 1| class Node():
| 2| def __init__(self, val = None):
| 3| self.val = val
| 4| self.next = None
| 5|
| 6| def remove_dups(head):
| 7| """
| 8| Time Complexity: O(N)
| 9| Space Complexity: O(N)
| 10| """
| 11| hashset = set()
| 12| prev = Node()
| 13| while head:
| 14| if head.val in hashset:
| 15| prev.next = head.next
| 16| else:
| 17| hashset.add(head.val)
| 18| prev = head
| 19| head = head.next
| 20|
| 21| def remove_dups_wothout_set(head):
| 22| """
| 23| Time Complexity: O(N^2)
| 24| Space Complexity: O(1)
| 25| """
| 26| current = head
| 27| while current:
| 28| runner = current
| 29| while runner.next:
| 30| if runner.next.val == current.val:
| 31| runner.next = runner.next.next
| 32| else:
| 33| runner = runner.next
| 34| current = current.next
| 35|
| 36| def print_linked_list(head):
| 37| string = ""
| 38| while head.next:
| 39| string += head.val + " -> "
| 40| head = head.next
| 41| string += head.val
| 42| print(string)
| 43|
| 44| # A A B C D C F G
| 45|
| 46| a1 = Node("A")
| 47| a2 = Node("A")
| 48| b = Node("B")
| 49| c1 = Node("C")
| 50| d = Node("D")
| 51| c2 = Node("C")
| 52| f = Node("F")
| 53| g = Node("G")
| 54|
| 55| a1.next = a2
| 56| a2.next = b
| 57| b.next = c1
| 58| c1.next = d
| 59| d.next = c2
| 60| c2.next = f
| 61| f.next = g
| 62|
| 63| remove_dups(a1)
| 64| print_linked_list(a1)
| 65| remove_dups_wothout_set(a1)
| 66| print_linked_list(a1)
#========================================================================================================================
# :: linkedlist/swap_in_pairs.py ::
#========================================================================================================================
| 1| """
| 2| Given a linked list, swap every two adjacent nodes
| 3| and return its head.
| 4|
| 5| For example,
| 6| Given 1->2->3->4, you should return the list as 2->1->4->3.
| 7|
| 8| Your algorithm should use only constant space.
| 9| You may not modify the values in the list,
| 10| only nodes itself can be changed.
| 11| """
| 12| class Node(object):
| 13| def __init__(self, x):
| 14| self.val = x
| 15| self.next = None
| 16|
| 17| def swap_pairs(head):
| 18| if not head:
| 19| return head
| 20| start = Node(0)
| 21| start.next = head
| 22| current = start
| 23| while current.next and current.next.next:
| 24| first = current.next
| 25| second = current.next.next
| 26| first.next = second.next
| 27| current.next = second
| 28| current.next.next = first
| 29| current = current.next.next
| 30| return start.next
#========================================================================================================================
# :: linkedlist/is_sorted.py ::
#========================================================================================================================
| 1| """
| 2| Given a linked list, is_sort function returns true if the list is in sorted
| 3| (increasing) order and return false otherwise. An empty list is considered
| 4| to be sorted.
| 5|
| 6| For example:
| 7| Null :List is sorted
| 8| 1 2 3 4 :List is sorted
| 9| 1 2 -1 3 :List is not sorted
| 10| """
| 11| def is_sorted(head):
| 12| if not head:
| 13| return True
| 14| current = head
| 15| while current.next:
| 16| if current.val > current.next.val:
| 17| return False
| 18| current = current.next
| 19| return True
#========================================================================================================================
# :: iterables/__init__.py ::
#========================================================================================================================
| 1| from .convolved import *
#========================================================================================================================
# :: iterables/convolved.py ::
#========================================================================================================================
| 1| """Iterable to get every convolution window per loop iteration.
| 2|
| 3| ## Example Usage
| 4|
| 5| ```
| 6| from algorithms.iterables import convolved
| 7| # This would also work: from conv import convolved
| 8|
| 9| some_list = [1, 2, 3]
| 10| for kernel_hover in convolved(some_list, kernel_size=2, stride=1, padding=2, default_value=42):
| 11| print(kernel_hover)
| 12| ```
| 13|
| 14| ## Result:
| 15|
| 16| ```
| 17| [42, 42]
| 18| [42, 1]
| 19| [1, 2]
| 20| [2, 3]
| 21| [3, 42]
| 22| [42, 42]
| 23| ```
| 24|
| 25| """
| 26|
| 27|
| 28| def convolved(iterable, kernel_size=1, stride=1, padding=0, default_value=None):
| 29| """Iterable to get every convolution window per loop iteration.
| 30|
| 31| For example:
| 32| `convolved([1, 2, 3, 4], kernel_size=2)`
| 33| will produce the following result:
| 34| `[[1, 2], [2, 3], [3, 4]]`.
| 35| `convolved([1, 2, 3], kernel_size=2, stride=1, padding=2, default_value=42)`
| 36| will produce the following result:
| 37| `[[42, 42], [42, 1], [1, 2], [2, 3], [3, 42], [42, 42]]`
| 38|
| 39| Arguments:
| 40| iterable: An object to iterate on. It should support slice indexing if `padding == 0`.
| 41| kernel_size: The number of items yielded at every iteration.
| 42| stride: The step size between each iteration.
| 43| padding: Padding must be an integer or a string with value `SAME` or `VALID`. If it is an integer, it represents
| 44| how many values we add with `default_value` on the borders. If it is a string, `SAME` means that the
| 45| convolution will add some padding according to the kernel_size, and `VALID` is the same as
| 46| specifying `padding=0`.
| 47| default_value: Default fill value for padding and values outside iteration range.
| 48|
| 49| For more information, refer to:
| 50| - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
| 51| - https://github.com/guillaume-chevalier/python-conv-lib
| 52| - MIT License, Copyright (c) 2018 Guillaume Chevalier
| 53| """
| 54| # Input validation and error messages
| 55| if not hasattr(iterable, '__iter__'):
| 56| raise ValueError(
| 57| "Can't iterate on object.".format(
| 58| iterable))
| 59| if stride < 1:
| 60| raise ValueError(
| 61| "Stride must be of at least one. Got `stride={}`.".format(
| 62| stride))
| 63| if not (padding in ['SAME', 'VALID'] or type(padding) in [int]):
| 64| raise ValueError(
| 65| "Padding must be an integer or a string with value `SAME` or `VALID`.")
| 66| if not isinstance(padding, str):
| 67| if padding < 0:
| 68| raise ValueError(
| 69| "Padding must be of at least zero. Got `padding={}`.".format(
| 70| padding))
| 71| else:
| 72| if padding == 'SAME':
| 73| padding = kernel_size // 2
| 74| elif padding == 'VALID':
| 75| padding = 0
| 76| if not type(iterable) == list:
| 77| iterable = list(iterable)
| 78|
| 79| # Add padding to iterable
| 80| if padding > 0:
| 81| pad = [default_value] * padding
| 82| iterable = pad + list(iterable) + pad
| 83|
| 84| # Fill missing value to the right
| 85| remainder = (kernel_size - len(iterable)) % stride
| 86| extra_pad = [default_value] * remainder
| 87| iterable = iterable + extra_pad
| 88|
| 89| i = 0
| 90| while True:
| 91| if i > len(iterable) - kernel_size:
| 92| break
| 93| yield iterable[i:i + kernel_size]
| 94| i += stride
| 95|
| 96| def convolved_1d(iterable, kernel_size=1, stride=1, padding=0, default_value=None):
| 97| """1D Iterable to get every convolution window per loop iteration.
| 98|
| 99| For more information, refer to:
|100| - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
|101| - https://github.com/guillaume-chevalier/python-conv-lib
|102| - MIT License, Copyright (c) 2018 Guillaume Chevalier
|103| """
|104| return convolved(iterable, kernel_size, stride, padding, default_value)
|105|
|106|
|107| def convolved_2d(iterable, kernel_size=1, stride=1, padding=0, default_value=None):
|108| """2D Iterable to get every convolution window per loop iteration.
|109|
|110| For more information, refer to:
|111| - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
|112| - https://github.com/guillaume-chevalier/python-conv-lib
|113| - MIT License, Copyright (c) 2018 Guillaume Chevalier
|114| """
|115| kernel_size = dimensionize(kernel_size, nd=2)
|116| stride = dimensionize(stride, nd=2)
|117| padding = dimensionize(padding, nd=2)
|118|
|119| for row_packet in convolved(iterable, kernel_size[0], stride[0], padding[0], default_value):
|120| transposed_inner = []
|121| for col in tuple(row_packet):
|122| transposed_inner.append(list(
|123| convolved(col, kernel_size[1], stride[1], padding[1], default_value)
|124| ))
|125|
|126| if len(transposed_inner) > 0:
|127| for col_i in range(len(transposed_inner[0])):
|128| yield tuple(row_j[col_i] for row_j in transposed_inner)
|129|
|130|
|131| def dimensionize(maybe_a_list, nd=2):
|132| """Convert integers to a list of integers to fit the number of dimensions if
|133| the argument is not already a list.
|134|
|135| For example:
|136| `dimensionize(3, nd=2)`
|137| will produce the following result:
|138| `(3, 3)`.
|139| `dimensionize([3, 1], nd=2)`
|140| will produce the following result:
|141| `[3, 1]`.
|142|
|143| For more information, refer to:
|144| - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
|145| - https://github.com/guillaume-chevalier/python-conv-lib
|146| - MIT License, Copyright (c) 2018 Guillaume Chevalier
|147| """
|148| if not hasattr(maybe_a_list, '__iter__'):
|149| # Argument is probably an integer so we map it to a list of size `nd`.
|150| now_a_list = [maybe_a_list] * nd
|151| return now_a_list
|152| else:
|153| # Argument is probably an `nd`-sized list.
|154| return maybe_a_list
#========================================================================================================================
# :: graph/dijkstra.py ::
#========================================================================================================================
| 1| #Dijkstra's single source shortest path algorithm
| 2|
| 3| class Graph():
| 4|
| 5| def __init__(self, vertices):
| 6| self.vertices = vertices
| 7| self.graph = [[0 for column in range(vertices)] for row in range(vertices)]
| 8|
| 9| def min_distance(self, dist, min_dist_set):
| 10| min_dist = float("inf")
| 11| for v in range(self.vertices):
| 12| if dist[v] < min_dist and min_dist_set[v] == False:
| 13| min_dist = dist[v]
| 14| min_index = v
| 15| return min_index
| 16|
| 17| def dijkstra(self, src):
| 18|
| 19| dist = [float("inf")] * self.vertices
| 20| dist[src] = 0
| 21| min_dist_set = [False] * self.vertices
| 22|
| 23| for count in range(self.vertices):
| 24|
| 25| #minimum distance vertex that is not processed
| 26| u = self.min_distance(dist, min_dist_set)
| 27|
| 28| #put minimum distance vertex in shortest tree
| 29| min_dist_set[u] = True
| 30|
| 31| #Update dist value of the adjacent vertices
| 32| for v in range(self.vertices):
| 33| if self.graph[u][v] > 0 and min_dist_set[v] == False and dist[v] > dist[u] + self.graph[u][v]:
| 34| dist[v] = dist[u] + self.graph[u][v]
| 35|
| 36| return dist
#========================================================================================================================
# :: graph/path_between_two_vertices_in_digraph.py ::
#========================================================================================================================
| 1| from collections import defaultdict
| 2|
| 3| class Graph:
| 4| def __init__(self,v):
| 5| self.v = v
| 6| self.graph = defaultdict(list)
| 7| self.has_path = False
| 8|
| 9| def add_edge(self,u,v):
| 10| self.graph[u].append(v)
| 11|
| 12| def dfs(self,x,y):
| 13| visited = [False] * self.v
| 14| self.dfsutil(visited,x,y,)
| 15|
| 16| def dfsutil(self,visited,x,y):
| 17| visited[x] = True
| 18| for i in self.graph[x]:
| 19| if y in self.graph[x]:
| 20| self.has_path = True
| 21| return
| 22| if(not(visited[i])):
| 23| self.dfsutil(visited,x,i)
| 24|
| 25| def is_reachable(self,x,y):
| 26| self.has_path = False
| 27| self.dfs(x,y)
| 28| return self.has_path
| 29|
| 30|
| 31| # Create a graph given in the above diagram
| 32| g = Graph(4)
| 33| g.add_edge(0, 1)
| 34| g.add_edge(0, 2)
| 35| g.add_edge(1, 2)
| 36| g.add_edge(2, 0)
| 37| g.add_edge(2, 3)
| 38| g.add_edge(3, 3)
| 39|
| 40| u =1; v = 3
| 41|
| 42| if g.is_reachable(u, v):
| 43| print("There is a path from %d to %d" % (u,v))
| 44| else :
| 45| print("There is no path from %d to %d" % (u,v))
| 46|
| 47| u = 3; v = 1
| 48| if g.is_reachable(u, v) :
| 49| print("There is a path from %d to %d" % (u,v))
| 50| else :
| 51| print("There is no path from %d to %d" % (u,v))
#========================================================================================================================
# :: graph/markov_chain.py ::
#========================================================================================================================
| 1| import random
| 2|
| 3| my_chain = {
| 4| 'A': {'A': 0.6,
| 5| 'E': 0.4},
| 6| 'E': {'A': 0.7,
| 7| 'E': 0.3}
| 8| }
| 9|
| 10| def __choose_state(state_map):
| 11| choice = random.random()
| 12| probability_reached = 0
| 13| for state, probability in state_map.items():
| 14| probability_reached += probability
| 15| if probability_reached > choice:
| 16| return state
| 17|
| 18| def next_state(chain, current_state):
| 19| next_state_map = chain.get(current_state)
| 20| next_state = __choose_state(next_state_map)
| 21| return next_state
| 22|
| 23| def iterating_markov_chain(chain, state):
| 24| while True:
| 25| state = next_state(chain, state)
| 26| yield state
#========================================================================================================================
# :: graph/traversal.py ::
#========================================================================================================================
| 1| graph = {'A': set(['B', 'C', 'F']),
| 2| 'B': set(['A', 'D', 'E']),
| 3| 'C': set(['A', 'F']),
| 4| 'D': set(['B']),
| 5| 'E': set(['B', 'F']),
| 6| 'F': set(['A', 'C', 'E'])}
| 7|
| 8| # dfs and bfs are the ultimately same except that they are visiting nodes in
| 9| # different order. To simulate this ordering we would use stack for dfs and
| 10| # queue for bfs.
| 11| #
| 12|
| 13| def dfs_traverse(graph, start):
| 14| visited, stack = set(), [start]
| 15| while stack:
| 16| node = stack.pop()
| 17| if node not in visited:
| 18| visited.add(node)
| 19| for nextNode in graph[node]:
| 20| if nextNode not in visited:
| 21| stack.append(nextNode)
| 22| return visited
| 23|
| 24| # print(dfs_traverse(graph, 'A'))
| 25|
| 26|
| 27| def bfs_traverse(graph, start):
| 28| visited, queue = set(), [start]
| 29| while queue:
| 30| node = queue.pop(0)
| 31| if node not in visited:
| 32| visited.add(node)
| 33| for nextNode in graph[node]:
| 34| if nextNode not in visited:
| 35| queue.append(nextNode)
| 36| return visited
| 37|
| 38| # print(bfs_traverse(graph, 'A'))
| 39|
| 40| def dfs_traverse_recursive(graph, start, visited=None):
| 41| if visited is None:
| 42| visited = set()
| 43| visited.add(start)
| 44| for nextNode in graph[start]:
| 45| if nextNode not in visited:
| 46| dfs_traverse_recursive(graph, nextNode, visited)
| 47| return visited
| 48|
| 49| # print(dfs_traverse_recursive(graph, 'A'))
| 50|
| 51| # def find_path(graph, start, end, visited=[]):
| 52| # # basecase
| 53| # visitied = visited + [start]
| 54| # if start == end:
| 55| # return visited
| 56| # if start not in graph:
| 57| # return None
| 58| # for node in graph[start]:
| 59| # if node not in visited:
| 60| # new_visited = find_path(graph, node, end, visited)
| 61| # return new_visited
| 62| # return None
| 63|
| 64| # print(find_path(graph, 'A', 'F'))
| 65|
| 66|
#========================================================================================================================
# :: graph/find_path.py ::
#========================================================================================================================
| 1| myGraph = {'A': ['B', 'C'],
| 2| 'B': ['C', 'D'],
| 3| 'C': ['D', 'F'],
| 4| 'D': ['C'],
| 5| 'E': ['F'],
| 6| 'F': ['C']}
| 7|
| 8| # find path from start to end using recursion with backtracking
| 9| def find_path(graph, start, end, path=[]):
| 10| path = path + [start]
| 11| if (start == end):
| 12| return path
| 13| if not start in graph:
| 14| return None
| 15| for node in graph[start]:
| 16| if node not in path:
| 17| newpath = find_path(graph, node, end, path)
| 18| return newpath
| 19| return None
| 20|
| 21| # find all path
| 22| def find_all_path(graph, start, end, path=[]):
| 23| path = path + [start]
| 24| print(path)
| 25| if (start == end):
| 26| return [path]
| 27| if not start in graph:
| 28| return None
| 29| paths = []
| 30| for node in graph[start]:
| 31| if node not in path:
| 32| newpaths = find_all_path(graph, node, end, path)
| 33| for newpath in newpaths:
| 34| paths.append(newpath)
| 35| return paths
| 36|
| 37| def find_shortest_path(graph, start, end, path=[]):
| 38| path = path + [start]
| 39| if start == end:
| 40| return path
| 41| if start not in graph:
| 42| return None
| 43| shortest = None
| 44| for node in graph[start]:
| 45| if node not in path:
| 46| newpath = find_shortest_path(graph, node, end, path)
| 47| if newpath:
| 48| if not shortest or len(newpath) < len(shortest):
| 49| shortest = newpath
| 50| return shortest
| 51|
| 52| print(find_all_path(myGraph, 'A', 'F'))
| 53| # print(find_shortest_path(myGraph, 'A', 'D'))
#========================================================================================================================
# :: graph/Transitive_Closure_DFS.py ::
#========================================================================================================================
| 1| # This class represents a directed graph using adjacency
| 2| class Graph:
| 3| def __init__(self, vertices):
| 4| # No. of vertices
| 5| self.V = vertices
| 6|
| 7| # default dictionary to store graph
| 8| self.graph = {}
| 9|
| 10| # To store transitive closure
| 11| self.tc = [[0 for j in range(self.V)] for i in range(self.V)]
| 12|
| 13| # function to add an edge to graph
| 14| def add_edge(self, u, v):
| 15| if u in self.graph:
| 16| self.graph[u].append(v)
| 17| else:
| 18| self.graph[u] = [v]
| 19|
| 20| # A recursive DFS traversal function that finds
| 21| # all reachable vertices for s
| 22| def dfs_util(self, s, v):
| 23|
| 24| # Mark reachability from s to v as true.
| 25| self.tc[s][v] = 1
| 26|
| 27| # Find all the vertices reachable through v
| 28| for i in self.graph[v]:
| 29| if self.tc[s][i] == 0:
| 30| self.dfs_util(s, i)
| 31|
| 32| # The function to find transitive closure. It uses
| 33| # recursive dfs_util()
| 34| def transitive_closure(self):
| 35|
| 36| # Call the recursive helper function to print DFS
| 37| # traversal starting from all vertices one by one
| 38| for i in range(self.V):
| 39| self.dfs_util(i, i)
| 40| print(self.tc)
| 41|
| 42|
| 43| g = Graph(4)
| 44| g.add_edge(0, 1)
| 45| g.add_edge(0, 2)
| 46| g.add_edge(1, 2)
| 47| g.add_edge(2, 0)
| 48| g.add_edge(2, 3)
| 49| g.add_edge(3, 3)
| 50|
| 51| print("Transitive closure matrix is")
| 52| g.transitive_closure()
#========================================================================================================================
# :: graph/clone_graph.py ::
#========================================================================================================================
| 1| """
| 2| Clone an undirected graph. Each node in the graph contains a label and a list
| 3| of its neighbors.
| 4|
| 5|
| 6| OJ's undirected graph serialization:
| 7| Nodes are labeled uniquely.
| 8|
| 9| We use # as a separator for each node, and , as a separator for node label and
| 10| each neighbor of the node.
| 11| As an example, consider the serialized graph {0,1,2#1,2#2,2}.
| 12|
| 13| The graph has a total of three nodes, and therefore contains three parts as
| 14| separated by #.
| 15|
| 16| First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
| 17| Second node is labeled as 1. Connect node 1 to node 2.
| 18| Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a
| 19| self-cycle.
| 20| Visually, the graph looks like the following:
| 21|
| 22| 1
| 23| / \
| 24| / \
| 25| 0 --- 2
| 26| / \
| 27| \_/
| 28| """
| 29| import collections
| 30|
| 31|
| 32| # Definition for a undirected graph node
| 33| class UndirectedGraphNode:
| 34| def __init__(self, x):
| 35| self.label = x
| 36| self.neighbors = []
| 37|
| 38|
| 39| # BFS
| 40| def clone_graph1(node):
| 41| if not node:
| 42| return
| 43| node_copy = UndirectedGraphNode(node.label)
| 44| dic = {node: node_copy}
| 45| queue = collections.deque([node])
| 46| while queue:
| 47| node = queue.popleft()
| 48| for neighbor in node.neighbors:
| 49| if neighbor not in dic: # neighbor is not visited
| 50| neighbor_copy = UndirectedGraphNode(neighbor.label)
| 51| dic[neighbor] = neighbor_copy
| 52| dic[node].neighbors.append(neighbor_copy)
| 53| queue.append(neighbor)
| 54| else:
| 55| dic[node].neighbors.append(dic[neighbor])
| 56| return node_copy
| 57|
| 58|
| 59| # DFS iteratively
| 60| def clone_graph2(node):
| 61| if not node:
| 62| return
| 63| node_copy = UndirectedGraphNode(node.label)
| 64| dic = {node: node_copy}
| 65| stack = [node]
| 66| while stack:
| 67| node = stack.pop()
| 68| for neighbor in node.neighbors:
| 69| if neighbor not in dic:
| 70| neighbor_copy = UndirectedGraphNode(neighbor.label)
| 71| dic[neighbor] = neighbor_copy
| 72| dic[node].neighbors.append(neighbor_copy)
| 73| stack.append(neighbor)
| 74| else:
| 75| dic[node].neighbors.append(dic[neighbor])
| 76| return node_copy
| 77|
| 78|
| 79| # DFS recursively
| 80| def clone_graph(node):
| 81| if not node:
| 82| return
| 83| node_copy = UndirectedGraphNode(node.label)
| 84| dic = {node: node_copy}
| 85| dfs(node, dic)
| 86| return node_copy
| 87|
| 88|
| 89| def dfs(node, dic):
| 90| for neighbor in node.neighbors:
| 91| if neighbor not in dic:
| 92| neighbor_copy = UndirectedGraphNode(neighbor.label)
| 93| dic[neighbor] = neighbor_copy
| 94| dic[node].neighbors.append(neighbor_copy)
| 95| dfs(neighbor, dic)
| 96| else:
| 97| dic[node].neighbors.append(dic[neighbor])
#========================================================================================================================
# :: graph/graph.py ::
#========================================================================================================================
| 1| """
| 2| These are classes to represent a Graph and its elements.
| 3| It can be shared across graph algorithms.
| 4| """
| 5|
| 6| class Node(object):
| 7| def __init__(self, name):
| 8| self.name = name
| 9|
| 10| @staticmethod
| 11| def get_name(obj):
| 12| if isinstance(obj, Node):
| 13| return obj.name
| 14| elif isinstance(obj, str):
| 15| return obj
| 16| return''
| 17|
| 18| def __eq__(self, obj):
| 19| return self.name == self.get_name(obj)
| 20|
| 21| def __repr__(self):
| 22| return self.name
| 23|
| 24| def __hash__(self):
| 25| return hash(self.name)
| 26|
| 27| def __ne__(self, obj):
| 28| return self.name != self.get_name(obj)
| 29|
| 30| def __lt__(self, obj):
| 31| return self.name < self.get_name(obj)
| 32|
| 33| def __le__(self, obj):
| 34| return self.name <= self.get_name(obj)
| 35|
| 36| def __gt__(self, obj):
| 37| return self.name > self.get_name(obj)
| 38|
| 39| def __ge__(self, obj):
| 40| return self.name >= self.get_name(obj)
| 41|
| 42| def __bool__(self):
| 43| return self.name
| 44|
| 45| class DirectedEdge(object):
| 46| def __init__(self, node_from, node_to):
| 47| self.nf = node_from
| 48| self.nt = node_to
| 49|
| 50| def __eq__(self, obj):
| 51| if isinstance(obj, DirectedEdge):
| 52| return obj.nf == self.nf and obj.nt == self.nt
| 53| return False
| 54|
| 55| def __repr__(self):
| 56| return '({0} -> {1})'.format(self.nf, self.nt)
| 57|
| 58| class DirectedGraph(object):
| 59| def __init__(self, load_dict={}):
| 60| self.nodes = []
| 61| self.edges = []
| 62| self.adjmt = {}
| 63|
| 64| if load_dict and type(load_dict) == dict:
| 65| for v in load_dict:
| 66| node_from = self.add_node(v)
| 67| self.adjmt[node_from] = []
| 68| for w in load_dict[v]:
| 69| node_to = self.add_node(w)
| 70| self.adjmt[node_from].append(node_to)
| 71| self.add_edge(v, w)
| 72|
| 73| def add_node(self, node_name):
| 74| try:
| 75| return self.nodes[self.nodes.index(node_name)]
| 76| except ValueError:
| 77| node = Node(node_name)
| 78| self.nodes.append(node)
| 79| return node
| 80|
| 81| def add_edge(self, node_name_from, node_name_to):
| 82| try:
| 83| node_from = self.nodes[self.nodes.index(node_name_from)]
| 84| node_to = self.nodes[self.nodes.index(node_name_to)]
| 85| self.edges.append(DirectedEdge(node_from, node_to))
| 86| except ValueError:
| 87| pass
| 88|
| 89| class Graph:
| 90| def __init__(self, vertices):
| 91| # No. of vertices
| 92| self.V = vertices
| 93|
| 94| # default dictionary to store graph
| 95| self.graph = {}
| 96|
| 97| # To store transitive closure
| 98| self.tc = [[0 for j in range(self.V)] for i in range(self.V)]
| 99|
|100| # function to add an edge to graph
|101| def add_edge(self, u, v):
|102| if u in self.graph:
|103| self.graph[u].append(v)
|104| else:
|105| self.graph[u] = [v]
|106|
|107| #g = Graph(4)
|108| #g.add_edge(0, 1)
|109| #g.add_edge(0, 2)
|110| #g.add_edge(1, 2)
|111| #g.add_edge(2, 0)
|112| #g.add_edge(2, 3)
|113| #g.add_edge(3, 3)
#========================================================================================================================
# :: graph/minimum_spanning_tree.py ::
#========================================================================================================================
| 1| # Minimum spanning tree (MST) is going to use an undirected graph
| 2| #
| 3| # The disjoint set is represented with an list <n> of integers where
| 4| # <n[i]> is the parent of the node at position <i>.
| 5| # If <n[i]> = <i>, <i> it's a root, or a head, of a set
| 6|
| 7|
| 8| class Edge:
| 9| def __init__(self, u, v, weight):
| 10| self.u = u
| 11| self.v = v
| 12| self.weight = weight
| 13|
| 14|
| 15| class DisjointSet:
| 16| def __init__(self, n):
| 17| # Args:
| 18| # n (int): Number of vertices in the graph
| 19|
| 20| self.parent = [None] * n # Contains wich node is the parent of the node at poisition <i>
| 21| self.size = [1] * n # Contains size of node at index <i>, used to optimize merge
| 22| for i in range(n):
| 23| self.parent[i] = i # Make all nodes his own parent, creating n sets.
| 24|
| 25| def merge_set(self, a, b):
| 26| # Args:
| 27| # a, b (int): Indexes of nodes whose sets will be merged.
| 28|
| 29| # Get the set of nodes at position <a> and <b>
| 30| # If <a> and <b> are the roots, this will be constant O(1)
| 31| a = self.find_set(a)
| 32| b = self.find_set(b)
| 33|
| 34| # Join the shortest node to the longest, minimizing tree size (faster find)
| 35| if self.size[a] < self.size[b]:
| 36| self.parent[a] = b # Merge set(a) and set(b)
| 37| self.size[b] += self.size[a] # Add size of old set(a) to set(b)
| 38| else:
| 39| self.parent[b] = a # Merge set(b) and set(a)
| 40| self.size[a] += self.size[b] # Add size of old set(b) to set(a)
| 41|
| 42| def find_set(self, a):
| 43| if self.parent[a] != a:
| 44| # Very important, memoize result of the
| 45| # recursion in the list to optimize next
| 46| # calls and make this operation practically constant, O(1)
| 47| self.parent[a] = self.find_set(self.parent[a])
| 48|
| 49| # node <a> it's the set root, so we can return that index
| 50| return self.parent[a]
| 51|
| 52|
| 53| def kruskal(n, edges, ds):
| 54| # Args:
| 55| # n (int): Number of vertices in the graph
| 56| # edges (list of Edge): Edges of the graph
| 57| # ds (DisjointSet): DisjointSet of the vertices
| 58| # Returns:
| 59| # int: sum of weights of the minnimum spanning tree
| 60| #
| 61| # Kruskal algorithm:
| 62| # This algorithm will find the optimal graph with less edges and less
| 63| # total weight to connect all vertices (MST), the MST will always contain
| 64| # n-1 edges because it's the minimum required to connect n vertices.
| 65| #
| 66| # Procedure:
| 67| # Sort the edges (criteria: less weight).
| 68| # Only take edges of nodes in different sets.
| 69| # If we take a edge, we need to merge the sets to discard these.
| 70| # After repeat this until select n-1 edges, we will have the complete MST.
| 71| edges.sort(key=lambda edge: edge.weight)
| 72|
| 73| mst = [] # List of edges taken, minimum spanning tree
| 74|
| 75| for edge in edges:
| 76| set_u = ds.find_set(edge.u) # Set of the node <u>
| 77| set_v = ds.find_set(edge.v) # Set of the node <v>
| 78| if set_u != set_v:
| 79| ds.merge_set(set_u, set_v)
| 80| mst.append(edge)
| 81| if len(mst) == n-1:
| 82| # If we have selected n-1 edges, all the other
| 83| # edges will be discarted, so, we can stop here
| 84| break
| 85|
| 86| return sum([edge.weight for edge in mst])
| 87|
| 88|
| 89|
| 90|
| 91| if __name__ == "__main__":
| 92| # Test. How input works:
| 93| # Input consists of different weighted, connected, undirected graphs.
| 94| # line 1:
| 95| # integers n, m
| 96| # lines 2..m+2:
| 97| # edge with the format -> node index u, node index v, integer weight
| 98| #
| 99| # Samples of input:
|100| #
|101| # 5 6
|102| # 1 2 3
|103| # 1 3 8
|104| # 2 4 5
|105| # 3 4 2
|106| # 3 5 4
|107| # 4 5 6
|108| #
|109| # 3 3
|110| # 2 1 20
|111| # 3 1 20
|112| # 2 3 100
|113| #
|114| # Sum of weights of the optimal paths:
|115| # 14, 40
|116| import sys
|117| for n_m in sys.stdin:
|118| n, m = map(int, n_m.split())
|119| ds = DisjointSet(m)
|120| edges = [None] * m # Create list of size <m>
|121|
|122| # Read <m> edges from input
|123| for i in range(m):
|124| u, v, weight = map(int, input().split())
|125| u -= 1 # Convert from 1-indexed to 0-indexed
|126| v -= 1 # Convert from 1-indexed to 0-indexed
|127| edges[i] = Edge(u, v, weight)
|128|
|129| # After finish input and graph creation, use Kruskal algorithm for MST:
|130| print("MST weights sum:", kruskal(n, edges, ds))
#========================================================================================================================
# :: graph/__init__.py ::
#========================================================================================================================
| 1| from .tarjan import *
| 2| from .check_bipartite import *
#========================================================================================================================
# :: graph/tarjan.py ::
#========================================================================================================================
| 1| """
| 2| Implements Tarjan's algorithm for finding strongly connected components
| 3| in a graph.
| 4| https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
| 5| """
| 6| from algorithms.graph.graph import DirectedGraph
| 7|
| 8| class Tarjan(object):
| 9| def __init__(self, dict_graph):
| 10| self.graph = DirectedGraph(dict_graph)
| 11| self.index = 0
| 12| self.stack = []
| 13|
| 14| # Runs Tarjan
| 15| # Set all node index to None
| 16| for v in self.graph.nodes:
| 17| v.index = None
| 18|
| 19| self.sccs = []
| 20| for v in self.graph.nodes:
| 21| if v.index == None:
| 22| self.strongconnect(v, self.sccs)
| 23|
| 24| def strongconnect(self, v, sccs):
| 25| # Set the depth index for v to the smallest unused index
| 26| v.index = self.index
| 27| v.lowlink = self.index
| 28| self.index += 1
| 29| self.stack.append(v)
| 30| v.on_stack = True
| 31|
| 32| # Consider successors of v
| 33| for w in self.graph.adjmt[v]:
| 34| if w.index == None:
| 35| # Successor w has not yet been visited; recurse on it
| 36| self.strongconnect(w, sccs)
| 37| v.lowlink = min(v.lowlink, w.lowlink)
| 38| elif w.on_stack:
| 39| # Successor w is in stack S and hence in the current SCC
| 40| # If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored
| 41| # Note: The next line may look odd - but is correct.
| 42| # It says w.index not w.lowlink; that is deliberate and from the original paper
| 43| v.lowlink = min(v.lowlink, w.index)
| 44|
| 45| # If v is a root node, pop the stack and generate an SCC
| 46| if v.lowlink == v.index:
| 47| # start a new strongly connected component
| 48| scc = []
| 49| while True:
| 50| w = self.stack.pop()
| 51| w.on_stack = False
| 52| scc.append(w)
| 53| if w == v:
| 54| break
| 55| scc.sort()
| 56| sccs.append(scc)
| 57|
#========================================================================================================================
# :: graph/find_all_cliques.py ::
#========================================================================================================================
| 1| # takes dict of sets
| 2| # each key is a vertex
| 3| # value is set of all edges connected to vertex
| 4| # returns list of lists (each sub list is a maximal clique)
| 5| # implementation of the basic algorithm described in:
| 6| # Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph",
| 7|
| 8|
| 9| def find_all_cliques(edges):
| 10| def expand_clique(candidates, nays):
| 11| nonlocal compsub
| 12| if not candidates and not nays:
| 13| nonlocal solutions
| 14| solutions.append(compsub.copy())
| 15| else:
| 16| for selected in candidates.copy():
| 17| candidates.remove(selected)
| 18| candidates_temp = get_connected(selected, candidates)
| 19| nays_temp = get_connected(selected, nays)
| 20| compsub.append(selected)
| 21| expand_clique(candidates_temp, nays_temp)
| 22| nays.add(compsub.pop())
| 23|
| 24| def get_connected(vertex, old_set):
| 25| new_set = set()
| 26| for neighbor in edges[str(vertex)]:
| 27| if neighbor in old_set:
| 28| new_set.add(neighbor)
| 29| return new_set
| 30|
| 31| compsub = []
| 32| solutions = []
| 33| possibles = set(edges.keys())
| 34| expand_clique(possibles, set())
| 35| return solutions
#========================================================================================================================
# :: graph/cycle_detection.py ::
#========================================================================================================================
| 1| """
| 2| Given a directed graph, check whether it contains a cycle.
| 3|
| 4| Real-life scenario: deadlock detection in a system. Processes may be
| 5| represented by vertices, then and an edge A -> B could mean that process A is
| 6| waiting for B to release its lock on a resource.
| 7| """
| 8| from enum import Enum
| 9|
| 10|
| 11| class TraversalState(Enum):
| 12| WHITE = 0
| 13| GRAY = 1
| 14| BLACK = 2
| 15|
| 16|
| 17| example_graph_with_cycle = {'A': ['B', 'C'],
| 18| 'B': ['D'],
| 19| 'C': ['F'],
| 20| 'D': ['E', 'F'],
| 21| 'E': ['B'],
| 22| 'F': []}
| 23|
| 24| example_graph_without_cycle = {'A': ['B', 'C'],
| 25| 'B': ['D', 'E'],
| 26| 'C': ['F'],
| 27| 'D': ['E'],
| 28| 'E': [],
| 29| 'F': []}
| 30|
| 31|
| 32| def is_in_cycle(graph, traversal_states, vertex):
| 33| if traversal_states[vertex] == TraversalState.GRAY:
| 34| return True
| 35| traversal_states[vertex] = TraversalState.GRAY
| 36| for neighbor in graph[vertex]:
| 37| if is_in_cycle(graph, traversal_states, neighbor):
| 38| return True
| 39| traversal_states[vertex] = TraversalState.BLACK
| 40| return False
| 41|
| 42|
| 43| def contains_cycle(graph):
| 44| traversal_states = {vertex: TraversalState.WHITE for vertex in graph}
| 45| for vertex, state in traversal_states.items():
| 46| if (state == TraversalState.WHITE and
| 47| is_in_cycle(graph, traversal_states, vertex)):
| 48| return True
| 49| return False
| 50|
| 51| print(contains_cycle(example_graph_with_cycle))
| 52| print(contains_cycle(example_graph_without_cycle))
#========================================================================================================================
# :: graph/satisfiability.py ::
#========================================================================================================================
| 1| '''
| 2| Given a formula in conjunctive normal form (2-CNF), finds a way to assign
| 3| True/False values to all variables to satisfy all clauses, or reports there
| 4| is no solution.
| 5|
| 6| https://en.wikipedia.org/wiki/2-satisfiability
| 7| '''
| 8|
| 9|
| 10| ''' Format:
| 11| - each clause is a pair of literals
| 12| - each literal in the form (name, is_neg)
| 13| where name is an arbitrary identifier,
| 14| and is_neg is true if the literal is negated
| 15| '''
| 16| formula = [(('x', False), ('y', False)),
| 17| (('y', True), ('y', True)),
| 18| (('a', False), ('b', False)),
| 19| (('a', True), ('c', True)),
| 20| (('c', False), ('b', True))]
| 21|
| 22|
| 23| def dfs_transposed(v, graph, order, vis):
| 24| vis[v] = True
| 25|
| 26| for u in graph[v]:
| 27| if not vis[u]:
| 28| dfs_transposed(u, graph, order, vis)
| 29|
| 30| order.append(v)
| 31|
| 32|
| 33| def dfs(v, current_comp, vertex_scc, graph, vis):
| 34| vis[v] = True
| 35| vertex_scc[v] = current_comp
| 36|
| 37| for u in graph[v]:
| 38| if not vis[u]:
| 39| dfs(u, current_comp, vertex_scc, graph, vis)
| 40|
| 41|
| 42| def add_edge(graph, vertex_from, vertex_to):
| 43| if vertex_from not in graph:
| 44| graph[vertex_from] = []
| 45|
| 46| graph[vertex_from].append(vertex_to)
| 47|
| 48|
| 49| def scc(graph):
| 50| ''' Computes the strongly connected components of a graph '''
| 51| order = []
| 52| vis = {vertex: False for vertex in graph}
| 53|
| 54| graph_transposed = {vertex: [] for vertex in graph}
| 55|
| 56| for (v, neighbours) in graph.iteritems():
| 57| for u in neighbours:
| 58| add_edge(graph_transposed, u, v)
| 59|
| 60| for v in graph:
| 61| if not vis[v]:
| 62| dfs_transposed(v, graph_transposed, order, vis)
| 63|
| 64| vis = {vertex: False for vertex in graph}
| 65| vertex_scc = {}
| 66|
| 67| current_comp = 0
| 68| for v in reversed(order):
| 69| if not vis[v]:
| 70| # Each dfs will visit exactly one component
| 71| dfs(v, current_comp, vertex_scc, graph, vis)
| 72| current_comp += 1
| 73|
| 74| return vertex_scc
| 75|
| 76|
| 77| def build_graph(formula):
| 78| ''' Builds the implication graph from the formula '''
| 79| graph = {}
| 80|
| 81| for clause in formula:
| 82| for (lit, _) in clause:
| 83| for neg in [False, True]:
| 84| graph[(lit, neg)] = []
| 85|
| 86| for ((a_lit, a_neg), (b_lit, b_neg)) in formula:
| 87| add_edge(graph, (a_lit, a_neg), (b_lit, not b_neg))
| 88| add_edge(graph, (b_lit, b_neg), (a_lit, not a_neg))
| 89|
| 90| return graph
| 91|
| 92|
| 93| def solve_sat(formula):
| 94| graph = build_graph(formula)
| 95| vertex_scc = scc(graph)
| 96|
| 97| for (var, _) in graph:
| 98| if vertex_scc[(var, False)] == vertex_scc[(var, True)]:
| 99| return None # The formula is contradictory
|100|
|101| comp_repr = {} # An arbitrary representant from each component
|102|
|103| for vertex in graph:
|104| if not vertex_scc[vertex] in comp_repr:
|105| comp_repr[vertex_scc[vertex]] = vertex
|106|
|107| comp_value = {} # True/False value for each strongly connected component
|108| components = sorted(vertex_scc.values())
|109|
|110| for comp in components:
|111| if comp not in comp_value:
|112| comp_value[comp] = False
|113|
|114| (lit, neg) = comp_repr[comp]
|115| comp_value[vertex_scc[(lit, not neg)]] = True
|116|
|117| value = {var: comp_value[vertex_scc[(var, False)]] for (var, _) in graph}
|118|
|119| return value
|120|
|121|
|122| if __name__ == '__main__':
|123| result = solve_sat(formula)
|124|
|125| for (variable, assign) in result.iteritems():
|126| print("{}:{}".format(variable, assign))
#========================================================================================================================
# :: graph/check_bipartite.py ::
#========================================================================================================================
| 1| """
| 2|
| 3| Bipartite graph is a graph whose vertices can be divided into two disjoint and independent sets.
| 4| (https://en.wikipedia.org/wiki/Bipartite_graph)
| 5|
| 6| Time complexity is O(|E|)
| 7| Space complexity is O(|V|)
| 8|
| 9| """
| 10|
| 11| def check_bipartite(adj_list):
| 12|
| 13| V = len(adj_list)
| 14|
| 15| # Divide vertexes in the graph into set_type 1 and 2
| 16| # Initialize all set_types as -1
| 17| set_type = [-1 for v in range(V)]
| 18| set_type[0] = 0
| 19|
| 20| q = [0]
| 21|
| 22| while q:
| 23| v = q.pop(0)
| 24|
| 25| # If there is a self-loop, it cannot be bipartite
| 26| if adj_list[v][v]:
| 27| return False
| 28|
| 29| for u in range(V):
| 30| if adj_list[v][u]:
| 31| if set_type[u] == set_type[v]:
| 32| return False
| 33| elif set_type[u] == -1:
| 34| # set type of u opposite of v
| 35| set_type[u] = 1 - set_type[v]
| 36| q.append(u)
| 37|
| 38| return True
| 39|
#========================================================================================================================
# :: graph/check_digraph_strongly_connected.py ::
#========================================================================================================================
| 1| from collections import defaultdict
| 2|
| 3| class Graph:
| 4| def __init__(self,v):
| 5| self.v = v
| 6| self.graph = defaultdict(list)
| 7|
| 8| def add_edge(self,u,v):
| 9| self.graph[u].append(v)
| 10|
| 11| def dfs(self):
| 12| visited = [False] * self.v
| 13| self.dfs_util(0,visited)
| 14| if visited == [True]*self.v:
| 15| return True
| 16| return False
| 17|
| 18| def dfs_util(self,i,visited):
| 19| visited[i] = True
| 20| for u in self.graph[i]:
| 21| if not(visited[u]):
| 22| self.dfs_util(u,visited)
| 23|
| 24| def reverse_graph(self):
| 25| g = Graph(self.v)
| 26| for i in range(len(self.graph)):
| 27| for j in self.graph[i]:
| 28| g.add_edge(j,i)
| 29| return g
| 30|
| 31|
| 32| def is_sc(self):
| 33| if self.dfs():
| 34| gr = self.reverse_graph()
| 35| if gr.dfs():
| 36| return True
| 37| return False
| 38|
| 39|
| 40| g1 = Graph(5)
| 41| g1.add_edge(0, 1)
| 42| g1.add_edge(1, 2)
| 43| g1.add_edge(2, 3)
| 44| g1.add_edge(3, 0)
| 45| g1.add_edge(2, 4)
| 46| g1.add_edge(4, 2)
| 47| print ("Yes") if g1.is_sc() else print("No")
| 48|
| 49| g2 = Graph(4)
| 50| g2.add_edge(0, 1)
| 51| g2.add_edge(1, 2)
| 52| g2.add_edge(2, 3)
| 53| print ("Yes") if g2.is_sc() else print("No")
#========================================================================================================================
# :: search/search_rotate.py ::
#========================================================================================================================
| 1| """
| 2| Search in Rotated Sorted Array
| 3| Suppose an array sorted in ascending order is rotated at some pivot unknown
| 4| to you beforehand. (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).
| 5|
| 6| You are given a target value to search. If found in the array return its index,
| 7| otherwise return -1.
| 8|
| 9| Your algorithm's runtime complexity must be in the order of O(log n).
| 10| ---------------------------------------------------------------------------------
| 11| Explanation algorithm:
| 12|
| 13| In classic binary search, we compare val with the midpoint to figure out if
| 14| val belongs on the low or the high side. The complication here is that the
| 15| array is rotated and may have an inflection point. Consider, for example:
| 16|
| 17| Array1: [10, 15, 20, 0, 5]
| 18| Array2: [50, 5, 20, 30, 40]
| 19|
| 20| Note that both arrays have a midpoint of 20, but 5 appears on the left side of
| 21| one and on the right side of the other. Therefore, comparing val with the
| 22| midpoint is insufficient.
| 23|
| 24| However, if we look a bit deeper, we can see that one half of the array must be
| 25| ordered normally(increasing order). We can therefore look at the normally ordered
| 26| half to determine whether we should search the low or hight side.
| 27|
| 28| For example, if we are searching for 5 in Array1, we can look at the left element (10)
| 29| and middle element (20). Since 10 < 20, the left half must be ordered normally. And, since 5
| 30| is not between those, we know that we must search the right half
| 31|
| 32| In array2, we can see that since 50 > 20, the right half must be ordered normally. We turn to
| 33| the middle 20, and right 40 element to check if 5 would fall between them. The value 5 would not
| 34| Therefore, we search the left half.
| 35|
| 36| There are 2 possible solution: iterative and recursion.
| 37| Recursion helps you understand better the above algorithm explanation
| 38| """
| 39| def search_rotate(array, val):
| 40| low, high = 0, len(array) - 1
| 41| while low <= high:
| 42| mid = (low + high) // 2
| 43| if val == array[mid]:
| 44| return mid
| 45|
| 46| if array[low] <= array[mid]:
| 47| if array[low] <= val <= array[mid]:
| 48| high = mid - 1
| 49| else:
| 50| low = mid + 1
| 51| else:
| 52| if array[mid] <= val <= array[high]:
| 53| low = mid + 1
| 54| else:
| 55| high = mid - 1
| 56|
| 57| return -1
| 58|
| 59| # Recursion technique
| 60| def search_rotate_recur(array, low, high, val):
| 61| if low >= high:
| 62| return -1
| 63| mid = (low + high) // 2
| 64| if val == array[mid]: # found element
| 65| return mid
| 66| if array[low] <= array[mid]:
| 67| if array[low] <= val <= array[mid]:
| 68| return search_rotate_recur(array, low, mid - 1, val) # Search left
| 69| else:
| 70| return search_rotate_recur(array, mid + 1, high, val) # Search right
| 71| else:
| 72| if array[mid] <= val <= array[high]:
| 73| return search_rotate_recur(array, mid + 1, high, val) # Search right
| 74| else:
| 75| return search_rotate_recur(array, low, mid - 1, val) # Search left
#========================================================================================================================
# :: search/binary_search.py ::
#========================================================================================================================
| 1| #
| 2| # Binary search works for a sorted array.
| 3| # Note: The code logic is written for an array sorted in
| 4| # increasing order.
| 5| # T(n): O(log n)
| 6| #
| 7| def binary_search(array, query):
| 8| lo, hi = 0, len(array) - 1
| 9| while lo <= hi:
| 10| mid = (hi + lo) // 2
| 11| val = array[mid]
| 12| if val == query:
| 13| return mid
| 14| elif val < query:
| 15| lo = mid + 1
| 16| else:
| 17| hi = mid - 1
| 18| return None
| 19|
| 20| def binary_search_recur(array, low, high, val):
| 21| if low > high: # error case
| 22| return -1
| 23| mid = (low + high) // 2
| 24| if val < array[mid]:
| 25| return binary_search_recur(array, low, mid - 1, val)
| 26| elif val > array[mid]:
| 27| return binary_search_recur(array, mid + 1, high, val)
| 28| else:
| 29| return mid
#========================================================================================================================
# :: search/search_range.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of integers nums sorted in ascending order, find the starting
| 3| and ending position of a given target value. If the target is not found in the
| 4| array, return [-1, -1].
| 5|
| 6| For example:
| 7| Input: nums = [5,7,7,8,8,8,10], target = 8
| 8| Output: [3,5]
| 9| Input: nums = [5,7,7,8,8,8,10], target = 11
| 10| Output: [-1,-1]
| 11| """
| 12| def search_range(nums, target):
| 13| """
| 14| :type nums: List[int]
| 15| :type target: int
| 16| :rtype: List[int]
| 17| """
| 18| low = 0
| 19| high = len(nums) - 1
| 20| while low <= high:
| 21| mid = low + (high - low) // 2
| 22| if target < nums[mid]:
| 23| high = mid - 1
| 24| elif target > nums[mid]:
| 25| low = mid + 1
| 26| else:
| 27| break
| 28|
| 29| for j in range(len(nums) - 1, -1, -1):
| 30| if nums[j] == target:
| 31| return [mid, j]
| 32|
| 33| return [-1, -1]
#========================================================================================================================
# :: search/first_occurrence.py ::
#========================================================================================================================
| 1| #
| 2| # Find first occurance of a number in a sorted array (increasing order)
| 3| # Approach- Binary Search
| 4| # T(n)- O(log n)
| 5| #
| 6| def first_occurrence(array, query):
| 7| lo, hi = 0, len(array) - 1
| 8| while lo <= hi:
| 9| mid = (lo + hi) // 2
| 10| #print("lo: ", lo, " hi: ", hi, " mid: ", mid)
| 11| if lo == hi:
| 12| break
| 13| if array[mid] < query:
| 14| lo = mid + 1
| 15| else:
| 16| hi = mid
| 17| if array[lo] == query:
| 18| return lo
#========================================================================================================================
# :: search/__init__.py ::
#========================================================================================================================
| 1| from .binary_search import *
| 2| from .first_occurrence import *
| 3| from .last_occurrence import *
| 4| from .linear_search import *
| 5| from .search_insert import *
| 6| from .two_sum import *
| 7| from .search_range import *
| 8| from .find_min_rotate import *
| 9| from .search_rotate import *
| 10| from .jump_search import *
| 11| from .next_greatest_letter import *
#========================================================================================================================
# :: search/next_greatest_letter.py ::
#========================================================================================================================
| 1| '''
| 2| Given a list of sorted characters letters containing only lowercase letters,
| 3| and given a target letter target, find the smallest element in the list that
| 4| is larger than the given target.
| 5|
| 6| Letters also wrap around. For example, if the target is target = 'z' and
| 7| letters = ['a', 'b'], the answer is 'a'.
| 8|
| 9| Input:
| 10| letters = ["c", "f", "j"]
| 11| target = "a"
| 12| Output: "c"
| 13|
| 14| Input:
| 15| letters = ["c", "f", "j"]
| 16| target = "c"
| 17| Output: "f"
| 18|
| 19| Input:
| 20| letters = ["c", "f", "j"]
| 21| target = "d"
| 22| Output: "f"
| 23|
| 24| Reference: https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/
| 25| '''
| 26|
| 27| import bisect
| 28|
| 29| """
| 30| Using bisect libarary
| 31| """
| 32| def next_greatest_letter(letters, target):
| 33| index = bisect.bisect(letters, target)
| 34| return letters[index % len(letters)]
| 35|
| 36| """
| 37| Using binary search: complexity O(logN)
| 38| """
| 39| def next_greatest_letter_v1(letters, target):
| 40| if letters[0] > target:
| 41| return letters[0]
| 42| if letters[len(letters) - 1] <= target:
| 43| return letters[0]
| 44| left, right = 0, len(letters) - 1
| 45| while left <= right:
| 46| mid = left + (right - left) // 2
| 47| if letters[mid] > target:
| 48| right = mid - 1
| 49| else:
| 50| left = mid + 1
| 51| return letters[left]
| 52|
| 53| """
| 54| Brute force: complexity O(N)
| 55| """
| 56| def next_greatest_letter_v2(letters, target):
| 57| for index in letters:
| 58| if index > target:
| 59| return index
| 60| return letters[0]
#========================================================================================================================
# :: search/search_insert.py ::
#========================================================================================================================
| 1| """
| 2| Given a sorted array and a target value, return the index if the target is
| 3| found. If not, return the index where it would be if it were inserted in order.
| 4|
| 5| For example:
| 6| [1,3,5,6], 5 -> 2
| 7| [1,3,5,6], 2 -> 1
| 8| [1,3,5,6], 7 -> 4
| 9| [1,3,5,6], 0 -> 0
| 10| """
| 11| def search_insert(array, val):
| 12| low = 0
| 13| high = len(array) - 1
| 14| while low <= high:
| 15| mid = low + (high - low) // 2
| 16| if val > array[mid]:
| 17| low = mid + 1
| 18| else:
| 19| high = mid - 1
| 20| return low
#========================================================================================================================
# :: search/linear_search.py ::
#========================================================================================================================
| 1| #
| 2| # Linear search works in any array.
| 3| #
| 4| # T(n): O(n)
| 5| #
| 6|
| 7| def linear_search(array, query):
| 8| for i in range(len(array)):
| 9| if array[i] == query:
| 10| return i
| 11|
| 12| return -1
#========================================================================================================================
# :: search/find_min_rotate.py ::
#========================================================================================================================
| 1| """
| 2| Suppose an array sorted in ascending order is rotated at some pivot unknown
| 3| to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
| 4|
| 5| Find the minimum element. The complexity must be O(logN)
| 6|
| 7| You may assume no duplicate exists in the array.
| 8| """
| 9| def find_min_rotate(array):
| 10| low = 0
| 11| high = len(array) - 1
| 12| while low < high:
| 13| mid = (low + high) // 2
| 14| if array[mid] > array[high]:
| 15| low = mid + 1
| 16| else:
| 17| high = mid
| 18|
| 19| return array[low]
| 20|
| 21| def find_min_rotate_recur(array, low, high):
| 22| mid = (low + high) // 2
| 23| if mid == low:
| 24| return array[low]
| 25| elif array[mid] > array[high]:
| 26| return find_min_rotate_recur(array, mid + 1, high)
| 27| else:
| 28| return find_min_rotate_recur(array, low, mid)
#========================================================================================================================
# :: search/two_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of integers that is already sorted in ascending order, find two
| 3| numbers such that they add up to a specific target number. The function two_sum
| 4| should return indices of the two numbers such that they add up to the target,
| 5| where index1 must be less than index2. Please note that your returned answers
| 6| (both index1 and index2) are not zero-based.
| 7| You may assume that each input would have exactly one solution and you
| 8| may not use the same element twice.
| 9|
| 10| Input: numbers = [2, 7, 11, 15], target=9
| 11| Output: index1 = 1, index2 = 2
| 12|
| 13| Solution:
| 14| two_sum: using binary search
| 15| two_sum1: using dictionary as a hash table
| 16| two_sum2: using two pointers
| 17| """
| 18| # Using binary search technique
| 19| def two_sum(numbers, target):
| 20| for i in range(len(numbers)):
| 21| second_val = target - numbers[i]
| 22| low, high = i+1, len(numbers)-1
| 23| while low <= high:
| 24| mid = low + (high - low) // 2
| 25| if second_val == numbers[mid]:
| 26| return [i + 1, mid + 1]
| 27| elif second_val > numbers[mid]:
| 28| low = mid + 1
| 29| else:
| 30| high = mid - 1
| 31|
| 32| # Using dictionary as a hash table
| 33| def two_sum1(numbers, target):
| 34| dic = {}
| 35| for i, num in enumerate(numbers):
| 36| if target - num in dic:
| 37| return [dic[target - num] + 1, i + 1]
| 38| dic[num] = i
| 39|
| 40| # Using two pointers
| 41| def two_sum2(numbers, target):
| 42| p1 = 0 # pointer 1 holds from left of array numbers
| 43| p2 = len(numbers) - 1 # pointer 2 holds from right of array numbers
| 44| while p1 < p2:
| 45| s = numbers[p1] + numbers[p2]
| 46| if s == target:
| 47| return [p1 + 1, p2 + 1]
| 48| elif s > target:
| 49| p2 = p2 - 1
| 50| else:
| 51| p1 = p1 + 1
#========================================================================================================================
# :: search/last_occurrence.py ::
#========================================================================================================================
| 1| #
| 2| # Find last occurance of a number in a sorted array (increasing order)
| 3| # Approach- Binary Search
| 4| # T(n)- O(log n)
| 5| #
| 6| def last_occurrence(array, query):
| 7| lo, hi = 0, len(array) - 1
| 8| while lo <= hi:
| 9| mid = (hi + lo) // 2
| 10| if (array[mid] == query and mid == len(array)-1) or \
| 11| (array[mid] == query and array[mid+1] > query):
| 12| return mid
| 13| elif (array[mid] <= query):
| 14| lo = mid + 1
| 15| else:
| 16| hi = mid - 1
#========================================================================================================================
# :: search/jump_search.py ::
#========================================================================================================================
| 1| import math
| 2|
| 3| def jump_search(arr,target):
| 4| """Jump Search
| 5| Worst-case Complexity: O(√n) (root(n))
| 6| All items in list must be sorted like binary search
| 7|
| 8| Find block that contains target value and search it linearly in that block
| 9| It returns a first target value in array
| 10|
| 11| reference: https://en.wikipedia.org/wiki/Jump_search
| 12|
| 13| """
| 14| n = len(arr)
| 15| block_size = int(math.sqrt(n))
| 16| block_prev = 0
| 17| block= block_size
| 18|
| 19| # return -1 means that array doesn't contain taget value
| 20| # find block that contains target value
| 21|
| 22| if arr[n - 1] < target:
| 23| return -1
| 24| while block <= n and arr[block - 1] < target:
| 25| block_prev = block
| 26| block += block_size
| 27|
| 28| # find target value in block
| 29|
| 30| while arr[block_prev] < target :
| 31| block_prev += 1
| 32| if block_prev == min(block, n) :
| 33| return -1
| 34|
| 35| # if there is target value in array, return it
| 36|
| 37| if arr[block_prev] == target :
| 38| return block_prev
| 39| else :
| 40| return -1
#========================================================================================================================
# :: unix/__init__.py ::
#========================================================================================================================
| 1| from .path.join_with_slash import *
| 2| from .path.full_path import *
| 3| from .path.split import *
| 4| from .path.simplify_path import *
#========================================================================================================================
# :: unix/path/full_path.py ::
#========================================================================================================================
| 1| """
| 2| Get a full absolute path a file
| 3| """
| 4| import os
| 5| def full_path(file):
| 6| return os.path.abspath(os.path.expanduser(file))
#========================================================================================================================
# :: unix/path/split.py ::
#========================================================================================================================
| 1| """
| 2| Splitting a path into 2 parts
| 3| Example:
| 4| Input: https://algorithms/unix/test.py (for url)
| 5| Output:
| 6| part[0]: https://algorithms/unix
| 7| part[1]: test.py
| 8|
| 9| Input: algorithms/unix/test.py (for file path)
| 10| Output:
| 11| part[0]: algorithms/unix
| 12| part[1]: test.py
| 13| """
| 14| import os
| 15|
| 16| def split(path):
| 17| parts = []
| 18| split_part = path.rpartition('/')
| 19| # Takt the origin path without the last part
| 20| parts.append(split_part[0])
| 21| # Take the last element of list
| 22| parts.append(split_part[2])
| 23| return parts
#========================================================================================================================
# :: unix/path/join_with_slash.py ::
#========================================================================================================================
| 1| """
| 2| Both URL and file path joins use slashes as dividers between their parts.
| 3| For example:
| 4|
| 5| path/to/dir + file --> path/to/dir/file
| 6| path/to/dir/ + file --> path/to/dir/file
| 7| http://algorithms.com/ + part --> http://algorithms.com/part
| 8| http://algorithms.com + part --> http://algorithms/part
| 9| """
| 10| import os
| 11|
| 12| def join_with_slash(base, suffix):
| 13| # Remove / trailing
| 14| base = base.rstrip('/')
| 15| # Remove / leading
| 16| suffix = suffix.lstrip('/').rstrip()
| 17| full_path = "{}/{}".format(base, suffix)
| 18| return full_path
#========================================================================================================================
# :: unix/path/simplify_path.py ::
#========================================================================================================================
| 1| """
| 2| Given an absolute path for a file (Unix-style), simplify it.
| 3|
| 4| For example,
| 5| path = "/home/", => "/home"
| 6| path = "/a/./b/../../c/", => "/c"
| 7|
| 8| Corner Cases:
| 9|
| 10| Did you consider the case where path = "/../"?
| 11| In this case, you should return "/".
| 12| Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/".
| 13| In this case, you should ignore redundant slashes and return "/home/foo".
| 14|
| 15| Reference: https://leetcode.com/problems/simplify-path/description/
| 16| """
| 17|
| 18| import os
| 19| def simplify_path_v1(path):
| 20| return os.path.abspath(path)
| 21|
| 22| def simplify_path_v2(path):
| 23| stack, tokens = [], path.split("/")
| 24| for token in tokens:
| 25| if token == ".." and stack:
| 26| stack.pop()
| 27| elif token != ".." and token != "." and token:
| 28| stack.append(token)
| 29| return "/" + "/".join(stack)
#========================================================================================================================
# :: map/separate_chaining_hashtable.py ::
#========================================================================================================================
| 1| import unittest
| 2|
| 3|
| 4| class Node(object):
| 5| def __init__(self, key=None, value=None, next=None):
| 6| self.key = key
| 7| self.value = value
| 8| self.next = next
| 9|
| 10|
| 11| class SeparateChainingHashTable(object):
| 12| """
| 13| HashTable Data Type:
| 14| By having each bucket contain a linked list of elements that are hashed to that bucket.
| 15|
| 16| Usage:
| 17| >>> table = SeparateChainingHashTable() # Create a new, empty map.
| 18| >>> table.put('hello', 'world') # Add a new key-value pair.
| 19| >>> len(table) # Return the number of key-value pairs stored in the map.
| 20| 1
| 21| >>> table.get('hello') # Get value by key.
| 22| 'world'
| 23| >>> del table['hello'] # Equivalent to `table.del_('hello')`, deleting key-value pair.
| 24| >>> table.get('hello') is None # Return `None` if a key doesn't exist.
| 25| True
| 26| """
| 27| _empty = None
| 28|
| 29| def __init__(self, size=11):
| 30| self.size = size
| 31| self._len = 0
| 32| self._table = [self._empty] * size
| 33|
| 34| def put(self, key, value):
| 35| hash_ = self.hash(key)
| 36| node_ = self._table[hash_]
| 37| if node_ is self._empty:
| 38| self._table[hash_] = Node(key, value)
| 39| else:
| 40| while node_.next is not None:
| 41| if node_.key == key:
| 42| node_.value = value
| 43| return
| 44| node_ = node_.next
| 45| node_.next = Node(key, value)
| 46| self._len += 1
| 47|
| 48| def get(self, key):
| 49| hash_ = self.hash(key)
| 50| node_ = self._table[hash_]
| 51| while node_ is not self._empty:
| 52| if node_.key == key:
| 53| return node_.value
| 54| node_ = node_.next
| 55| return None
| 56|
| 57| def del_(self, key):
| 58| hash_ = self.hash(key)
| 59| node_ = self._table[hash_]
| 60| pre_node = None
| 61| while node_ is not None:
| 62| if node_.key == key:
| 63| if pre_node is None:
| 64| self._table[hash_] = node_.next
| 65| else:
| 66| pre_node.next = node_.next
| 67| self._len -= 1
| 68| pre_node = node_
| 69| node_ = node_.next
| 70|
| 71| def hash(self, key):
| 72| return hash(key) % self.size
| 73|
| 74| def __len__(self):
| 75| return self._len
| 76|
| 77| def __getitem__(self, key):
| 78| return self.get(key)
| 79|
| 80| def __delitem__(self, key):
| 81| return self.del_(key)
| 82|
| 83| def __setitem__(self, key, value):
| 84| self.put(key, value)
#========================================================================================================================
# :: map/is_isomorphic.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings s and t, determine if they are isomorphic.
| 3| Two strings are isomorphic if the characters in s can be replaced to get t.
| 4| All occurrences of a character must be replaced with another character while
| 5| preserving the order of characters. No two characters may map to the same
| 6| character but a character may map to itself.
| 7|
| 8| Example 1:
| 9| Input: s = "egg", t = "add"
| 10| Output: true
| 11|
| 12| Example 2:
| 13| Input: s = "foo", t = "bar"
| 14| Output: false
| 15|
| 16| Example 3:
| 17| Input: s = "paper", t = "title"
| 18| Output: true
| 19| Reference: https://leetcode.com/problems/isomorphic-strings/description/
| 20| """
| 21| def is_isomorphic(s, t):
| 22| """
| 23| :type s: str
| 24| :type t: str
| 25| :rtype: bool
| 26| """
| 27| if len(s) != len(t):
| 28| return False
| 29| dict = {}
| 30| set_value = set()
| 31| for i in range(len(s)):
| 32| if s[i] not in dict:
| 33| if t[i] in set_value:
| 34| return False
| 35| dict[s[i]] = t[i]
| 36| set_value.add(t[i])
| 37| else:
| 38| if dict[s[i]] != t[i]:
| 39| return False
| 40| return True
#========================================================================================================================
# :: map/longest_common_subsequence.py ::
#========================================================================================================================
| 1| """
| 2| Given string a and b, with b containing all distinct characters,
| 3| find the longest common sub sequence's length.
| 4|
| 5| Expected complexity O(n logn).
| 6| """
| 7|
| 8|
| 9| def max_common_sub_string(s1, s2):
| 10| # Assuming s2 has all unique chars
| 11| s2dic = {s2[i]: i for i in range(len(s2))}
| 12| maxr = 0
| 13| subs = ''
| 14| i = 0
| 15| while i < len(s1):
| 16| if s1[i] in s2dic:
| 17| j = s2dic[s1[i]]
| 18| k = i
| 19| while j < len(s2) and k < len(s1) and s1[k] == s2[j]:
| 20| k += 1
| 21| j += 1
| 22| if k - i > maxr:
| 23| maxr = k-i
| 24| subs = s1[i:k]
| 25| i = k
| 26| else:
| 27| i += 1
| 28| return subs
#========================================================================================================================
# :: map/word_pattern.py ::
#========================================================================================================================
| 1| """
| 2| Given a pattern and a string str, find if str follows the same pattern.
| 3| Here follow means a full match, such that there is a bijection between a
| 4| letter in pattern and a non-empty word in str.
| 5|
| 6| Example 1:
| 7| Input: pattern = "abba", str = "dog cat cat dog"
| 8| Output: true
| 9|
| 10| Example 2:
| 11| Input:pattern = "abba", str = "dog cat cat fish"
| 12| Output: false
| 13|
| 14| Example 3:
| 15| Input: pattern = "aaaa", str = "dog cat cat dog"
| 16| Output: false
| 17|
| 18| Example 4:
| 19| Input: pattern = "abba", str = "dog dog dog dog"
| 20| Output: false
| 21| Notes:
| 22| You may assume pattern contains only lowercase letters, and str contains lowercase letters separated by a single space.
| 23| Reference: https://leetcode.com/problems/word-pattern/description/
| 24| """
| 25| def word_pattern(pattern, str):
| 26| dict = {}
| 27| set_value = set()
| 28| list_str = str.split()
| 29| if len(list_str) != len(pattern):
| 30| return False
| 31| for i in range(len(pattern)):
| 32| if pattern[i] not in dict:
| 33| if list_str[i] in set_value:
| 34| return False
| 35| dict[pattern[i]] = list_str[i]
| 36| set_value.add(list_str[i])
| 37| else:
| 38| if dict[pattern[i]] != list_str[i]:
| 39| return False
| 40| return True
#========================================================================================================================
# :: map/is_anagram.py ::
#========================================================================================================================
| 1| """
| 2| Given two strings s and t , write a function to determine if t is an anagram of s.
| 3|
| 4| Example 1:
| 5| Input: s = "anagram", t = "nagaram"
| 6| Output: true
| 7|
| 8| Example 2:
| 9| Input: s = "rat", t = "car"
| 10| Output: false
| 11|
| 12| Note:
| 13| You may assume the string contains only lowercase alphabets.
| 14|
| 15| Reference: https://leetcode.com/problems/valid-anagram/description/
| 16| """
| 17| def is_anagram(s, t):
| 18| """
| 19| :type s: str
| 20| :type t: str
| 21| :rtype: bool
| 22| """
| 23| maps = {}
| 24| mapt = {}
| 25| for i in s:
| 26| maps[i] = maps.get(i, 0) + 1
| 27| for i in t:
| 28| mapt[i] = mapt.get(i, 0) + 1
| 29| return maps == mapt
#========================================================================================================================
# :: map/__init__.py ::
#========================================================================================================================
| 1| from .hashtable import *
| 2| from .separate_chaining_hashtable import *
| 3| from .word_pattern import *
| 4| from .is_isomorphic import *
| 5| from .is_anagram import *
#========================================================================================================================
# :: map/randomized_set.py ::
#========================================================================================================================
| 1| """
| 2| Design a data structure that supports all following operations
| 3| in average O(1) time.
| 4|
| 5| insert(val): Inserts an item val to the set if not already present.
| 6| remove(val): Removes an item val from the set if present.
| 7| getRandom: Returns a random element from current set of elements.
| 8| Each element must have the same probability of being returned.
| 9| """
| 10|
| 11|
| 12| import random
| 13|
| 14|
| 15| class RandomizedSet:
| 16| def __init__(self):
| 17| self.nums = []
| 18| self.idxs = {}
| 19|
| 20| def insert(self, val):
| 21| if val not in self.idxs:
| 22| self.nums.append(val)
| 23| self.idxs[val] = len(self.nums)-1
| 24| return True
| 25| return False
| 26|
| 27| def remove(self, val):
| 28| if val in self.idxs:
| 29| idx, last = self.idxs[val], self.nums[-1]
| 30| self.nums[idx], self.idxs[last] = last, idx
| 31| self.nums.pop()
| 32| self.idxs.pop(val, 0)
| 33| return True
| 34| return False
| 35|
| 36| def get_random(self):
| 37| idx = random.randint(0, len(self.nums)-1)
| 38| return self.nums[idx]
| 39|
| 40|
| 41| if __name__ == "__main__":
| 42| rs = RandomizedSet()
| 43| print("insert 1: ", rs.insert(1))
| 44| print("insert 2: ", rs.insert(2))
| 45| print("insert 3: ", rs.insert(3))
| 46| print("insert 4: ", rs.insert(4))
| 47| print("remove 3: ", rs.remove(3))
| 48| print("remove 3: ", rs.remove(3))
| 49| print("remove 1: ", rs.remove(1))
| 50| print("random: ", rs.get_random())
| 51| print("random: ", rs.get_random())
| 52| print("random: ", rs.get_random())
| 53| print("random: ", rs.get_random())
#========================================================================================================================
# :: map/hashtable.py ::
#========================================================================================================================
| 1| class HashTable(object):
| 2| """
| 3| HashMap Data Type
| 4| HashMap() Create a new, empty map. It returns an empty map collection.
| 5| put(key, val) Add a new key-value pair to the map. If the key is already in the map then replace
| 6| the old value with the new value.
| 7| get(key) Given a key, return the value stored in the map or None otherwise.
| 8| del_(key) or del map[key] Delete the key-value pair from the map using a statement of the form del map[key].
| 9| len() Return the number of key-value pairs stored in the map.
| 10| in Return True for a statement of the form key in map, if the given key is in the map, False otherwise.
| 11| """
| 12|
| 13| _empty = object()
| 14| _deleted = object()
| 15|
| 16| def __init__(self, size=11):
| 17| self.size = size
| 18| self._len = 0
| 19| self._keys = [self._empty] * size # keys
| 20| self._values = [self._empty] * size # values
| 21|
| 22| def put(self, key, value):
| 23| initial_hash = hash_ = self.hash(key)
| 24|
| 25| while True:
| 26| if self._keys[hash_] is self._empty or self._keys[hash_] is self._deleted:
| 27| # can assign to hash_ index
| 28| self._keys[hash_] = key
| 29| self._values[hash_] = value
| 30| self._len += 1
| 31| return
| 32| elif self._keys[hash_] == key:
| 33| # key already exists here, assign over
| 34| self._keys[hash_] = key
| 35| self._values[hash_] = value
| 36| return
| 37|
| 38| hash_ = self._rehash(hash_)
| 39|
| 40| if initial_hash == hash_:
| 41| # table is full
| 42| raise ValueError("Table is full")
| 43|
| 44| def get(self, key):
| 45| initial_hash = hash_ = self.hash(key)
| 46| while True:
| 47| if self._keys[hash_] is self._empty:
| 48| # That key was never assigned
| 49| return None
| 50| elif self._keys[hash_] == key:
| 51| # key found
| 52| return self._values[hash_]
| 53|
| 54| hash_ = self._rehash(hash_)
| 55| if initial_hash == hash_:
| 56| # table is full and wrapped around
| 57| return None
| 58|
| 59| def del_(self, key):
| 60| initial_hash = hash_ = self.hash(key)
| 61| while True:
| 62| if self._keys[hash_] is self._empty:
| 63| # That key was never assigned
| 64| return None
| 65| elif self._keys[hash_] == key:
| 66| # key found, assign with deleted sentinel
| 67| self._keys[hash_] = self._deleted
| 68| self._values[hash_] = self._deleted
| 69| self._len -= 1
| 70| return
| 71|
| 72| hash_ = self._rehash(hash_)
| 73| if initial_hash == hash_:
| 74| # table is full and wrapped around
| 75| return None
| 76|
| 77| def hash(self, key):
| 78| return key % self.size
| 79|
| 80| def _rehash(self, old_hash):
| 81| """
| 82| linear probing
| 83| """
| 84| return (old_hash + 1) % self.size
| 85|
| 86| def __getitem__(self, key):
| 87| return self.get(key)
| 88|
| 89| def __delitem__(self, key):
| 90| return self.del_(key)
| 91|
| 92| def __setitem__(self, key, value):
| 93| self.put(key, value)
| 94|
| 95| def __len__(self):
| 96| return self._len
| 97|
| 98|
| 99| class ResizableHashTable(HashTable):
|100| MIN_SIZE = 8
|101|
|102| def __init__(self):
|103| super().__init__(self.MIN_SIZE)
|104|
|105| def put(self, key, value):
|106| rv = super().put(key, value)
|107| # increase size of dict * 2 if filled >= 2/3 size (like python dict)
|108| if len(self) >= (self.size * 2) / 3:
|109| self.__resize()
|110|
|111| def __resize(self):
|112| keys, values = self._keys, self._values
|113| self.size *= 2 # this will be the new size
|114| self._len = 0
|115| self._keys = [self._empty] * self.size
|116| self._values = [self._empty] * self.size
|117| for key, value in zip(keys, values):
|118| if key is not self._empty and key is not self._deleted:
|119| self.put(key, value)
#========================================================================================================================
# :: map/valid_sudoku.py ::
#========================================================================================================================
| 1| """
| 2| Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules.
| 3|
| 4| The Sudoku board could be partially filled, where empty cells are filled with
| 5| the character '.'.
| 6| """
| 7|
| 8| def is_valid_sudoku(self, board):
| 9| seen = []
| 10| for i, row in enumerate(board):
| 11| for j, c in enumerate(row):
| 12| if c != '.':
| 13| seen += [(c,j),(i,c),(i/3,j/3,c)]
| 14| return len(seen) == len(set(seen))
#========================================================================================================================
# :: heap/binary_heap.py ::
#========================================================================================================================
| 1| """
| 2| Binary Heap. A min heap is a complete binary tree where each node is smaller
| 3| its childen. The root, therefore, is the minimum element in the tree. The min
| 4| heap use array to represent the data and operation. For example a min heap:
| 5|
| 6| 4
| 7| / \
| 8| 50 7
| 9| / \ /
| 10| 55 90 87
| 11|
| 12| Heap [0, 4, 50, 7, 55, 90, 87]
| 13|
| 14| Method in class: insert, remove_min
| 15| For example insert(2) in a min heap:
| 16|
| 17| 4 4 2
| 18| / \ / \ / \
| 19| 50 7 --> 50 2 --> 50 4
| 20| / \ / \ / \ / \ / \ / \
| 21| 55 90 87 2 55 90 87 7 55 90 87 7
| 22|
| 23| For example remove_min() in a min heap:
| 24|
| 25| 4 87 7
| 26| / \ / \ / \
| 27| 50 7 --> 50 7 --> 50 87
| 28| / \ / / \ / \
| 29| 55 90 87 55 90 55 90
| 30|
| 31| """
| 32| from abc import ABCMeta, abstractmethod
| 33|
| 34| class AbstractHeap(metaclass=ABCMeta):
| 35| """Abstract Class for Binary Heap."""
| 36| def __init__(self):
| 37| pass
| 38| @abstractmethod
| 39| def perc_up(self, i):
| 40| pass
| 41| @abstractmethod
| 42| def insert(self, val):
| 43| pass
| 44| @abstractmethod
| 45| def perc_down(self,i):
| 46| pass
| 47| @abstractmethod
| 48| def min_child(self,i):
| 49| pass
| 50| @abstractmethod
| 51| def remove_min(self,i):
| 52| pass
| 53| class BinaryHeap(AbstractHeap):
| 54| def __init__(self):
| 55| self.currentSize = 0
| 56| self.heap = [(0)]
| 57|
| 58| def perc_up(self, i):
| 59| while i // 2 > 0:
| 60| if self.heap[i] < self.heap[i // 2]:
| 61| # Swap value of child with value of its parent
| 62| self.heap[i], self.heap[i//2] = self.heap[i//2], self.heap[i]
| 63| i = i // 2
| 64|
| 65| """
| 66| Method insert always start by inserting the element at the bottom.
| 67| it inserts rightmost spot so as to maintain the complete tree property
| 68| Then, it fix the tree by swapping the new element with its parent,
| 69| until it finds an appropriate spot for the element. It essentially
| 70| perc_up the minimum element
| 71| Complexity: O(logN)
| 72| """
| 73| def insert(self, val):
| 74| self.heap.append(val)
| 75| self.currentSize = self.currentSize + 1
| 76| self.perc_up(self.currentSize)
| 77|
| 78| """
| 79| Method min_child returns index of smaller 2 childs of its parent
| 80| """
| 81| def min_child(self, i):
| 82| if 2 * i + 1 > self.currentSize: # No right child
| 83| return 2 * i
| 84| else:
| 85| # left child > right child
| 86| if self.heap[2 * i] > self.heap[2 * i +1]:
| 87| return 2 * i + 1
| 88| else:
| 89| return 2 * i
| 90|
| 91| def perc_down(self, i):
| 92| while 2 * i < self.currentSize:
| 93| min_child = self.min_child(i)
| 94| if self.heap[min_child] < self.heap[i]:
| 95| # Swap min child with parent
| 96| self.heap[min_child], self.heap[i] = self.heap[i], self.heap[min_child]
| 97| i = min_child
| 98| """
| 99| Remove Min method removes the minimum element and swap it with the last
|100| element in the heap( the bottommost, rightmost element). Then, it
|101| perc_down this element, swapping it with one of its children until the
|102| min heap property is restored
|103| Complexity: O(logN)
|104| """
|105| def remove_min(self):
|106| ret = self.heap[1] # the smallest value at beginning
|107| self.heap[1] = self.heap[self.currentSize] # Repalce it by the last value
|108| self.currentSize = self.currentSize - 1
|109| self.heap.pop()
|110| self.perc_down(1)
|111| return ret
#========================================================================================================================
# :: heap/k_closest_points.py ::
#========================================================================================================================
| 1| """Given a list of points, find the k closest to the origin.
| 2|
| 3| Idea: Maintain a max heap of k elements.
| 4| We can iterate through all points.
| 5| If a point p has a smaller distance to the origin than the top element of a heap, we add point p to the heap and remove the top element.
| 6| After iterating through all points, our heap contains the k closest points to the origin.
| 7| """
| 8|
| 9|
| 10| from heapq import heapify, heappushpop
| 11|
| 12|
| 13| def k_closest(points, k, origin=(0, 0)):
| 14| # Time: O(k+(n-k)logk)
| 15| # Space: O(k)
| 16| """Initialize max heap with first k points.
| 17| Python does not support a max heap; thus we can use the default min heap where the keys (distance) are negated.
| 18| """
| 19| heap = [(-distance(p, origin), p) for p in points[:k]]
| 20| heapify(heap)
| 21|
| 22| """
| 23| For every point p in points[k:],
| 24| check if p is smaller than the root of the max heap;
| 25| if it is, add p to heap and remove root. Reheapify.
| 26| """
| 27| for p in points[k:]:
| 28| d = distance(p, origin)
| 29|
| 30| heappushpop(heap, (-d, p)) # heappushpop does conditional check
| 31| """Same as:
| 32| if d < -heap[0][0]:
| 33| heappush(heap, (-d,p))
| 34| heappop(heap)
| 35|
| 36| Note: heappushpop is more efficient than separate push and pop calls.
| 37| Each heappushpop call takes O(logk) time.
| 38| """
| 39|
| 40| return [p for nd, p in heap] # return points in heap
| 41|
| 42|
| 43| def distance(point, origin=(0, 0)):
| 44| return (point[0] - origin[0])**2 + (point[1] - origin[1])**2
#========================================================================================================================
# :: heap/__init__.py ::
#========================================================================================================================
| 1| from .binary_heap import *
| 2| from .skyline import *
| 3| from .sliding_window_max import *
| 4| from .merge_sorted_k_lists import *
| 5| from .k_closest_points import *
#========================================================================================================================
# :: heap/sliding_window_max.py ::
#========================================================================================================================
| 1| """
| 2| Given an array nums, there is a sliding window of size k
| 3| which is moving from the very left of the array to the very right.
| 4| You can only see the k numbers in the window.
| 5| Each time the sliding window moves right by one position.
| 6|
| 7| For example,
| 8| Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.
| 9|
| 10| Window position Max
| 11| --------------- -----
| 12| [1 3 -1] -3 5 3 6 7 3
| 13| 1 [3 -1 -3] 5 3 6 7 3
| 14| 1 3 [-1 -3 5] 3 6 7 5
| 15| 1 3 -1 [-3 5 3] 6 7 5
| 16| 1 3 -1 -3 [5 3 6] 7 6
| 17| 1 3 -1 -3 5 [3 6 7] 7
| 18| Therefore, return the max sliding window as [3,3,5,5,6,7].
| 19| """
| 20| import collections
| 21|
| 22|
| 23| def max_sliding_window(nums, k):
| 24| """
| 25| :type nums: List[int]
| 26| :type k: int
| 27| :rtype: List[int]
| 28| """
| 29| if not nums:
| 30| return nums
| 31| queue = collections.deque()
| 32| res = []
| 33| for num in nums:
| 34| if len(queue) < k:
| 35| queue.append(num)
| 36| else:
| 37| res.append(max(queue))
| 38| queue.popleft()
| 39| queue.append(num)
| 40| res.append(max(queue))
| 41| return res
#========================================================================================================================
# :: heap/merge_sorted_k_lists.py ::
#========================================================================================================================
| 1| """
| 2| Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
| 3| """
| 4|
| 5|
| 6| from heapq import heappop, heapreplace, heapify
| 7| from queue import PriorityQueue
| 8|
| 9|
| 10| # Definition for singly-linked list.
| 11| class ListNode(object):
| 12| def __init__(self, x):
| 13| self.val = x
| 14| self.next = None
| 15|
| 16|
| 17| def merge_k_lists(lists):
| 18| dummy = node = ListNode(0)
| 19| h = [(n.val, n) for n in lists if n]
| 20| heapify(h)
| 21| while h:
| 22| v, n = h[0]
| 23| if n.next is None:
| 24| heappop(h) # only change heap size when necessary
| 25| else:
| 26| heapreplace(h, (n.next.val, n.next))
| 27| node.next = n
| 28| node = node.next
| 29|
| 30| return dummy.next
| 31|
| 32|
| 33| def merge_k_lists(lists):
| 34| dummy = ListNode(None)
| 35| curr = dummy
| 36| q = PriorityQueue()
| 37| for node in lists:
| 38| if node:
| 39| q.put((node.val, node))
| 40| while not q.empty():
| 41| curr.next = q.get()[1] # These two lines seem to
| 42| curr = curr.next # be equivalent to :- curr = q.get()[1]
| 43| if curr.next:
| 44| q.put((curr.next.val, curr.next))
| 45| return dummy.next
| 46|
| 47|
| 48| """
| 49| I think my code's complexity is also O(nlogk) and not using heap or priority queue,
| 50| n means the total elements and k means the size of list.
| 51|
| 52| The mergeTwoLists function in my code comes from the problem Merge Two Sorted Lists
| 53| whose complexity obviously is O(n), n is the sum of length of l1 and l2.
| 54|
| 55| To put it simpler, assume the k is 2^x, So the progress of combination is like a full binary tree,
| 56| from bottom to top. So on every level of tree, the combination complexity is n,
| 57| because every level have all n numbers without repetition.
| 58| The level of tree is x, ie log k. So the complexity is O(n log k).
| 59|
| 60| for example, 8 ListNode, and the length of every ListNode is x1, x2,
| 61| x3, x4, x5, x6, x7, x8, total is n.
| 62|
| 63| on level 3: x1+x2, x3+x4, x5+x6, x7+x8 sum: n
| 64|
| 65| on level 2: x1+x2+x3+x4, x5+x6+x7+x8 sum: n
| 66|
| 67| on level 1: x1+x2+x3+x4+x5+x6+x7+x8 sum: n
| 68| """
#========================================================================================================================
# :: heap/skyline.py ::
#========================================================================================================================
| 1| # -*- coding: utf-8 -*-
| 2| """
| 3| A city's skyline is the outer contour of the silhouette formed by all the buildings
| 4| in that city when viewed from a distance.
| 5| Now suppose you are given the locations and height of all the buildings
| 6| as shown on a cityscape photo (Figure A),
| 7| write a program to output the skyline formed by these buildings collectively (Figure B).
| 8|
| 9| The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi],
| 10| where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively,
| 11| and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX, and Ri - Li > 0.
| 12| You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.
| 13|
| 14| For instance, the dimensions of all buildings in Figure A are recorded as:
| 15| [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] .
| 16|
| 17| The output is a list of "key points" (red dots in Figure B) in the format of
| 18| [ [x1,y1], [x2, y2], [x3, y3], ... ]
| 19| that uniquely defines a skyline.
| 20| A key point is the left endpoint of a horizontal line segment. Note that the last key point,
| 21| where the rightmost building ends,
| 22| is merely used to mark the termination of the skyline, and always has zero height.
| 23| Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.
| 24|
| 25| For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ].
| 26|
| 27| Notes:
| 28|
| 29| The number of buildings in any input list is guaranteed to be in the range [0, 10000].
| 30| The input list is already sorted in ascending order by the left x position Li.
| 31| The output list must be sorted by the x position.
| 32| There must be no consecutive horizontal lines of equal height in the output skyline. For instance,
| 33| [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged
| 34| into one in the final output as such: [...[2 3], [4 5], [12 7], ...]
| 35|
| 36| """
| 37| import heapq
| 38|
| 39| def get_skyline(lrh):
| 40| """
| 41| Wortst Time Complexity: O(NlogN)
| 42| :type buildings: List[List[int]]
| 43| :rtype: List[List[int]]
| 44| """
| 45| skyline, live = [], []
| 46| i, n = 0, len(lrh)
| 47| while i < n or live:
| 48| if not live or i < n and lrh[i][0] <= -live[0][1]:
| 49| x = lrh[i][0]
| 50| while i < n and lrh[i][0] == x:
| 51| heapq.heappush(live, (-lrh[i][2], -lrh[i][1]))
| 52| i += 1
| 53| else:
| 54| x = -live[0][1]
| 55| while live and -live[0][1] <= x:
| 56| heapq.heappop(live)
| 57| height = len(live) and -live[0][0]
| 58| if not skyline or height != skyline[-1][1]:
| 59| skyline += [x, height],
| 60| return skyline
#========================================================================================================================
# :: ml/nearest_neighbor.py ::
#========================================================================================================================
| 1| import math
| 2|
| 3| def distance(x,y):
| 4| """[summary]
| 5| HELPER-FUNCTION
| 6| calculates the (eulidean) distance between vector x and y.
| 7|
| 8| Arguments:
| 9| x {[tuple]} -- [vector]
| 10| y {[tuple]} -- [vector]
| 11| """
| 12| assert len(x) == len(y), "The vector must have same length"
| 13| result = ()
| 14| sum = 0
| 15| for i in range(len(x)):
| 16| result += (x[i] -y[i],)
| 17| for component in result:
| 18| sum += component**2
| 19| return math.sqrt(sum)
| 20|
| 21|
| 22| def nearest_neighbor(x, tSet):
| 23| """[summary]
| 24| Implements the nearest neighbor algorithm
| 25|
| 26| Arguments:
| 27| x {[tupel]} -- [vector]
| 28| tSet {[dict]} -- [training set]
| 29|
| 30| Returns:
| 31| [type] -- [result of the AND-function]
| 32| """
| 33| assert isinstance(x, tuple) and isinstance(tSet, dict)
| 34| current_key = ()
| 35| min_d = float('inf')
| 36| for key in tSet:
| 37| d = distance(x, key)
| 38| if d < min_d:
| 39| min_d = d
| 40| current_key = key
| 41| return tSet[current_key]
#========================================================================================================================
# :: bfs/shortest_distance_from_all_buildings.py ::
#========================================================================================================================
| 1| import collections
| 2|
| 3| """
| 4| do BFS from each building, and decrement all empty place for every building visit
| 5| when grid[i][j] == -b_nums, it means that grid[i][j] are already visited from all b_nums
| 6| and use dist to record distances from b_nums
| 7| """
| 8|
| 9| def shortest_distance(grid):
| 10| if not grid or not grid[0]:
| 11| return -1
| 12|
| 13| matrix = [[[0,0] for i in range(len(grid[0]))] for j in range(len(grid))]
| 14|
| 15| count = 0 # count how many building we have visited
| 16| for i in range(len(grid)):
| 17| for j in range(len(grid[0])):
| 18| if grid[i][j] == 1:
| 19| bfs(grid, matrix, i, j, count)
| 20| count += 1
| 21|
| 22| res = float('inf')
| 23| for i in range(len(matrix)):
| 24| for j in range(len(matrix[0])):
| 25| if matrix[i][j][1]==count:
| 26| res = min(res, matrix[i][j][0])
| 27|
| 28| return res if res!=float('inf') else -1
| 29|
| 30| def bfs(grid, matrix, i, j, count):
| 31| q = [(i, j, 0)]
| 32| while q:
| 33| i, j, step = q.pop(0)
| 34| for k, l in [(i-1,j), (i+1,j), (i,j-1), (i,j+1)]:
| 35| # only the position be visited by count times will append to queue
| 36| if 0<=k<len(grid) and 0<=l<len(grid[0]) and \
| 37| matrix[k][l][1]==count and grid[k][l]==0:
| 38| matrix[k][l][0] += step+1
| 39| matrix[k][l][1] = count+1
| 40| q.append((k, l, step+1))
#========================================================================================================================
# :: bfs/__init__.py ::
#========================================================================================================================
| 1| from .maze_search import *
| 2| from .shortest_distance_from_all_buildings import *
| 3| from .word_ladder import *
#========================================================================================================================
# :: bfs/word_ladder.py ::
#========================================================================================================================
| 1| """
| 2| Given two words (begin_word and end_word), and a dictionary's word list,
| 3| find the length of shortest transformation sequence
| 4| from beginWord to endWord, such that:
| 5|
| 6| Only one letter can be changed at a time
| 7| Each intermediate word must exist in the word list
| 8| For example,
| 9|
| 10| Given:
| 11| begin_word = "hit"
| 12| end_word = "cog"
| 13| word_list = ["hot","dot","dog","lot","log"]
| 14| As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
| 15| return its length 5.
| 16|
| 17| Note:
| 18| Return -1 if there is no such transformation sequence.
| 19| All words have the same length.
| 20| All words contain only lowercase alphabetic characters.
| 21| """
| 22|
| 23|
| 24| def ladder_length(begin_word, end_word, word_list):
| 25| """
| 26| Bidirectional BFS!!!
| 27| :type begin_word: str
| 28| :type end_word: str
| 29| :type word_list: Set[str]
| 30| :rtype: int
| 31| """
| 32| if len(begin_word) != len(end_word):
| 33| return -1 # not possible
| 34|
| 35| if begin_word == end_word:
| 36| return 0
| 37|
| 38| # when only differ by 1 character
| 39| if sum(c1 != c2 for c1, c2 in zip(begin_word, end_word)) == 1:
| 40| return 1
| 41|
| 42| begin_set = set()
| 43| end_set = set()
| 44| begin_set.add(begin_word)
| 45| end_set.add(end_word)
| 46| result = 2
| 47| while begin_set and end_set:
| 48|
| 49| if len(begin_set) > len(end_set):
| 50| begin_set, end_set = end_set, begin_set
| 51|
| 52| next_begin_set = set()
| 53| for word in begin_set:
| 54| for ladder_word in word_range(word):
| 55| if ladder_word in end_set:
| 56| return result
| 57| if ladder_word in word_list:
| 58| next_begin_set.add(ladder_word)
| 59| word_list.remove(ladder_word)
| 60| begin_set = next_begin_set
| 61| result += 1
| 62| # print(begin_set)
| 63| # print(result)
| 64| return -1
| 65|
| 66|
| 67| def word_range(word):
| 68| for ind in range(len(word)):
| 69| temp = word[ind]
| 70| for c in [chr(x) for x in range(ord('a'), ord('z') + 1)]:
| 71| if c != temp:
| 72| yield word[:ind] + c + word[ind + 1:]
#========================================================================================================================
# :: bfs/maze_search.py ::
#========================================================================================================================
| 1| '''
| 2| BFS time complexity : O(|E|)
| 3| BFS space complexity : O(|V|)
| 4|
| 5| do BFS from (0,0) of the grid and get the minimum number of steps needed to get to the lower right column
| 6|
| 7| only step on the columns whose value is 1
| 8|
| 9| if there is no path, it returns -1
| 10|
| 11| Ex 1)
| 12| If grid is
| 13| [[1,0,1,1,1,1],
| 14| [1,0,1,0,1,0],
| 15| [1,0,1,0,1,1],
| 16| [1,1,1,0,1,1]],
| 17| the answer is: 14
| 18|
| 19| Ex 2)
| 20| If grid is
| 21| [[1,0,0],
| 22| [0,1,1],
| 23| [0,1,1]],
| 24| the answer is: -1
| 25| '''
| 26|
| 27| def maze_search(grid):
| 28| dx = [0,0,-1,1]
| 29| dy = [-1,1,0,0]
| 30| n = len(grid)
| 31| m = len(grid[0])
| 32| q = [(0,0,0)]
| 33| visit = [[0]*m for _ in range(n)]
| 34| if grid[0][0] == 0:
| 35| return -1
| 36| visit[0][0] = 1
| 37| while q:
| 38| i, j, step = q.pop(0)
| 39| if i == n-1 and j == m-1:
| 40| return step
| 41| for k in range(4):
| 42| x = i + dx[k]
| 43| y = j + dy[k]
| 44| if x>=0 and x<n and y>=0 and y<m:
| 45| if grid[x][y] ==1 and visit[x][y] == 0:
| 46| visit[x][y] = 1
| 47| q.append((x,y,step+1))
| 48| return -1
| 49|
| 50|
| 51| grid = [[1,1,1,1,1,1],
| 52| [1,0,1,0,1,0],
| 53| [1,0,1,0,1,1],
| 54| [1,1,1,0,1,1]]
| 55|
| 56| print(maze_search(grid))
#========================================================================================================================
# :: union-find/count_islands.py ::
#========================================================================================================================
| 1| """
| 2| A 2d grid map of m rows and n columns is initially filled with water.
| 3| We may perform an addLand operation which turns the water at position
| 4| (row, col) into a land. Given a list of positions to operate,
| 5| count the number of islands after each addLand operation.
| 6| An island is surrounded by water and is formed by connecting adjacent
| 7| lands horizontally or vertically.
| 8| You may assume all four edges of the grid are all surrounded by water.
| 9|
| 10| Given m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]].
| 11| Initially, the 2d grid grid is filled with water.
| 12| (Assume 0 represents water and 1 represents land).
| 13|
| 14| 0 0 0
| 15| 0 0 0
| 16| 0 0 0
| 17| Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land.
| 18|
| 19| 1 0 0
| 20| 0 0 0 Number of islands = 1
| 21| 0 0 0
| 22| Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land.
| 23|
| 24| 1 1 0
| 25| 0 0 0 Number of islands = 1
| 26| 0 0 0
| 27| Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land.
| 28|
| 29| 1 1 0
| 30| 0 0 1 Number of islands = 2
| 31| 0 0 0
| 32| Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land.
| 33|
| 34| 1 1 0
| 35| 0 0 1 Number of islands = 3
| 36| 0 1 0
| 37| """
| 38|
| 39|
| 40| class Solution(object):
| 41| def num_islands2(self, m, n, positions):
| 42| ans = []
| 43| islands = Union()
| 44| for p in map(tuple, positions):
| 45| islands.add(p)
| 46| for dp in (0, 1), (0, -1), (1, 0), (-1, 0):
| 47| q = (p[0] + dp[0], p[1] + dp[1])
| 48| if q in islands.id:
| 49| islands.unite(p, q)
| 50| ans += [islands.count]
| 51| return ans
| 52|
| 53| class Union(object):
| 54| def __init__(self):
| 55| self.id = {}
| 56| self.sz = {}
| 57| self.count = 0
| 58|
| 59| def add(self, p):
| 60| self.id[p] = p
| 61| self.sz[p] = 1
| 62| self.count += 1
| 63|
| 64| def root(self, i):
| 65| while i != self.id[i]:
| 66| self.id[i] = self.id[self.id[i]]
| 67| i = self.id[i]
| 68| return i
| 69|
| 70| def unite(self, p, q):
| 71| i, j = self.root(p), self.root(q)
| 72| if i == j:
| 73| return
| 74| if self.sz[i] > self.sz[j]:
| 75| i, j = j, i
| 76| self.id[i] = j
| 77| self.sz[j] += self.sz[i]
| 78| self.count -= 1
#========================================================================================================================
# :: arrays/longest_non_repeat.py ::
#========================================================================================================================
| 1| """
| 2| Given a string, find the length of the longest substring
| 3| without repeating characters.
| 4|
| 5| Examples:
| 6| Given "abcabcbb", the answer is "abc", which the length is 3.
| 7| Given "bbbbb", the answer is "b", with the length of 1.
| 8| Given "pwwkew", the answer is "wke", with the length of 3.
| 9| Note that the answer must be a substring,
| 10| "pwke" is a subsequence and not a substring.
| 11| """
| 12|
| 13|
| 14| def longest_non_repeat_v1(string):
| 15| """
| 16| Find the length of the longest substring
| 17| without repeating characters.
| 18| """
| 19| if string is None:
| 20| return 0
| 21| dict = {}
| 22| max_length = 0
| 23| j = 0
| 24| for i in range(len(string)):
| 25| if string[i] in dict:
| 26| j = max(dict[string[i]], j)
| 27| dict[string[i]] = i + 1
| 28| max_length = max(max_length, i - j + 1)
| 29| return max_length
| 30|
| 31| def longest_non_repeat_v2(string):
| 32| """
| 33| Find the length of the longest substring
| 34| without repeating characters.
| 35| Uses alternative algorithm.
| 36| """
| 37| if string is None:
| 38| return 0
| 39| start, max_len = 0, 0
| 40| used_char = {}
| 41| for index, char in enumerate(string):
| 42| if char in used_char and start <= used_char[char]:
| 43| start = used_char[char] + 1
| 44| else:
| 45| max_len = max(max_len, index - start + 1)
| 46| used_char[char] = index
| 47| return max_len
| 48|
| 49| # get functions of above, returning the max_len and substring
| 50| def get_longest_non_repeat_v1(string):
| 51| """
| 52| Find the length of the longest substring
| 53| without repeating characters.
| 54| Return max_len and the substring as a tuple
| 55| """
| 56| if string is None:
| 57| return 0, ''
| 58| sub_string = ''
| 59| dict = {}
| 60| max_length = 0
| 61| j = 0
| 62| for i in range(len(string)):
| 63| if string[i] in dict:
| 64| j = max(dict[string[i]], j)
| 65| dict[string[i]] = i + 1
| 66| if i - j + 1 > max_length:
| 67| max_length = i - j + 1
| 68| sub_string = string[j: i + 1]
| 69| return max_length, sub_string
| 70|
| 71| def get_longest_non_repeat_v2(string):
| 72| """
| 73| Find the length of the longest substring
| 74| without repeating characters.
| 75| Uses alternative algorithm.
| 76| Return max_len and the substring as a tuple
| 77| """
| 78| if string is None:
| 79| return 0, ''
| 80| sub_string = ''
| 81| start, max_len = 0, 0
| 82| used_char = {}
| 83| for index, char in enumerate(string):
| 84| if char in used_char and start <= used_char[char]:
| 85| start = used_char[char] + 1
| 86| else:
| 87| if index - start + 1 > max_len:
| 88| max_len = index - start + 1
| 89| sub_string = string[start: index + 1]
| 90| used_char[char] = index
| 91| return max_len, sub_string
#========================================================================================================================
# :: arrays/flatten.py ::
#========================================================================================================================
| 1| """
| 2| Implement Flatten Arrays.
| 3| Given an array that may contain nested arrays,
| 4| produce a single resultant array.
| 5| """
| 6| from collections import Iterable
| 7|
| 8|
| 9| # return list
| 10| def flatten(input_arr, output_arr=None):
| 11| if output_arr is None:
| 12| output_arr = []
| 13| for ele in input_arr:
| 14| if isinstance(ele, Iterable):
| 15| flatten(ele, output_arr) #tail-recursion
| 16| else:
| 17| output_arr.append(ele) #produce the result
| 18| return output_arr
| 19|
| 20|
| 21| # returns iterator
| 22| def flatten_iter(iterable):
| 23| """
| 24| Takes as input multi dimensional iterable and
| 25| returns generator which produces one dimensional output.
| 26| """
| 27| for element in iterable:
| 28| if isinstance(element, Iterable):
| 29| yield from flatten_iter(element)
| 30| else:
| 31| yield element
#========================================================================================================================
# :: arrays/max_ones_index.py ::
#========================================================================================================================
| 1| """
| 2| Find the index of 0 to be replaced with 1 to get
| 3| longest continuous sequence
| 4| of 1s in a binary array.
| 5| Returns index of 0 to be
| 6| replaced with 1 to get longest
| 7| continuous sequence of 1s.
| 8| If there is no 0 in array, then
| 9| it returns -1.
| 10|
| 11| e.g.
| 12| let input array = [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1]
| 13| If we replace 0 at index 3 with 1, we get the longest continuous
| 14| sequence of 1s in the array.
| 15| So the function return => 3
| 16| """
| 17|
| 18|
| 19| def max_ones_index(arr):
| 20|
| 21| n = len(arr)
| 22| max_count = 0
| 23| max_index = 0
| 24| prev_zero = -1
| 25| prev_prev_zero = -1
| 26|
| 27| for curr in range(n):
| 28|
| 29| # If current element is 0,
| 30| # then calculate the difference
| 31| # between curr and prev_prev_zero
| 32| if arr[curr] == 0:
| 33| if curr - prev_prev_zero > max_count:
| 34| max_count = curr - prev_prev_zero
| 35| max_index = prev_zero
| 36|
| 37| prev_prev_zero = prev_zero
| 38| prev_zero = curr
| 39|
| 40| if n - prev_prev_zero > max_count:
| 41| max_index = prev_zero
| 42|
| 43| return max_index
#========================================================================================================================
# :: arrays/plus_one.py ::
#========================================================================================================================
| 1| """
| 2| Given a non-negative number represented as an array of digits,
| 3| adding one to each numeral.
| 4|
| 5| The digits are stored big-endian, such that the most significant
| 6| digit is at the head of the list.
| 7| """
| 8|
| 9|
| 10| def plus_one_v1(digits):
| 11| """
| 12| :type digits: List[int]
| 13| :rtype: List[int]
| 14| """
| 15| digits[-1] = digits[-1] + 1
| 16| res = []
| 17| ten = 0
| 18| i = len(digits)-1
| 19| while i >= 0 or ten == 1:
| 20| summ = 0
| 21| if i >= 0:
| 22| summ += digits[i]
| 23| if ten:
| 24| summ += 1
| 25| res.append(summ % 10)
| 26| ten = summ // 10
| 27| i -= 1
| 28| return res[::-1]
| 29|
| 30|
| 31| def plus_one_v2(digits):
| 32| n = len(digits)
| 33| for i in range(n-1, -1, -1):
| 34| if digits[i] < 9:
| 35| digits[i] += 1
| 36| return digits
| 37| digits[i] = 0
| 38| digits.insert(0, 1)
| 39| return digits
| 40|
| 41|
| 42| def plus_one_v3(num_arr):
| 43|
| 44| for idx in reversed(list(enumerate(num_arr))):
| 45| num_arr[idx[0]] = (num_arr[idx[0]] + 1) % 10
| 46| if num_arr[idx[0]]:
| 47| return num_arr
| 48| return [1] + num_arr
#========================================================================================================================
# :: arrays/n_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of n integers, are there elements a, b, .. , n in nums
| 3| such that a + b + .. + n = target?
| 4|
| 5| Find all unique n-tuplets in the array which gives the sum of target.
| 6|
| 7| Example:
| 8| basic:
| 9| Given:
| 10| n = 4
| 11| nums = [1, 0, -1, 0, -2, 2]
| 12| target = 0,
| 13| return [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
| 14|
| 15| advanced:
| 16| Given:
| 17| n = 2
| 18| nums = [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]]
| 19| target = -5
| 20| def sum(a, b):
| 21| return [a[0] + b[1], a[1] + b[0]]
| 22| def compare(num, target):
| 23| if num[0] < target:
| 24| return -1
| 25| elif if num[0] > target:
| 26| return 1
| 27| else:
| 28| return 0
| 29| return [[-9, 5], [8, 4]]
| 30| (TL:DR) because -9 + 4 = -5
| 31| """
| 32|
| 33|
| 34| def n_sum(n, nums, target, **kv):
| 35| """
| 36| n: int
| 37| nums: list[object]
| 38| target: object
| 39| sum_closure: function, optional
| 40| Given two elements of nums, return sum of both.
| 41| compare_closure: function, optional
| 42| Given one object of nums and target, return -1, 1, or 0.
| 43| same_closure: function, optional
| 44| Given two object of nums, return bool.
| 45| return: list[list[object]]
| 46|
| 47| Note:
| 48| 1. type of sum_closure's return should be same
| 49| as type of compare_closure's first param
| 50| """
| 51|
| 52| def sum_closure_default(a, b):
| 53| return a + b
| 54|
| 55| def compare_closure_default(num, target):
| 56| """ above, below, or right on? """
| 57| if num < target:
| 58| return -1
| 59| elif num > target:
| 60| return 1
| 61| else:
| 62| return 0
| 63|
| 64| def same_closure_default(a, b):
| 65| return a == b
| 66|
| 67| def n_sum(n, nums, target):
| 68| if n == 2: # want answers with only 2 terms? easy!
| 69| results = two_sum(nums, target)
| 70| else:
| 71| results = []
| 72| prev_num = None
| 73| for index, num in enumerate(nums):
| 74| if prev_num is not None and \
| 75| same_closure(prev_num, num):
| 76| continue
| 77|
| 78| prev_num = num
| 79| n_minus1_results = (
| 80| n_sum( # recursive call
| 81| n - 1, # a
| 82| nums[index + 1:], # b
| 83| target - num # c
| 84| ) # x = n_sum( a, b, c )
| 85| ) # n_minus1_results = x
| 86|
| 87| n_minus1_results = (
| 88| append_elem_to_each_list(num, n_minus1_results)
| 89| )
| 90| results += n_minus1_results
| 91| return union(results)
| 92|
| 93| def two_sum(nums, target):
| 94| nums.sort()
| 95| lt = 0
| 96| rt = len(nums) - 1
| 97| results = []
| 98| while lt < rt:
| 99| sum_ = sum_closure(nums[lt], nums[rt])
|100| flag = compare_closure(sum_, target)
|101| if flag == -1:
|102| lt += 1
|103| elif flag == 1:
|104| rt -= 1
|105| else:
|106| results.append(sorted([nums[lt], nums[rt]]))
|107| lt += 1
|108| rt -= 1
|109| while (lt < len(nums) and
|110| same_closure(nums[lt - 1], nums[lt])):
|111| lt += 1
|112| while (0 <= rt and
|113| same_closure(nums[rt], nums[rt + 1])):
|114| rt -= 1
|115| return results
|116|
|117| def append_elem_to_each_list(elem, container):
|118| results = []
|119| for elems in container:
|120| elems.append(elem)
|121| results.append(sorted(elems))
|122| return results
|123|
|124| def union(duplicate_results):
|125| results = []
|126|
|127| if len(duplicate_results) != 0:
|128| duplicate_results.sort()
|129| results.append(duplicate_results[0])
|130| for result in duplicate_results[1:]:
|131| if results[-1] != result:
|132| results.append(result)
|133|
|134| return results
|135|
|136| sum_closure = kv.get('sum_closure', sum_closure_default)
|137| same_closure = kv.get('same_closure', same_closure_default)
|138| compare_closure = kv.get('compare_closure', compare_closure_default)
|139| nums.sort()
|140| return n_sum(n, nums, target)
#========================================================================================================================
# :: arrays/__init__.py ::
#========================================================================================================================
| 1| from .delete_nth import *
| 2| from .flatten import *
| 3| from .garage import *
| 4| from .josephus import *
| 5| from .longest_non_repeat import *
| 6| from .max_ones_index import *
| 7| from .merge_intervals import *
| 8| from .missing_ranges import *
| 9| from .move_zeros import *
| 10| from .plus_one import *
| 11| from .rotate import *
| 12| from .summarize_ranges import *
| 13| from .three_sum import *
| 14| from .trimmean import *
| 15| from .top_1 import *
| 16| from .two_sum import *
| 17| from .limit import *
| 18| from .n_sum import *
#========================================================================================================================
# :: arrays/missing_ranges.py ::
#========================================================================================================================
| 1| """
| 2| Find missing ranges between low and high in the given array.
| 3| Ex) [3, 5] lo=1 hi=10 => answer: [(1, 2), (4, 4), (6, 10)]
| 4| """
| 5|
| 6| def missing_ranges(arr, lo, hi):
| 7|
| 8| res = []
| 9| start = lo
| 10|
| 11| for n in arr:
| 12|
| 13| if n == start:
| 14| start += 1
| 15| elif n > start:
| 16| res.append((start, n-1))
| 17| start = n + 1
| 18|
| 19| if start <= hi: # after done iterating thru array,
| 20| res.append((start, hi)) # append remainder to list
| 21|
| 22| return res
#========================================================================================================================
# :: arrays/limit.py ::
#========================================================================================================================
| 1| """
| 2| Sometimes you need to limit array result to use. Such as you only need the
| 3| value over 10 or, you need value under than 100. By use this algorithms, you
| 4| can limit your array to specific value
| 5|
| 6| If array, Min, Max value was given, it returns array that contains values of
| 7| given array which was larger than Min, and lower than Max. You need to give
| 8| 'unlimit' to use only Min or Max.
| 9|
| 10| ex) limit([1,2,3,4,5], None, 3) = [1,2,3]
| 11|
| 12| Complexity = O(n)
| 13| """
| 14|
| 15| # tl:dr -- array slicing by value
| 16| def limit(arr, min_lim=None, max_lim=None):
| 17| if min_lim is None and max_lim is not None:
| 18| return [x for x in arr if x <= max_lim]
| 19| if max_lim is None and min_lim is not None:
| 20| return [x for x in arr if x >= min_lim]
| 21| if max_lim is not None and min_lim is not None:
| 22| return [x for x in arr if min_lim <= x <= max_lim]
| 23| return arr
#========================================================================================================================
# :: arrays/summarize_ranges.py ::
#========================================================================================================================
| 1| """
| 2| Given a sorted integer array without duplicates,
| 3| return the summary of its ranges.
| 4|
| 5| For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)].
| 6| """
| 7|
| 8|
| 9| def summarize_ranges(array):
| 10| """
| 11| :type array: List[int]
| 12| :rtype: List[]
| 13| """
| 14| res = []
| 15| if len(array) == 1:
| 16| return [str(array[0])]
| 17| i = 0
| 18| while i < len(array):
| 19| num = array[i]
| 20| while i + 1 < len(array) and array[i + 1] - array[i] == 1:
| 21| i += 1
| 22| if array[i] != num:
| 23| res.append((num, array[i]))
| 24| else:
| 25| res.append((num, num))
| 26| i += 1
| 27| return res
#========================================================================================================================
# :: arrays/move_zeros.py ::
#========================================================================================================================
| 1| """
| 2| Write an algorithm that takes an array and moves all of the zeros to the end,
| 3| preserving the order of the other elements.
| 4| move_zeros([false, 1, 0, 1, 2, 0, 1, 3, "a"])
| 5| returns => [false, 1, 1, 2, 1, 3, "a", 0, 0]
| 6|
| 7| The time complexity of the below algorithm is O(n).
| 8| """
| 9|
| 10|
| 11| # False == 0 is True
| 12| def move_zeros(array):
| 13| result = []
| 14| zeros = 0
| 15|
| 16| for i in array:
| 17| if i == 0 and type(i) != bool: # not using `not i` to avoid `False`, `[]`, etc.
| 18| zeros += 1
| 19| else:
| 20| result.append(i)
| 21|
| 22| result.extend([0] * zeros)
| 23| return result
| 24|
| 25|
| 26| print(move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]))
#========================================================================================================================
# :: arrays/three_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given an array S of n integers, are there three distinct elements
| 3| a, b, c in S such that a + b + c = 0?
| 4| Find all unique triplets in the array which gives the sum of zero.
| 5|
| 6| Note: The solution set must not contain duplicate triplets.
| 7|
| 8| For example, given array S = [-1, 0, 1, 2, -1, -4],
| 9|
| 10| A solution set is:
| 11| {
| 12| (-1, 0, 1),
| 13| (-1, -1, 2)
| 14| }
| 15| """
| 16|
| 17|
| 18| def three_sum(array):
| 19| """
| 20| :param array: List[int]
| 21| :return: Set[ Tuple[int, int, int] ]
| 22| """
| 23| res = set()
| 24| array.sort()
| 25| for i in range(len(array) - 2):
| 26| if i > 0 and array[i] == array[i - 1]:
| 27| continue
| 28| l, r = i + 1, len(array) - 1
| 29| while l < r:
| 30| s = array[i] + array[l] + array[r]
| 31| if s > 0:
| 32| r -= 1
| 33| elif s < 0:
| 34| l += 1
| 35| else:
| 36| # found three sum
| 37| res.add((array[i], array[l], array[r]))
| 38|
| 39| # remove duplicates
| 40| while l < r and array[l] == array[l + 1]:
| 41| l += 1
| 42|
| 43| while l < r and array[r] == array[r - 1]:
| 44| r -= 1
| 45|
| 46| l += 1
| 47| r -= 1
| 48| return res
#========================================================================================================================
# :: arrays/trimmean.py ::
#========================================================================================================================
| 1| """
| 2| When make reliable means, we need to neglect best and worst values.
| 3| For example, when making average score on athletes we need this option.
| 4| So, this algorithm affixes some percentage to neglect when making mean.
| 5| For example, if you suggest 20%, it will neglect the best 10% of values
| 6| and the worst 10% of values.
| 7|
| 8| This algorithm takes an array and percentage to neglect. After sorted,
| 9| if index of array is larger or smaller than desired ratio, we don't
| 10| compute it.
| 11|
| 12| Compleity: O(n)
| 13| """
| 14| def trimmean(arr, per):
| 15| ratio = per/200
| 16| # /100 for easy calculation by *, and /2 for easy adaption to best and worst parts.
| 17| cal_sum = 0
| 18| # sum value to be calculated to trimmean.
| 19| arr.sort()
| 20| neg_val = int(len(arr)*ratio)
| 21| arr = arr[neg_val:len(arr)-neg_val]
| 22| for i in arr:
| 23| cal_sum += i
| 24| return cal_sum/len(arr)
#========================================================================================================================
# :: arrays/top_1.py ::
#========================================================================================================================
| 1| """
| 2| This algorithm receives an array and returns most_frequent_value
| 3| Also, sometimes it is possible to have multiple 'most_frequent_value's,
| 4| so this function returns a list. This result can be used to find a
| 5| representative value in an array.
| 6|
| 7| This algorithm gets an array, makes a dictionary of it,
| 8| finds the most frequent count, and makes the result list.
| 9|
| 10| For example: top_1([1, 1, 2, 2, 3, 4]) will return [1, 2]
| 11|
| 12| (TL:DR) Get mathematical Mode
| 13| Complexity: O(n)
| 14| """
| 15| def top_1(arr):
| 16| values = {}
| 17| #reserve each value which first appears on keys
| 18| #reserve how many time each value appears by index number on values
| 19| result = []
| 20| f_val = 0
| 21|
| 22| for i in arr:
| 23| if i in values:
| 24| values[i] += 1
| 25| else:
| 26| values[i] = 1
| 27|
| 28| f_val = max(values.values())
| 29|
| 30| for i in values.keys():
| 31| if values[i] == f_val:
| 32| result.append(i)
| 33| else:
| 34| continue
| 35|
| 36| return result
| 37|
#========================================================================================================================
# :: arrays/delete_nth.py ::
#========================================================================================================================
| 1| """
| 2| Given a list lst and a number N, create a new list
| 3| that contains each number of the list at most N times without reordering.
| 4|
| 5| For example if N = 2, and the input is [1,2,3,1,2,1,2,3], you take [1,2,3,1,2],
| 6| drop the next [1,2] since this would lead to 1 and 2 being in the result 3 times, and then take 3,
| 7| which leads to [1,2,3,1,2,3]
| 8| """
| 9| import collections
| 10|
| 11|
| 12| # Time complexity O(n^2)
| 13| def delete_nth_naive(array, n):
| 14| ans = []
| 15| for num in array:
| 16| if ans.count(num) < n:
| 17| ans.append(num)
| 18| return ans
| 19|
| 20|
| 21| # Time Complexity O(n), using hash tables.
| 22| def delete_nth(array, n):
| 23| result = []
| 24| counts = collections.defaultdict(int) # keep track of occurrences
| 25|
| 26| for i in array:
| 27|
| 28| if counts[i] < n:
| 29| result.append(i)
| 30| counts[i] += 1
| 31|
| 32| return result
#========================================================================================================================
# :: arrays/rotate.py ::
#========================================================================================================================
| 1| """
| 2| Rotate an array of n elements to the right by k steps.
| 3|
| 4| For example, with n = 7 and k = 3,
| 5| the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4].
| 6|
| 7| Note:
| 8| Try to come up as many solutions as you can,
| 9| there are at least 3 different ways to solve this problem.
| 10| """
| 11|
| 12|
| 13| def rotate_v1(array, k):
| 14| """
| 15| Rotate the entire array 'k' times
| 16| T(n)- O(nk)
| 17|
| 18| :type array: List[int]
| 19| :type k: int
| 20| :rtype: void Do not return anything, modify array in-place instead.
| 21| """
| 22| array = array[:]
| 23| n = len(array)
| 24| for i in range(k): # unused variable is not a problem
| 25| temp = array[n - 1]
| 26| for j in range(n-1, 0, -1):
| 27| array[j] = array[j - 1]
| 28| array[0] = temp
| 29| return array
| 30|
| 31|
| 32| def rotate_v2(array, k):
| 33| """
| 34| Reverse segments of the array, followed by the entire array
| 35| T(n)- O(n)
| 36| :type array: List[int]
| 37| :type k: int
| 38| :rtype: void Do not return anything, modify nums in-place instead.
| 39| """
| 40| array = array[:]
| 41|
| 42| def reverse(arr, a, b):
| 43| while a < b:
| 44| arr[a], arr[b] = arr[b], arr[a]
| 45| a += 1
| 46| b -= 1
| 47|
| 48| n = len(array)
| 49| k = k % n
| 50| reverse(array, 0, n - k - 1)
| 51| reverse(array, n - k, n - 1)
| 52| reverse(array, 0, n - 1)
| 53| return array
| 54|
| 55|
| 56| def rotate_v3(array, k):
| 57| if array is None:
| 58| return None
| 59| length = len(array)
| 60| k = k % length
| 61| return array[length - k:] + array[:length - k]
#========================================================================================================================
# :: arrays/josephus.py ::
#========================================================================================================================
| 1| """
| 2| There are people sitting in a circular fashion,
| 3| print every third member while removing them,
| 4| the next counter starts immediately after the member is removed.
| 5| Print till all the members are exhausted.
| 6|
| 7| For example:
| 8| Input: consider 123456789 members sitting in a circular fashion,
| 9| Output: 369485271
| 10| """
| 11|
| 12|
| 13| def josephus(int_list, skip):
| 14| skip = skip - 1 # list starts with 0 index
| 15| idx = 0
| 16| len_list = (len(int_list))
| 17| while len_list > 0:
| 18| idx = (skip + idx) % len_list # hash index to every 3rd
| 19| yield int_list.pop(idx)
| 20| len_list -= 1
#========================================================================================================================
# :: arrays/two_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given an array of integers, return indices of the two numbers
| 3| such that they add up to a specific target.
| 4|
| 5| You may assume that each input would have exactly one solution,
| 6| and you may not use the same element twice.
| 7|
| 8| Example:
| 9| Given nums = [2, 7, 11, 15], target = 9,
| 10|
| 11| Because nums[0] + nums[1] = 2 + 7 = 9,
| 12| return (0, 1)
| 13| """
| 14|
| 15|
| 16| def two_sum(array, target):
| 17| dic = {}
| 18| for i, num in enumerate(array):
| 19| if num in dic:
| 20| return dic[num], i
| 21| else:
| 22| dic[target - num] = i
| 23| return None
#========================================================================================================================
# :: arrays/merge_intervals.py ::
#========================================================================================================================
| 1| """
| 2| In mathematics, a (real) interval is a set of real
| 3| numbers with the property that any number that lies
| 4| between two numbers in the set is also included in the set.
| 5| """
| 6|
| 7|
| 8| class Interval:
| 9| """
| 10| A set of real numbers with methods to determine if other
| 11| numbers are included in the set.
| 12| Includes related methods to merge and print interval sets.
| 13| """
| 14| def __init__(self, start=0, end=0):
| 15| self.start = start
| 16| self.end = end
| 17|
| 18| def __repr__(self):
| 19| return "Interval ({}, {})".format(self.start, self.end)
| 20|
| 21| def __iter__(self):
| 22| return iter(range(self.start, self.end))
| 23|
| 24| def __getitem__(self, index):
| 25| if index < 0:
| 26| return self.end + index
| 27| return self.start + index
| 28|
| 29| def __len__(self):
| 30| return self.end - self.start
| 31|
| 32| def __contains__(self, item):
| 33| if self.start >= item >= self.end:
| 34| return True
| 35| return False
| 36|
| 37| def __eq__(self, other):
| 38| if self.start == other.start and self.end == other.end:
| 39| return True
| 40| return False
| 41|
| 42| def as_list(self):
| 43| """ Return interval as list. """
| 44| return list(self)
| 45|
| 46| @staticmethod
| 47| def merge(intervals):
| 48| """ Merge two intervals into one. """
| 49| out = []
| 50| for i in sorted(intervals, key=lambda i: i.start):
| 51| if out and i.start <= out[-1].end:
| 52| out[-1].end = max(out[-1].end, i.end)
| 53| else:
| 54| out += i,
| 55| return out
| 56|
| 57| @staticmethod
| 58| def print_intervals(intervals):
| 59| """ Print out the intervals. """
| 60| res = []
| 61| for i in intervals:
| 62| res.append(repr(i))
| 63| print("".join(res))
| 64|
| 65|
| 66| def merge_intervals(intervals):
| 67| """ Merge intervals in the form of a list. """
| 68| if intervals is None:
| 69| return None
| 70| intervals.sort(key=lambda i: i[0])
| 71| out = [intervals.pop(0)]
| 72| for i in intervals:
| 73| if out[-1][-1] >= i[0]:
| 74| out[-1][-1] = max(out[-1][-1], i[-1])
| 75| else:
| 76| out.append(i)
| 77| return out
#========================================================================================================================
# :: arrays/garage.py ::
#========================================================================================================================
| 1| """
| 2| There is a parking lot with only one empty spot. Given the initial state
| 3| of the parking lot and the final state. Each step we are only allowed to
| 4| move a car
| 5| out of its place and move it into the empty spot.
| 6| The goal is to find out the least movement needed to rearrange
| 7| the parking lot from the initial state to the final state.
| 8|
| 9| Say the initial state is an array:
| 10|
| 11| [1, 2, 3, 0, 4],
| 12| where 1, 2, 3, 4 are different cars, and 0 is the empty spot.
| 13|
| 14| And the final state is
| 15|
| 16| [0, 3, 2, 1, 4].
| 17| We can swap 1 with 0 in the initial array to get [0, 2, 3, 1, 4] and so on.
| 18| Each step swap with 0 only.
| 19|
| 20| Edit:
| 21| Now also prints the sequence of changes in states.
| 22| Output of this example :-
| 23|
| 24| initial: [1, 2, 3, 0, 4]
| 25| final: [0, 3, 2, 1, 4]
| 26| Steps = 4
| 27| Sequence :
| 28| 0 2 3 1 4
| 29| 2 0 3 1 4
| 30| 2 3 0 1 4
| 31| 0 3 2 1 4
| 32| """
| 33|
| 34|
| 35| def garage(initial, final):
| 36|
| 37| initial = initial[::] # prevent changes in original 'initial'
| 38| seq = [] # list of each step in sequence
| 39| steps = 0
| 40| while initial != final:
| 41| zero = initial.index(0)
| 42| if zero != final.index(0): # if zero isn't where it should be,
| 43| car_to_move = final[zero] # what should be where zero is,
| 44| pos = initial.index(car_to_move) # and where is it?
| 45| initial[zero], initial[pos] = initial[pos], initial[zero]
| 46| else:
| 47| for i in range(len(initial)):
| 48| if initial[i] != final[i]:
| 49| initial[zero], initial[i] = initial[i], initial[zero]
| 50| break
| 51| seq.append(initial[::])
| 52| steps += 1
| 53|
| 54| return steps, seq
| 55| # e.g.: 4, [{0, 2, 3, 1, 4}, {2, 0, 3, 1, 4},
| 56| # {2, 3, 0, 1, 4}, {0, 3, 2, 1, 4}]
| 57|
| 58| """
| 59| thus:
| 60| 1 2 3 0 4 -- zero = 3, true, car_to_move = final[3] = 1,
| 61| pos = initial.index(1) = 0, switched [0], [3]
| 62| 0 2 3 1 4 -- zero = 0, f, initial[1] != final[1], switched 0,1
| 63| 2 0 3 1 4 -- zero = 1, t, car_to_move = final[1] = 3,
| 64| pos = initial.index(3) = 2, switched [1], [2]
| 65| 2 3 0 1 4 -- zero = 2, t, car_to_move = final[2] = 2,
| 66| pos = initial.index(2) = 0, switched [0], [2]
| 67| 0 3 2 1 4 -- initial == final
| 68| """
#========================================================================================================================
# :: compression/__init__.py ::
#========================================================================================================================
#========================================================================================================================
# :: compression/huffman_coding.py ::
#========================================================================================================================
| 1| """
| 2| Huffman coding is an efficient method of compressing data without losing information.
| 3| This algorithm analyzes the symbols that appear in a message.
| 4| Symbols that appear more often will be encoded as a shorter-bit string
| 5| while symbols that aren't used as much will be encoded as longer strings.
| 6| """
| 7|
| 8| from collections import defaultdict, deque
| 9| import heapq
| 10|
| 11|
| 12| class Node:
| 13| def __init__(self, frequency=0, sign=None, left=None, right=None):
| 14| self.frequency = frequency
| 15| self.sign = sign
| 16| self.left = left
| 17| self.right = right
| 18|
| 19| def __lt__(self, other):
| 20| return self.frequency < other.frequency
| 21|
| 22| def __gt__(self, other):
| 23| return self.frequency > other.frequency
| 24|
| 25| def __eq__(self, other):
| 26| return self.frequency == other.frequency
| 27|
| 28| def __str__(self):
| 29| return "<ch: {0}: {1}>".format(self.sign, self.frequency)
| 30|
| 31| def __repr__(self):
| 32| return "<ch: {0}: {1}>".format(self.sign, self.frequency)
| 33|
| 34|
| 35| class HuffmanReader:
| 36| def __init__(self, file):
| 37| self.file = file
| 38| self.buffer = []
| 39| self.is_last_byte = False
| 40|
| 41| def get_number_of_additional_bits_in_the_last_byte(self) -> int:
| 42| bin_num = self.get_bit() + self.get_bit() + self.get_bit()
| 43| return int(bin_num, 2)
| 44|
| 45| def load_tree(self) -> Node:
| 46| """
| 47| Load tree from file
| 48|
| 49| :return:
| 50| """
| 51| node_stack = deque()
| 52| queue_leaves = deque()
| 53| root = Node()
| 54|
| 55| current_node = root
| 56| is_end_of_tree = False
| 57| while not is_end_of_tree:
| 58| current_bit = self.get_bit()
| 59| if current_bit == "0":
| 60| current_node.left = Node()
| 61| current_node.right = Node()
| 62| node_stack.append(current_node.right) # going to left node, right push on stack
| 63| current_node = current_node.left
| 64| else:
| 65| queue_leaves.append(current_node)
| 66| if node_stack:
| 67| current_node = node_stack.pop()
| 68| else:
| 69| is_end_of_tree = True
| 70|
| 71| self._fill_tree(queue_leaves)
| 72|
| 73| return root
| 74|
| 75| def _fill_tree(self, leaves_queue):
| 76| """
| 77| Load values to tree after reading tree
| 78| :param leaves_queue:
| 79| :return:
| 80| """
| 81| leaves_queue.reverse()
| 82| while leaves_queue:
| 83| node = leaves_queue.pop()
| 84| s = int(self.get_byte(), 2)
| 85| node.sign = s
| 86|
| 87| def _load_byte(self, buff_limit=8) -> bool:
| 88| """
| 89| Load next byte is buffer is less than buff_limit
| 90| :param buff_limit:
| 91| :return: True if there is enough bits in buffer to read
| 92| """
| 93| if len(self.buffer) <= buff_limit:
| 94| byte = self.file.read(1)
| 95|
| 96| if not byte:
| 97| return False
| 98|
| 99| i = int.from_bytes(byte, "big")
|100| self.buffer.extend(list("{0:08b}".format(i)))
|101|
|102| return True
|103|
|104| def get_bit(self, buff_limit=8):
|105| if self._load_byte(buff_limit):
|106| bit = self.buffer.pop(0)
|107| return bit
|108| else:
|109| return -1
|110|
|111| def get_byte(self):
|112| if self._load_byte():
|113| byte_list = self.buffer[:8]
|114| self.buffer = self.buffer[8:]
|115|
|116| return "".join(byte_list)
|117| else:
|118| return -1
|119|
|120|
|121| class HuffmanWriter:
|122| def __init__(self, file):
|123| self.file = file
|124| self.buffer = ""
|125| self.saved_bits = 0
|126|
|127| def write_char(self, char):
|128| self.write_int(ord(char))
|129|
|130| def write_int(self, num):
|131| bin_int = "{0:08b}".format(num)
|132| self.write_bits(bin_int)
|133|
|134| def write_bits(self, bits):
|135| self.saved_bits += len(bits)
|136|
|137| self.buffer += bits
|138|
|139| while len(self.buffer) >= 8:
|140| i = int(self.buffer[:8], 2)
|141| self.file.write(bytes([i]))
|142| self.buffer = self.buffer[8:]
|143|
|144| def save_tree(self, tree):
|145| """
|146| Generate and save tree code to file
|147| :param tree:
|148| :return:
|149| """
|150| signs = []
|151| tree_code = ""
|152|
|153| def get_code_tree(T):
|154| nonlocal tree_code
|155| if T.sign is not None:
|156| signs.append(T.sign)
|157| if T.left:
|158| tree_code += "0"
|159| get_code_tree(T.left)
|160| if T.right:
|161| tree_code += "1"
|162| get_code_tree(T.right)
|163|
|164| get_code_tree(tree)
|165| self.write_bits(tree_code + "1") # "1" indicates that tree ended (it will be needed to load the tree)
|166| for int_sign in signs:
|167| self.write_int(int_sign)
|168|
|169| def _save_information_about_additional_bits(self, additional_bits: int):
|170| """
|171| Overwrite first three bits in the file
|172| :param additional_bits: number of bits that were appended to fill last byte
|173| :return:
|174| """
|175| self.file.seek(0)
|176| first_byte_raw = self.file.read(1)
|177| self.file.seek(0)
|178| first_byte = "{0:08b}".format(int.from_bytes(first_byte_raw, "big"))
|179| # overwrite first three bits
|180| first_byte = first_byte[3:]
|181| first_byte = "{0:03b}".format(additional_bits) + first_byte
|182|
|183| self.write_bits(first_byte)
|184|
|185| def close(self):
|186| additional_bits = 8 - len(self.buffer)
|187| if additional_bits != 8: # buffer is empty, no need to append extra "0"
|188| self.write_bits("0" * additional_bits)
|189| self._save_information_about_additional_bits(additional_bits)
|190|
|191|
|192| class TreeFinder:
|193| """
|194| Class to help find signs in tree
|195| """
|196|
|197| def __init__(self, tree):
|198| self.root = tree
|199| self.current_node = tree
|200| self.found = None
|201|
|202| def find(self, bit):
|203| """
|204| Find sign in tree
|205| :param bit:
|206| :return: True if sign is found
|207| """
|208| if bit == "0":
|209| self.current_node = self.current_node.left
|210| elif bit == "1":
|211| self.current_node = self.current_node.right
|212| else:
|213| self._reset()
|214| return True
|215|
|216| if self.current_node.sign is not None:
|217| self._reset(self.current_node.sign)
|218| return True
|219| else:
|220| return False
|221|
|222| def _reset(self, found=""):
|223| self.found = found
|224| self.current_node = self.root
|225|
|226|
|227| class HuffmanCoding:
|228| def __init__(self):
|229| pass
|230|
|231| @staticmethod
|232| def decode_file(file_in_name, file_out_name):
|233| with open(file_in_name, "rb") as file_in, open(file_out_name, "wb") as file_out:
|234| reader = HuffmanReader(file_in)
|235| additional_bits = reader.get_number_of_additional_bits_in_the_last_byte()
|236| tree = reader.load_tree()
|237|
|238| HuffmanCoding._decode_and_write_signs_to_file(file_out, reader, tree, additional_bits)
|239|
|240| print("File decoded.")
|241|
|242| @staticmethod
|243| def _decode_and_write_signs_to_file(file, reader: HuffmanReader, tree: Node, additional_bits: int):
|244| tree_finder = TreeFinder(tree)
|245| is_end_of_file = False
|246|
|247| while not is_end_of_file:
|248| bit = reader.get_bit()
|249| if bit != -1:
|250| while not tree_finder.find(bit): # read whole code
|251| bit = reader.get_bit(0)
|252| file.write(bytes([tree_finder.found]))
|253| else: # There is last byte in buffer to parse
|254| is_end_of_file = True
|255| last_byte = reader.buffer
|256| last_byte = last_byte[:-additional_bits] # remove additional "0" used to fill byte
|257| for bit in last_byte:
|258| if tree_finder.find(bit):
|259| file.write(bytes([tree_finder.found]))
|260|
|261| @staticmethod
|262| def encode_file(file_in_name, file_out_name):
|263| with open(file_in_name, "rb") as file_in, open(file_out_name, mode="wb+") as file_out:
|264| signs_frequency = HuffmanCoding._get_char_frequency(file_in)
|265| file_in.seek(0)
|266| tree = HuffmanCoding._create_tree(signs_frequency)
|267| codes = HuffmanCoding._generate_codes(tree)
|268|
|269| writer = HuffmanWriter(file_out)
|270| writer.write_bits("000") # leave space to save how many bits will be appended to fill the last byte
|271| writer.save_tree(tree)
|272| HuffmanCoding._encode_and_write_signs_to_file(file_in, writer, codes)
|273| writer.close()
|274|
|275| print("File encoded.")
|276|
|277| @staticmethod
|278| def _encode_and_write_signs_to_file(file, writer: HuffmanWriter, codes: dict):
|279| sign = file.read(1)
|280| while sign:
|281| int_char = int.from_bytes(sign, "big")
|282| writer.write_bits(codes[int_char])
|283| sign = file.read(1)
|284|
|285| @staticmethod
|286| def _get_char_frequency(file) -> dict:
|287| is_end_of_file = False
|288| signs_frequency = defaultdict(lambda: 0)
|289| while not is_end_of_file:
|290| prev_pos = file.tell()
|291| sign = file.read(1)
|292| curr_pos = file.tell()
|293| if prev_pos == curr_pos:
|294| is_end_of_file = True
|295| else:
|296| signs_frequency[int.from_bytes(sign, "big")] += 1
|297|
|298| return signs_frequency
|299|
|300| @staticmethod
|301| def _generate_codes(tree: Node) -> dict:
|302| codes = dict()
|303| HuffmanCoding._go_through_tree_and_create_codes(tree, "", codes)
|304| return codes
|305|
|306| @staticmethod
|307| def _create_tree(signs_frequency: dict) -> Node:
|308| nodes = [Node(frequency=frequency, sign=char_int) for char_int, frequency in signs_frequency.items()]
|309| heapq.heapify(nodes)
|310|
|311| while len(nodes) > 1:
|312| left = heapq.heappop(nodes)
|313| right = heapq.heappop(nodes)
|314| new_node = Node(frequency=left.frequency + right.frequency, left=left, right=right)
|315| heapq.heappush(nodes, new_node)
|316|
|317| return nodes[0] # root
|318|
|319| @staticmethod
|320| def _go_through_tree_and_create_codes(tree: Node, code: str, dict_codes: dict):
|321| if tree.sign is not None:
|322| dict_codes[tree.sign] = code
|323|
|324| if tree.left:
|325| HuffmanCoding._go_through_tree_and_create_codes(tree.left, code + "0", dict_codes)
|326|
|327| if tree.right:
|328| HuffmanCoding._go_through_tree_and_create_codes(tree.right, code + "1", dict_codes)
#========================================================================================================================
# :: maths/primes_sieve_of_eratosthenes.py ::
#========================================================================================================================
| 1| """
| 2| Return list of all primes less than n,
| 3| Using sieve of Eratosthenes.
| 4|
| 5| Modification:
| 6| We don't need to check all even numbers, we can make the sieve excluding even
| 7| numbers and adding 2 to the primes list by default.
| 8|
| 9| We are going to make an array of: x / 2 - 1 if number is even, else x / 2
| 10| (The -1 with even number it's to exclude the number itself)
| 11| Because we just need numbers [from 3..x if x is odd]
| 12|
| 13| # We can get value represented at index i with (i*2 + 3)
| 14|
| 15| For example, for x = 10, we start with an array of x / 2 - 1 = 4
| 16| [1, 1, 1, 1]
| 17| 3 5 7 9
| 18|
| 19| For x = 11:
| 20| [1, 1, 1, 1, 1]
| 21| 3 5 7 9 11 # 11 is odd, it's included in the list
| 22|
| 23| With this, we have reduced the array size to a half,
| 24| and complexity it's also a half now.
| 25| """
| 26|
| 27|
| 28| def get_primes(n):
| 29| """Return list of all primes less than n,
| 30| Using sieve of Eratosthenes.
| 31| """
| 32| if n <= 0:
| 33| raise ValueError("'n' must be a positive integer.")
| 34| # If x is even, exclude x from list (-1):
| 35| sieve_size = (n // 2 - 1) if n % 2 == 0 else (n // 2)
| 36| sieve = [True for _ in range(sieve_size)] # Sieve
| 37| primes = [] # List of Primes
| 38| if n >= 2:
| 39| primes.append(2) # 2 is prime by default
| 40| for i in range(sieve_size):
| 41| if sieve[i]:
| 42| value_at_i = i*2 + 3
| 43| primes.append(value_at_i)
| 44| for j in range(i, sieve_size, value_at_i):
| 45| sieve[j] = False
| 46| return primes
#========================================================================================================================
# :: maths/next_perfect_square.py ::
#========================================================================================================================
| 1| """
| 2| This program will look for the next perfect square.
| 3| Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise look for the next perfect square
| 4| for instance if you pass 121 then the script should return the next perfect square which is 144.
| 5| """
| 6|
| 7| def find_next_square(sq):
| 8| root = sq ** 0.5
| 9| if root.is_integer():
| 10| return (root + 1)**2
| 11| return -1
| 12|
| 13|
| 14| # Another way:
| 15|
| 16| def find_next_square2(sq):
| 17| x = sq**0.5
| 18| return -1 if x % 1 else (x+1)**2
| 19|
#========================================================================================================================
# :: maths/decimal_to_binary_ip.py ::
#========================================================================================================================
| 1| """
| 2| Given an ip address in dotted-decimal representation, determine the
| 3| binary representation. For example,
| 4| decimal_to_binary(255.0.0.5) returns 11111111.00000000.00000000.00000101
| 5| accepts string
| 6| returns string
| 7| """
| 8|
| 9| def decimal_to_binary_util(val):
| 10| bits = [128, 64, 32, 16, 8, 4, 2, 1]
| 11| val = int(val)
| 12| binary_rep = ''
| 13| for bit in bits:
| 14| if val >= bit:
| 15| binary_rep += str(1)
| 16| val -= bit
| 17| else:
| 18| binary_rep += str(0)
| 19|
| 20| return binary_rep
| 21|
| 22| def decimal_to_binary_ip(ip):
| 23| values = ip.split('.')
| 24| binary_list = []
| 25| for val in values:
| 26| binary_list.append(decimal_to_binary_util(val))
| 27| return '.'.join(binary_list)
#========================================================================================================================
# :: maths/next_bigger.py ::
#========================================================================================================================
| 1| """
| 2| I just bombed an interview and made pretty much zero
| 3| progress on my interview question.
| 4|
| 5| Given a number, find the next higher number which has the
| 6| exact same set of digits as the original number.
| 7| For example: given 38276 return 38627.
| 8| given 99999 return -1. (no such number exists)
| 9|
| 10| Condensed mathematical description:
| 11|
| 12| Find largest index i such that array[i − 1] < array[i].
| 13| (If no such i exists, then this is already the last permutation.)
| 14|
| 15| Find largest index j such that j ≥ i and array[j] > array[i − 1].
| 16|
| 17| Swap array[j] and array[i − 1].
| 18|
| 19| Reverse the suffix starting at array[i].
| 20|
| 21| """
| 22| import unittest
| 23|
| 24|
| 25| def next_bigger(num):
| 26|
| 27| digits = [int(i) for i in str(num)]
| 28| idx = len(digits) - 1
| 29|
| 30| while idx >= 1 and digits[idx-1] >= digits[idx]:
| 31| idx -= 1
| 32|
| 33| if idx == 0:
| 34| return -1 # no such number exists
| 35|
| 36| pivot = digits[idx-1]
| 37| swap_idx = len(digits) - 1
| 38|
| 39| while pivot >= digits[swap_idx]:
| 40| swap_idx -= 1
| 41|
| 42| digits[swap_idx], digits[idx-1] = digits[idx-1], digits[swap_idx]
| 43| digits[idx:] = digits[:idx-1:-1] # prefer slicing instead of reversed(digits[idx:])
| 44|
| 45| return int(''.join(str(x) for x in digits))
| 46|
| 47|
| 48| class TestSuite(unittest.TestCase):
| 49|
| 50| def test_next_bigger(self):
| 51|
| 52| self.assertEqual(next_bigger(38276), 38627)
| 53| self.assertEqual(next_bigger(12345), 12354)
| 54| self.assertEqual(next_bigger(1528452), 1528524)
| 55| self.assertEqual(next_bigger(138654), 143568)
| 56|
| 57| self.assertEqual(next_bigger(54321), -1)
| 58| self.assertEqual(next_bigger(999), -1)
| 59| self.assertEqual(next_bigger(5), -1)
| 60|
| 61|
| 62| if __name__ == '__main__':
| 63|
| 64| unittest.main()
#========================================================================================================================
# :: maths/is_strobogrammatic.py ::
#========================================================================================================================
| 1| """
| 2| A strobogrammatic number is a number that looks
| 3| the same when rotated 180 degrees (looked at upside down).
| 4|
| 5| Write a function to determine if a number is strobogrammatic.
| 6| The number is represented as a string.
| 7|
| 8| For example, the numbers "69", "88", and "818" are all strobogrammatic.
| 9| """
| 10|
| 11|
| 12| def is_strobogrammatic(num):
| 13| """
| 14| :type num: str
| 15| :rtype: bool
| 16| """
| 17| comb = "00 11 88 69 96"
| 18| i = 0
| 19| j = len(num) - 1
| 20| while i <= j:
| 21| x = comb.find(num[i]+num[j])
| 22| if x == -1:
| 23| return False
| 24| i += 1
| 25| j -= 1
| 26| return True
| 27|
| 28|
| 29| def is_strobogrammatic2(num: str):
| 30| """Another implementation."""
| 31| return num == num[::-1].replace('6', '#').replace('9', '6').replace('#', '9')
#========================================================================================================================
# :: maths/gcd.py ::
#========================================================================================================================
| 1| def gcd(a, b):
| 2| """Computes the greatest common divisor of integers a and b using
| 3| Euclid's Algorithm.
| 4| """
| 5| while b != 0:
| 6| a, b = b, a % b
| 7| return a
| 8|
| 9|
| 10| def lcm(a, b):
| 11| """Computes the lowest common multiple of integers a and b."""
| 12| return a * b / gcd(a, b)
#========================================================================================================================
# :: maths/rsa.py ::
#========================================================================================================================
| 1| """
| 2| RSA encryption algorithm
| 3| a method for encrypting a number that uses seperate encryption and decryption keys
| 4| this file only implements the key generation algorithm
| 5|
| 6| there are three important numbers in RSA called n, e, and d
| 7| e is called the encryption exponent
| 8| d is called the decryption exponent
| 9| n is called the modulus
| 10|
| 11| these three numbers satisfy
| 12| ((x ** e) ** d) % n == x % n
| 13|
| 14| to use this system for encryption, n and e are made publicly available, and d is kept secret
| 15| a number x can be encrypted by computing (x ** e) % n
| 16| the original number can then be recovered by computing (E ** d) % n, where E is
| 17| the encrypted number
| 18|
| 19| fortunately, python provides a three argument version of pow() that can compute powers modulo
| 20| a number very quickly:
| 21| (a ** b) % c == pow(a,b,c)
| 22| """
| 23|
| 24| import random
| 25|
| 26|
| 27| def generate_key(k, seed=None):
| 28| """
| 29| the RSA key generating algorithm
| 30| k is the number of bits in n
| 31| """
| 32|
| 33| def modinv(a, m):
| 34| """calculate the inverse of a mod m
| 35| that is, find b such that (a * b) % m == 1"""
| 36| b = 1
| 37| while not (a * b) % m == 1:
| 38| b += 1
| 39| return b
| 40|
| 41| def gen_prime(k, seed=None):
| 42| """generate a prime with k bits"""
| 43|
| 44| def is_prime(num):
| 45| if num == 2:
| 46| return True
| 47| for i in range(2, int(num ** 0.5) + 1):
| 48| if num % i == 0:
| 49| return False
| 50| return True
| 51|
| 52| random.seed(seed)
| 53| while True:
| 54| key = random.randrange(int(2 ** (k - 1)), int(2 ** k))
| 55| if is_prime(key):
| 56| return key
| 57|
| 58| # size in bits of p and q need to add up to the size of n
| 59| p_size = k / 2
| 60| q_size = k - p_size
| 61|
| 62| e = gen_prime(k, seed) # in many cases, e is also chosen to be a small constant
| 63|
| 64| while True:
| 65| p = gen_prime(p_size, seed)
| 66| if p % e != 1:
| 67| break
| 68|
| 69| while True:
| 70| q = gen_prime(q_size, seed)
| 71| if q % e != 1:
| 72| break
| 73|
| 74| n = p * q
| 75| l = (p - 1) * (q - 1) # calculate totient function
| 76| d = modinv(e, l)
| 77|
| 78| return int(n), int(e), int(d)
| 79|
| 80|
| 81| def encrypt(data, e, n):
| 82| return pow(int(data), int(e), int(n))
| 83|
| 84|
| 85| def decrypt(data, d, n):
| 86| return pow(int(data), int(d), int(n))
| 87|
| 88|
| 89|
| 90| # sample usage:
| 91| # n,e,d = generate_key(16)
| 92| # data = 20
| 93| # encrypted = pow(data,e,n)
| 94| # decrypted = pow(encrypted,d,n)
| 95| # assert decrypted == data
| 96|
#========================================================================================================================
# :: maths/rabin_miller.py ::
#========================================================================================================================
| 1| """
| 2| Rabin-Miller primality test
| 3| returning False implies that n is guaranteed composite
| 4| returning True means that n is probably prime
| 5| with a 4 ** -k chance of being wrong
| 6| """
| 7| import random
| 8|
| 9|
| 10| def is_prime(n, k):
| 11|
| 12| def pow2_factor(num):
| 13| """factor n into a power of 2 times an odd number"""
| 14| power = 0
| 15| while num % 2 == 0:
| 16| num /= 2
| 17| power += 1
| 18| return power, num
| 19|
| 20| def valid_witness(a):
| 21| """
| 22| returns true if a is a valid 'witness' for n
| 23| a valid witness increases chances of n being prime
| 24| an invalid witness guarantees n is composite
| 25| """
| 26| x = pow(int(a), int(d), int(n))
| 27|
| 28| if x == 1 or x == n - 1:
| 29| return False
| 30|
| 31| for _ in range(r - 1):
| 32| x = pow(int(x), int(2), int(n))
| 33|
| 34| if x == 1:
| 35| return True
| 36| if x == n - 1:
| 37| return False
| 38|
| 39| return True
| 40|
| 41| # precondition n >= 5
| 42| if n < 5:
| 43| return n == 2 or n == 3 # True for prime
| 44|
| 45| r, d = pow2_factor(n - 1)
| 46|
| 47| for _ in range(k):
| 48| if valid_witness(random.randrange(2, n - 2)):
| 49| return False
| 50|
| 51| return True
#========================================================================================================================
# :: maths/__init__.py ::
#========================================================================================================================
| 1| from .base_conversion import *
| 2| from .decimal_to_binary_ip import *
| 3| from .euler_totient import *
| 4| from .extended_gcd import *
| 5| from .factorial import *
| 6| from .gcd import *
| 7| from .generate_strobogrammtic import *
| 8| from .is_strobogrammatic import *
| 9| from .modular_exponential import *
| 10| from .next_perfect_square import *
| 11| from .prime_check import *
| 12| from .primes_sieve_of_eratosthenes import *
| 13| from .pythagoras import *
| 14| from .rabin_miller import *
| 15| from .rsa import *
| 16| from .combination import *
#========================================================================================================================
# :: maths/combination.py ::
#========================================================================================================================
| 1| def combination(n, r):
| 2| """This function calculates nCr."""
| 3| if n == r or r == 0:
| 4| return 1
| 5| else:
| 6| return combination(n-1, r-1) + combination(n-1, r)
| 7|
| 8| def combination_memo(n, r):
| 9| """This function calculates nCr using memoization method."""
| 10| memo = {}
| 11| def recur(n, r):
| 12| if n == r or r == 0:
| 13| return 1
| 14| if (n, r) not in memo:
| 15| memo[(n, r)] = recur(n - 1, r - 1) + recur(n - 1, r)
| 16| return memo[(n, r)]
| 17| return recur(n, r)
#========================================================================================================================
# :: maths/euler_totient.py ::
#========================================================================================================================
| 1| """
| 2| Euler's totient function, also known as phi-function ϕ(n),
| 3| counts the number of integers between 1 and n inclusive,
| 4| which are coprime to n.
| 5| (Two numbers are coprime if their greatest common divisor (GCD) equals 1).
| 6| """
| 7| def euler_totient(n):
| 8| """Euler's totient function or Phi function.
| 9| Time Complexity: O(sqrt(n))."""
| 10| result = n;
| 11| for i in range(2, int(n ** 0.5) + 1):
| 12| if n % i == 0:
| 13| while n % i == 0:
| 14| n //= i
| 15| result -= result // i
| 16| if n > 1:
| 17| result -= result // n;
| 18| return result;
#========================================================================================================================
# :: maths/base_conversion.py ::
#========================================================================================================================
| 1| """
| 2| Integer base conversion algorithm
| 3|
| 4| int2base(5, 2) return '101'.
| 5| base2int('F', 16) return 15.
| 6|
| 7| """
| 8|
| 9| import string
| 10|
| 11| def int_to_base(n, base):
| 12| """
| 13| :type n: int
| 14| :type base: int
| 15| :rtype: str
| 16| """
| 17| is_negative = False
| 18| if n == 0:
| 19| return '0'
| 20| elif n < 0:
| 21| is_negative = True
| 22| n *= -1
| 23| digit = string.digits + string.ascii_uppercase
| 24| res = ''
| 25| while n > 0:
| 26| res += digit[n % base]
| 27| n //= base
| 28| if is_negative:
| 29| return '-' + res[::-1]
| 30| else:
| 31| return res[::-1]
| 32|
| 33|
| 34| def base_to_int(s, base):
| 35| """
| 36| Note : You can use int() built-in function instread of this.
| 37| :type s: str
| 38| :type base: int
| 39| :rtype: int
| 40| """
| 41|
| 42| digit = {}
| 43| for i,c in enumerate(string.digits + string.ascii_uppercase):
| 44| digit[c] = i
| 45| multiplier = 1
| 46| res = 0
| 47| for c in s[::-1]:
| 48| res += digit[c] * multiplier
| 49| multiplier *= base
| 50| return res
#========================================================================================================================
# :: maths/factorial.py ::
#========================================================================================================================
| 1| def factorial(n, mod=None):
| 2| """Calculates factorial iteratively.
| 3| If mod is not None, then return (n! % mod)
| 4| Time Complexity - O(n)"""
| 5| if not (isinstance(n, int) and n >= 0):
| 6| raise ValueError("'n' must be a non-negative integer.")
| 7| if mod is not None and not (isinstance(mod, int) and mod > 0):
| 8| raise ValueError("'mod' must be a positive integer")
| 9| result = 1
| 10| if n == 0:
| 11| return 1
| 12| for i in range(2, n+1):
| 13| result *= i
| 14| if mod:
| 15| result %= mod
| 16| return result
| 17|
| 18|
| 19| def factorial_recur(n, mod=None):
| 20| """Calculates factorial recursively.
| 21| If mod is not None, then return (n! % mod)
| 22| Time Complexity - O(n)"""
| 23| if not (isinstance(n, int) and n >= 0):
| 24| raise ValueError("'n' must be a non-negative integer.")
| 25| if mod is not None and not (isinstance(mod, int) and mod > 0):
| 26| raise ValueError("'mod' must be a positive integer")
| 27| if n == 0:
| 28| return 1
| 29| result = n * factorial(n - 1, mod)
| 30| if mod:
| 31| result %= mod
| 32| return result
#========================================================================================================================
# :: maths/generate_strobogrammtic.py ::
#========================================================================================================================
| 1| """
| 2| A strobogrammatic number is a number that looks
| 3| the same when rotated 180 degrees (looked at upside down).
| 4|
| 5| Find all strobogrammatic numbers that are of length = n.
| 6|
| 7| For example,
| 8| Given n = 2, return ["11","69","88","96"].
| 9| """
| 10|
| 11|
| 12| def gen_strobogrammatic(n):
| 13| """
| 14| :type n: int
| 15| :rtype: List[str]
| 16| """
| 17| return helper(n, n)
| 18|
| 19|
| 20| def helper(n, length):
| 21| if n == 0:
| 22| return [""]
| 23| if n == 1:
| 24| return ["1", "0", "8"]
| 25| middles = helper(n-2, length)
| 26| result = []
| 27| for middle in middles:
| 28| if n != length:
| 29| result.append("0" + middle + "0")
| 30| result.append("8" + middle + "8")
| 31| result.append("1" + middle + "1")
| 32| result.append("9" + middle + "6")
| 33| result.append("6" + middle + "9")
| 34| return result
| 35|
| 36|
| 37| def strobogrammatic_in_range(low, high):
| 38| """
| 39| :type low: str
| 40| :type high: str
| 41| :rtype: int
| 42| """
| 43| res = []
| 44| count = 0
| 45| low_len = len(low)
| 46| high_len = len(high)
| 47| for i in range(low_len, high_len + 1):
| 48| res.extend(helper2(i, i))
| 49| for perm in res:
| 50| if len(perm) == low_len and int(perm) < int(low):
| 51| continue
| 52| elif len(perm) == high_len and int(perm) > int(high):
| 53| continue
| 54| else:
| 55| count += 1
| 56| return count
| 57|
| 58|
| 59| def helper2(n, length):
| 60| if n == 0:
| 61| return [""]
| 62| if n == 1:
| 63| return ["0", "8", "1"]
| 64| mids = helper(n-2, length)
| 65| res = []
| 66| for mid in mids:
| 67| if n != length:
| 68| res.append("0"+mid+"0")
| 69| res.append("1"+mid+"1")
| 70| res.append("6"+mid+"9")
| 71| res.append("9"+mid+"6")
| 72| res.append("8"+mid+"8")
| 73| return res
#========================================================================================================================
# :: maths/extended_gcd.py ::
#========================================================================================================================
| 1| def extended_gcd(a, b):
| 2| """Extended GCD algorithm.
| 3| Return s, t, g
| 4| such that a * s + b * t = GCD(a, b)
| 5| and s and t are co-prime.
| 6| """
| 7|
| 8| old_s, s = 1, 0
| 9| old_t, t = 0, 1
| 10| old_r, r = a, b
| 11|
| 12| while r != 0:
| 13| quotient = old_r / r
| 14|
| 15| old_r, r = r, old_r - quotient * r
| 16| old_s, s = s, old_s - quotient * s
| 17| old_t, t = t, old_t - quotient * t
| 18|
| 19| return old_s, old_t, old_r
#========================================================================================================================
# :: maths/pythagoras.py ::
#========================================================================================================================
| 1| """
| 2| input two of the three side in right angled triangle and return the third. use "?" to indicate the unknown side.
| 3| """
| 4|
| 5| def pythagoras(opposite,adjacent,hypotenuse):
| 6| try:
| 7| if opposite == str("?"):
| 8| return ("Opposite = " + str(((hypotenuse**2) - (adjacent**2))**0.5))
| 9| elif adjacent == str("?"):
| 10| return ("Adjacent = " + str(((hypotenuse**2) - (opposite**2))**0.5))
| 11| elif hypotenuse == str("?"):
| 12| return ("Hypotenuse = " + str(((opposite**2) + (adjacent**2))**0.5))
| 13| else:
| 14| return "You already know the answer!"
| 15| except:
| 16| raise ValueError("invalid argument were given.")
#========================================================================================================================
# :: maths/prime_check.py ::
#========================================================================================================================
| 1| def prime_check(n):
| 2| """Return True if n is a prime number
| 3| Else return False.
| 4| """
| 5|
| 6| if n <= 1:
| 7| return False
| 8| if n == 2 or n == 3:
| 9| return True
| 10| if n % 2 == 0 or n % 3 == 0:
| 11| return False
| 12| j = 5
| 13| while j * j <= n:
| 14| if n % j == 0 or n % (j + 2) == 0:
| 15| return False
| 16| j += 6
| 17| return True
#========================================================================================================================
# :: maths/hailstone.py ::
#========================================================================================================================
| 1| def hailstone(n):
| 2| """Return the 'hailstone sequence' from n to 1
| 3| n: The starting point of the hailstone sequence
| 4| """
| 5|
| 6| sequence = [n]
| 7| while n > 1:
| 8| if n%2 != 0:
| 9| n = 3*n + 1
| 10| else:
| 11| n = int(n/2)
| 12| sequence.append(n)
| 13| return sequence
#========================================================================================================================
# :: maths/nth_digit.py ::
#========================================================================================================================
| 1| def find_nth_digit(n):
| 2| """find the nth digit of given number.
| 3| 1. find the length of the number where the nth digit is from.
| 4| 2. find the actual number where the nth digit is from
| 5| 3. find the nth digit and return
| 6| """
| 7| length = 1
| 8| count = 9
| 9| start = 1
| 10| while n > length * count:
| 11| n -= length * count
| 12| length += 1
| 13| count *= 10
| 14| start *= 10
| 15| start += (n-1) / length
| 16| s = str(start)
| 17| return int(s[(n-1) % length])
#========================================================================================================================
# :: maths/modular_exponential.py ::
#========================================================================================================================
| 1| def modular_exponential(base, exponent, mod):
| 2| """Computes (base ^ exponent) % mod.
| 3| Time complexity - O(log n)
| 4| Use similar to Python in-built function pow."""
| 5| if exponent < 0:
| 6| raise ValueError("Exponent must be positive.")
| 7| base %= mod
| 8| result = 1
| 9|
| 10| while exponent > 0:
| 11| # If the last bit is 1, add 2^k.
| 12| if exponent & 1:
| 13| result = (result * base) % mod
| 14| exponent = exponent >> 1
| 15| # Utilize modular multiplication properties to combine the computed mod C values.
| 16| base = (base * base) % mod
| 17|
| 18| return result
#========================================================================================================================
# :: maths/sqrt_precision_factor.py ::
#========================================================================================================================
| 1| """
| 2| Given a positive integer N and a precision factor P,
| 3| it produces an output
| 4| with a maximum error P from the actual square root of N.
| 5|
| 6| Example:
| 7| Given N = 5 and P = 0.001, can produce output x such that
| 8| 2.235 < x < 2.237. Actual square root of 5 being 2.236.
| 9| """
| 10|
| 11|
| 12| def square_root(n, epsilon=0.001):
| 13| """Return square root of n, with maximum absolute error epsilon"""
| 14| guess = n / 2
| 15|
| 16| while abs(guess * guess - n) > epsilon:
| 17| guess = (guess + (n / guess)) / 2
| 18|
| 19| return guess
#========================================================================================================================
# :: maths/summing_digits.py ::
#========================================================================================================================
| 1| """
| 2| Recently, I encountered an interview question whose description was as below:
| 3|
| 4| The number 89 is the first integer with more than one digit whose digits when raised up to consecutive powers give the same
| 5| number. For example, 89 = 8**1 + 9**2 gives the number 89.
| 6|
| 7| The next number after 89 with this property is 135 = 1**1 + 3**2 + 5**3 = 135.
| 8|
| 9| Write a function that returns a list of numbers with the above property. The function will receive range as parameter.
| 10| """
| 11|
| 12| def sum_dig_pow(a, b):
| 13| result = []
| 14|
| 15| for number in range(a, b + 1):
| 16| exponent = 1 # set to 1
| 17| summation = 0 # set to 1
| 18| number_as_string = str(number)
| 19|
| 20| tokens = list(map(int, number_as_string)) # parse the string into individual digits
| 21|
| 22| for k in tokens:
| 23| summation = summation + (k ** exponent)
| 24| exponent += 1
| 25|
| 26| if summation == number:
| 27| result.append(number)
| 28| return result
| 29|
| 30|
| 31| # Some test cases:
| 32| assert sum_dig_pow(1, 10) == [1, 2, 3, 4, 5, 6, 7, 8, 9]
| 33| assert sum_dig_pow(1, 100) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 89]
#========================================================================================================================
# :: dp/rod_cut.py ::
#========================================================================================================================
| 1| # A Dynamic Programming solution for Rod cutting problem
| 2| INT_MIN = -32767
| 3|
| 4| # Returns the best obtainable price for a rod of length n and
| 5| # price[] as prices of different pieces
| 6| def cut_rod(price):
| 7| n = len(price)
| 8| val = [0]*(n+1)
| 9|
| 10| # Build the table val[] in bottom up manner and return
| 11| # the last entry from the table
| 12| for i in range(1, n+1):
| 13| max_val = INT_MIN
| 14| for j in range(i):
| 15| max_val = max(max_val, price[j] + val[i-j-1])
| 16| val[i] = max_val
| 17|
| 18| return val[n]
| 19|
| 20| # Driver program to test above functions
| 21| arr = [1, 5, 8, 9, 10, 17, 17, 20]
| 22| print("Maximum Obtainable Value is " + str(cut_rod(arr)))
| 23|
| 24| # This code is contributed by Bhavya Jain
#========================================================================================================================
# :: dp/house_robber.py ::
#========================================================================================================================
| 1| """
| 2| You are a professional robber planning to rob houses along a street.
| 3| Each house has a certain amount of money stashed,
| 4| the only constraint stopping you from robbing each of them
| 5| is that adjacent houses have security system connected and
| 6| it will automatically contact the police if two adjacent houses
| 7| were broken into on the same night.
| 8|
| 9| Given a list of non-negative integers representing the amount of money
| 10| of each house, determine the maximum amount of money you
| 11| can rob tonight without alerting the police.
| 12| """
| 13|
| 14|
| 15| def house_robber(houses):
| 16| last, now = 0, 0
| 17| for house in houses:
| 18| tmp = now
| 19| now = max(last + house, now)
| 20| last = tmp
| 21| return now
#========================================================================================================================
# :: dp/job_scheduling.py ::
#========================================================================================================================
| 1| # Python program for weighted job scheduling using Dynamic
| 2| # Programming and Binary Search
| 3|
| 4| # Class to represent a job
| 5| class Job:
| 6| def __init__(self, start, finish, profit):
| 7| self.start = start
| 8| self.finish = finish
| 9| self.profit = profit
| 10|
| 11|
| 12| # A Binary Search based function to find the latest job
| 13| # (before current job) that doesn't conflict with current
| 14| # job. "index" is index of the current job. This function
| 15| # returns -1 if all jobs before index conflict with it.
| 16| # The array jobs[] is sorted in increasing order of finish
| 17| # time.
| 18| def binary_search(job, start_index):
| 19|
| 20| # Initialize 'lo' and 'hi' for Binary Search
| 21| lo = 0
| 22| hi = start_index - 1
| 23|
| 24| # Perform binary Search iteratively
| 25| while lo <= hi:
| 26| mid = (lo + hi) // 2
| 27| if job[mid].finish <= job[start_index].start:
| 28| if job[mid + 1].finish <= job[start_index].start:
| 29| lo = mid + 1
| 30| else:
| 31| return mid
| 32| else:
| 33| hi = mid - 1
| 34| return -1
| 35|
| 36| # The main function that returns the maximum possible
| 37| # profit from given array of jobs
| 38| def schedule(job):
| 39|
| 40| # Sort jobs according to finish time
| 41| job = sorted(job, key = lambda j: j.finish)
| 42|
| 43| # Create an array to store solutions of subproblems. table[i]
| 44| # stores the profit for jobs till arr[i] (including arr[i])
| 45| n = len(job)
| 46| table = [0 for _ in range(n)]
| 47|
| 48| table[0] = job[0].profit
| 49|
| 50| # Fill entries in table[] using recursive property
| 51| for i in range(1, n):
| 52|
| 53| # Find profit including the current job
| 54| incl_prof = job[i].profit
| 55| l = binary_search(job, i)
| 56| if (l != -1):
| 57| incl_prof += table[l]
| 58|
| 59| # Store maximum of including and excluding
| 60| table[i] = max(incl_prof, table[i - 1])
| 61|
| 62| return table[n-1]
#========================================================================================================================
# :: dp/buy_sell_stock.py ::
#========================================================================================================================
| 1| """
| 2| Say you have an array for which the ith element
| 3| is the price of a given stock on day i.
| 4|
| 5| If you were only permitted to complete at most one transaction
| 6| (ie, buy one and sell one share of the stock),
| 7| design an algorithm to find the maximum profit.
| 8|
| 9| Example 1:
| 10| Input: [7, 1, 5, 3, 6, 4]
| 11| Output: 5
| 12|
| 13| max. difference = 6-1 = 5
| 14| (not 7-1 = 6, as selling price needs to be larger than buying price)
| 15| Example 2:
| 16| Input: [7, 6, 4, 3, 1]
| 17| Output: 0
| 18|
| 19| In this case, no transaction is done, i.e. max profit = 0.
| 20| """
| 21|
| 22|
| 23| # O(n^2) time
| 24| def max_profit_naive(prices):
| 25| """
| 26| :type prices: List[int]
| 27| :rtype: int
| 28| """
| 29| max_so_far = 0
| 30| for i in range(0, len(prices) - 1):
| 31| for j in range(i + 1, len(prices)):
| 32| max_so_far = max(max_so_far, prices[j] - prices[i])
| 33| return max_so_far
| 34|
| 35|
| 36| # O(n) time
| 37| def max_profit_optimized(prices):
| 38| """
| 39| input: [7, 1, 5, 3, 6, 4]
| 40| diff : [X, -6, 4, -2, 3, -2]
| 41| :type prices: List[int]
| 42| :rtype: int
| 43| """
| 44| cur_max, max_so_far = 0, 0
| 45| for i in range(1, len(prices)):
| 46| cur_max = max(0, cur_max + prices[i] - prices[i-1])
| 47| max_so_far = max(max_so_far, cur_max)
| 48| return max_so_far
#========================================================================================================================
# :: dp/regex_matching.py ::
#========================================================================================================================
| 1| """
| 2| Implement regular expression matching with support for '.' and '*'.
| 3|
| 4| '.' Matches any single character.
| 5| '*' Matches zero or more of the preceding element.
| 6|
| 7| The matching should cover the entire input string (not partial).
| 8|
| 9| The function prototype should be:
| 10| bool isMatch(const char *s, const char *p)
| 11|
| 12| Some examples:
| 13| isMatch("aa","a") → false
| 14| isMatch("aa","aa") → true
| 15| isMatch("aaa","aa") → false
| 16| isMatch("aa", "a*") → true
| 17| isMatch("aa", ".*") → true
| 18| isMatch("ab", ".*") → true
| 19| isMatch("aab", "c*a*b") → true
| 20| """
| 21| import unittest
| 22|
| 23| class Solution(object):
| 24| def is_match(self, s, p):
| 25| m, n = len(s) + 1, len(p) + 1
| 26| matches = [[False] * n for _ in range(m)]
| 27|
| 28| # Match empty string with empty pattern
| 29| matches[0][0] = True
| 30|
| 31| # Match empty string with .*
| 32| for i, element in enumerate(p[1:], 2):
| 33| matches[0][i] = matches[0][i - 2] and element == '*'
| 34|
| 35| for i, ss in enumerate(s, 1):
| 36| for j, pp in enumerate(p, 1):
| 37| if pp != '*':
| 38| # The previous character has matched and the current one
| 39| # has to be matched. Two possible matches: the same or .
| 40| matches[i][j] = matches[i - 1][j - 1] and \
| 41| (ss == pp or pp == '.')
| 42| else:
| 43| # Horizontal look up [j - 2].
| 44| # Not use the character before *.
| 45| matches[i][j] |= matches[i][j - 2]
| 46|
| 47| # Vertical look up [i - 1].
| 48| # Use at least one character before *.
| 49| # p a b *
| 50| # s 1 0 0 0
| 51| # a 0 1 0 1
| 52| # b 0 0 1 1
| 53| # b 0 0 0 ?
| 54| if ss == p[j - 2] or p[j - 2] == '.':
| 55| matches[i][j] |= matches[i - 1][j]
| 56|
| 57| return matches[-1][-1]
| 58|
| 59| class TestSolution(unittest.TestCase):
| 60| def test_none_0(self):
| 61| s = ""
| 62| p = ""
| 63| self.assertTrue(Solution().isMatch(s, p))
| 64|
| 65| def test_none_1(self):
| 66| s = ""
| 67| p = "a"
| 68| self.assertFalse(Solution().isMatch(s, p))
| 69|
| 70| def test_no_symbol_equal(self):
| 71| s = "abcd"
| 72| p = "abcd"
| 73| self.assertTrue(Solution().isMatch(s, p))
| 74|
| 75| def test_no_symbol_not_equal_0(self):
| 76| s = "abcd"
| 77| p = "efgh"
| 78| self.assertFalse(Solution().isMatch(s, p))
| 79|
| 80| def test_no_symbol_not_equal_1(self):
| 81| s = "ab"
| 82| p = "abb"
| 83| self.assertFalse(Solution().isMatch(s, p))
| 84|
| 85| def test_symbol_0(self):
| 86| s = ""
| 87| p = "a*"
| 88| self.assertTrue(Solution().isMatch(s, p))
| 89|
| 90| def test_symbol_1(self):
| 91| s = "a"
| 92| p = "ab*"
| 93| self.assertTrue(Solution().isMatch(s, p))
| 94|
| 95| def test_symbol_2(self):
| 96| # E.g.
| 97| # s a b b
| 98| # p 1 0 0 0
| 99| # a 0 1 0 0
|100| # b 0 0 1 0
|101| # * 0 1 1 1
|102| s = "abb"
|103| p = "ab*"
|104| self.assertTrue(Solution().isMatch(s, p))
|105|
|106|
|107| if __name__ == "__main__":
|108| unittest.main()
#========================================================================================================================
# :: dp/knapsack.py ::
#========================================================================================================================
| 1| """
| 2| Given the capacity of the knapsack and items specified by weights and values,
| 3| return the maximum summarized value of the items that can be fit in the
| 4| knapsack.
| 5|
| 6| Example:
| 7| capacity = 5, items(value, weight) = [(60, 5), (50, 3), (70, 4), (30, 2)]
| 8| result = 80 (items valued 50 and 30 can both be fit in the knapsack)
| 9|
| 10| The time complexity is O(n * m) and the space complexity is O(m), where n is
| 11| the total number of items and m is the knapsack's capacity.
| 12| """
| 13|
| 14|
| 15| class Item(object):
| 16|
| 17| def __init__(self, value, weight):
| 18| self.value = value
| 19| self.weight = weight
| 20|
| 21|
| 22| def get_maximum_value(items, capacity):
| 23| dp = [0] * (capacity + 1)
| 24| for item in items:
| 25| dp_tmp = [total_value for total_value in dp]
| 26| for current_weight in range(capacity + 1):
| 27| total_weight = current_weight + item.weight
| 28| if total_weight <= capacity:
| 29| dp_tmp[total_weight] = max(dp_tmp[total_weight],
| 30| dp[current_weight] + item.value)
| 31| dp = dp_tmp
| 32| return max(dp)
#========================================================================================================================
# :: dp/max_subarray.py ::
#========================================================================================================================
| 1|
| 2| def max_subarray(array):
| 3| max_so_far = max_now = array[0]
| 4| for i in range(1, len(array)):
| 5| max_now = max(array[i], max_now + array[i])
| 6| max_so_far = max(max_so_far, max_now)
| 7| return max_so_far
| 8|
| 9| a = [1, 2, -3, 4, 5, -7, 23]
| 10| print(a)
| 11| print(max_subarray(a))
#========================================================================================================================
# :: dp/__init__.py ::
#========================================================================================================================
| 1| from .buy_sell_stock import *
| 2| from .climbing_stairs import *
| 3| from .coin_change import *
| 4| from .combination_sum import *
| 5| from .edit_distance import *
| 6| from .egg_drop import *
| 7| from .fib import *
| 8| from .hosoya_triangle import *
| 9| from .house_robber import *
| 10| from .job_scheduling import *
| 11| from .knapsack import *
| 12| from .longest_increasing import *
| 13| from .matrix_chain_order import *
| 14| from .max_product_subarray import *
| 15| from .max_subarray import *
| 16| from .min_cost_path import *
| 17| from .num_decodings import *
| 18| from .regex_matching import *
| 19| from .rod_cut import *
| 20| from .word_break import *
#========================================================================================================================
# :: dp/matrix_chain_order.py ::
#========================================================================================================================
| 1| '''
| 2| Dynamic Programming
| 3| Implementation of matrix Chain Multiplication
| 4| Time Complexity: O(n^3)
| 5| Space Complexity: O(n^2)
| 6| '''
| 7| INF = float("inf")
| 8|
| 9| def matrix_chain_order(array):
| 10| n=len(array)
| 11| matrix = [[0 for x in range(n)] for x in range(n)]
| 12| sol = [[0 for x in range(n)] for x in range(n)]
| 13| for chain_length in range(2,n):
| 14| for a in range(1,n-chain_length+1):
| 15| b = a+chain_length-1
| 16|
| 17| matrix[a][b] = INF
| 18| for c in range(a, b):
| 19| cost = matrix[a][c] + matrix[c+1][b] + array[a-1]*array[c]*array[b]
| 20| if cost < matrix[a][b]:
| 21| matrix[a][b] = cost
| 22| sol[a][b] = c
| 23| return matrix , sol
| 24| #Print order of matrix with Ai as matrix
| 25|
| 26| def print_optimal_solution(optimal_solution,i,j):
| 27| if i==j:
| 28| print("A" + str(i),end = " ")
| 29| else:
| 30| print("(",end = " ")
| 31| print_optimal_solution(optimal_solution,i,optimal_solution[i][j])
| 32| print_optimal_solution(optimal_solution,optimal_solution[i][j]+1,j)
| 33| print(")",end = " ")
| 34|
| 35| def main():
| 36| array=[30,35,15,5,10,20,25]
| 37| n=len(array)
| 38| #Size of matrix created from above array will be
| 39| # 30*35 35*15 15*5 5*10 10*20 20*25
| 40| matrix , optimal_solution = matrix_chain_order(array)
| 41|
| 42| print("No. of Operation required: "+str((matrix[1][n-1])))
| 43| print_optimal_solution(optimal_solution,1,n-1)
| 44| if __name__ == '__main__':
| 45| main()
#========================================================================================================================
# :: dp/fib.py ::
#========================================================================================================================
| 1| def fib_recursive(n):
| 2| """[summary]
| 3| Computes the n-th fibonacci number recursive.
| 4| Problem: This implementation is very slow.
| 5| approximate O(2^n)
| 6|
| 7| Arguments:
| 8| n {[int]} -- [description]
| 9|
| 10| Returns:
| 11| [int] -- [description]
| 12| """
| 13|
| 14| # precondition
| 15| assert n >= 0, 'n must be a positive integer'
| 16|
| 17| if n <= 1:
| 18| return n
| 19| else:
| 20| return fib_recursive(n-1) + fib_recursive(n-2)
| 21|
| 22| # print(fib_recursive(35)) # => 9227465 (slow)
| 23|
| 24| def fib_list(n):
| 25| """[summary]
| 26| This algorithm computes the n-th fibbonacci number
| 27| very quick. approximate O(n)
| 28| The algorithm use dynamic programming.
| 29|
| 30| Arguments:
| 31| n {[int]} -- [description]
| 32|
| 33| Returns:
| 34| [int] -- [description]
| 35| """
| 36|
| 37| # precondition
| 38| assert n >= 0, 'n must be a positive integer'
| 39|
| 40| list_results = [0, 1]
| 41| for i in range(2, n+1):
| 42| list_results.append(list_results[i-1] + list_results[i-2])
| 43| return list_results[n]
| 44|
| 45| # print(fib_list(100)) # => 354224848179261915075
| 46|
| 47| def fib_iter(n):
| 48| """[summary]
| 49| Works iterative approximate O(n)
| 50|
| 51| Arguments:
| 52| n {[int]} -- [description]
| 53|
| 54| Returns:
| 55| [int] -- [description]
| 56| """
| 57|
| 58| # precondition
| 59| assert n >= 0, 'n must be positive integer'
| 60|
| 61| fib_1 = 0
| 62| fib_2 = 1
| 63| sum = 0
| 64| if n <= 1:
| 65| return n
| 66| for _ in range(n-1):
| 67| sum = fib_1 + fib_2
| 68| fib_1 = fib_2
| 69| fib_2 = sum
| 70| return sum
| 71|
| 72| # print(fib_iter(100)) # => 354224848179261915075
#========================================================================================================================
# :: dp/coin_change.py ::
#========================================================================================================================
| 1| """
| 2| Problem
| 3| Given a value N, if we want to make change for N cents, and we have infinite supply of each of
| 4| S = { S1, S2, .. , Sm} valued //coins, how many ways can we make the change?
| 5| The order of coins doesn't matter.
| 6| For example, for N = 4 and S = [1, 2, 3], there are four solutions:
| 7| [1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3].
| 8| So output should be 4.
| 9|
| 10| For N = 10 and S = [2, 5, 3, 6], there are five solutions:
| 11| [2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5].
| 12| So the output should be 5.
| 13| """
| 14|
| 15| def count(s, n):
| 16| # We need n+1 rows as the table is consturcted in bottom up
| 17| # manner using the base case 0 value case (n = 0)
| 18| m = len(s)
| 19| table = [[0 for x in range(m)] for x in range(n+1)]
| 20|
| 21| # Fill the enteries for 0 value case (n = 0)
| 22| for i in range(m):
| 23| table[0][i] = 1
| 24|
| 25| # Fill rest of the table enteries in bottom up manner
| 26| for i in range(1, n+1):
| 27| for j in range(m):
| 28| # Count of solutions including S[j]
| 29| x = table[i - s[j]][j] if i-s[j] >= 0 else 0
| 30|
| 31| # Count of solutions excluding S[j]
| 32| y = table[i][j-1] if j >= 1 else 0
| 33|
| 34| # total count
| 35| table[i][j] = x + y
| 36|
| 37| return table[n][m-1]
#========================================================================================================================
# :: dp/longest_increasing.py ::
#========================================================================================================================
| 1| """
| 2| Given an unsorted array of integers, find the length of longest increasing subsequence.
| 3|
| 4| Example:
| 5|
| 6| Input: [10,9,2,5,3,7,101,18]
| 7| Output: 4
| 8| Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
| 9|
| 10| The time complexity is O(n^2).
| 11| """
| 12|
| 13| def longest_increasing_subsequence(sequence):
| 14| """
| 15| Dynamic Programming Algorithm for
| 16| counting the length of longest increasing subsequence
| 17| type sequence: List[int]
| 18| """
| 19| length = len(sequence)
| 20| counts = [1 for _ in range(length)]
| 21| for i in range(1, length):
| 22| for j in range(0, i):
| 23| if sequence[i] > sequence[j]:
| 24| counts[i] = max(counts[i], counts[j] + 1)
| 25| print(counts)
| 26| return max(counts)
#========================================================================================================================
# :: dp/num_decodings.py ::
#========================================================================================================================
| 1| """
| 2| A message containing letters from A-Z is being
| 3| encoded to numbers using the following mapping:
| 4|
| 5| 'A' -> 1
| 6| 'B' -> 2
| 7| ...
| 8| 'Z' -> 26
| 9| Given an encoded message containing digits,
| 10| determine the total number of ways to decode it.
| 11|
| 12| For example,
| 13| Given encoded message "12",
| 14| it could be decoded as "AB" (1 2) or "L" (12).
| 15|
| 16| The number of ways decoding "12" is 2.
| 17| """
| 18|
| 19|
| 20| def num_decodings(s):
| 21| """
| 22| :type s: str
| 23| :rtype: int
| 24| """
| 25| if not s or s[0] == "0":
| 26| return 0
| 27| wo_last, wo_last_two = 1, 1
| 28| for i in range(1, len(s)):
| 29| x = wo_last if s[i] != "0" else 0
| 30| y = wo_last_two if int(s[i-1:i+1]) < 27 and s[i-1] != "0" else 0
| 31| wo_last_two = wo_last
| 32| wo_last = x+y
| 33| return wo_last
| 34|
| 35|
| 36| def num_decodings2(s):
| 37| if not s or s.startswith('0'):
| 38| return 0
| 39| stack = [1, 1]
| 40| for i in range(1, len(s)):
| 41| if s[i] == '0':
| 42| if s[i-1] == '0' or s[i-1] > '2':
| 43| # only '10', '20' is valid
| 44| return 0
| 45| stack.append(stack[-2])
| 46| elif 9 < int(s[i-1:i+1]) < 27:
| 47| # '01 - 09' is not allowed
| 48| stack.append(stack[-2]+stack[-1])
| 49| else:
| 50| # other case '01, 09, 27'
| 51| stack.append(stack[-1])
| 52| return stack[-1]
#========================================================================================================================
# :: dp/edit_distance.py ::
#========================================================================================================================
| 1| """The edit distance between two words is the minimum number of letter insertions,
| 2| letter deletions, and letter substitutions required to transform one word into another.
| 3|
| 4| For example, the edit distance between FOOD and MONEY is at most four:
| 5|
| 6| FOOD -> MOOD -> MOND -> MONED -> MONEY
| 7|
| 8| Given two words A and B, find the minimum number of operations required to transform one string into the other.
| 9| In other words, find the edit distance between A and B.
| 10|
| 11| Thought process:
| 12|
| 13| Let edit(i, j) denote the edit distance between the prefixes A[1..i] and B[1..j].
| 14|
| 15| Then, the function satisfies the following recurrence:
| 16|
| 17| edit(i, j) = i if j = 0
| 18| j if i = 0
| 19| min(edit(i-1, j) + 1,
| 20| edit(i, j-1), + 1,
| 21| edit(i-1, j-1) + cost) otherwise
| 22|
| 23| There are two base cases, both of which occur when one string is empty and the other is not.
| 24| 1. To convert an empty string A into a string B of length n, perform n insertions.
| 25| 2. To convert a string A of length m into an empty string B, perform m deletions.
| 26|
| 27| Here, the cost is 1 if a substitution is required,
| 28| or 0 if both chars in words A and B are the same at indexes i and j, respectively.
| 29|
| 30| To find the edit distance between two words A and B,
| 31| we need to find edit(m, n), where m is the length of A and n is the length of B.
| 32| """
| 33|
| 34|
| 35| def edit_distance(A, B):
| 36| # Time: O(m*n)
| 37| # Space: O(m*n)
| 38|
| 39| m, n = len(A) + 1, len(B) + 1
| 40|
| 41| edit = [[0 for _ in range(n)] for _ in range(m)]
| 42|
| 43|
| 44| for i in range(1, m):
| 45| for j in range(1, n):
| 46| if j == 0: edit[i][j] = i
| 47| if i == 0: edit[i][j] = j
| 48| cost = 0 if A[i - 1] == B[j - 1] else 1
| 49| edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + cost)
| 50|
| 51| print(list(row for row in edit))
| 52| return edit[-1][-1] # this is the same as edit[m][n]
| 53|
| 54|
| 55| assert edit_distance('ab', 'ab') == 0
| 56| assert edit_distance('ab', 'abc') == 1
| 57| assert edit_distance('azr', 'axy') == 2
#========================================================================================================================
# :: dp/max_product_subarray.py ::
#========================================================================================================================
| 1| """
| 2| Find the contiguous subarray within an array
| 3| (containing at least one number) which has the largest product.
| 4|
| 5| For example, given the array [2,3,-2,4],
| 6| the contiguous subarray [2,3] has the largest product = 6.
| 7| """
| 8| from functools import reduce
| 9|
| 10|
| 11| def max_product(nums):
| 12| """
| 13| :type nums: List[int]
| 14| :rtype: int
| 15| """
| 16| lmin = lmax = gmax = nums[0]
| 17| for i in range(len(nums)):
| 18| t1 = nums[i] * lmax
| 19| t2 = nums[i] * lmin
| 20| lmax = max(max(t1, t2), nums[i])
| 21| lmin = min(min(t1, t2), nums[i])
| 22| gmax = max(gmax, lmax)
| 23|
| 24|
| 25| '''
| 26| Another approach that would print max product and the subarray
| 27|
| 28| Examples:
| 29| subarray_with_max_product([2,3,6,-1,-1,9,5])
| 30| #=> max_product_so_far: 45, [-1, -1, 9, 5]
| 31| subarray_with_max_product([-2,-3,6,0,-7,-5])
| 32| #=> max_product_so_far: 36, [-2, -3, 6]
| 33| subarray_with_max_product([-4,-3,-2,-1])
| 34| #=> max_product_so_far: 24, [-4, -3, -2, -1]
| 35| subarray_with_max_product([-3,0,1])
| 36| #=> max_product_so_far: 1, [1]
| 37| '''
| 38|
| 39|
| 40| def subarray_with_max_product(arr):
| 41| ''' arr is list of positive/negative numbers '''
| 42| l = len(arr)
| 43| product_so_far = max_product_end = 1
| 44| max_start_i = 0
| 45| so_far_start_i = so_far_end_i = 0
| 46| all_negative_flag = True
| 47|
| 48| for i in range(l):
| 49| max_product_end *= arr[i]
| 50| if arr[i] > 0:
| 51| all_negative_flag = False
| 52|
| 53| if max_product_end <= 0:
| 54| max_product_end = arr[i]
| 55| max_start_i = i
| 56|
| 57| if product_so_far <= max_product_end:
| 58| product_so_far = max_product_end
| 59| so_far_end_i = i
| 60| so_far_start_i = max_start_i
| 61|
| 62| if all_negative_flag:
| 63| print("max_product_so_far: %s, %s" %
| 64| (reduce(lambda x, y: x * y, arr), arr))
| 65| else:
| 66| print("max_product_so_far: %s, %s" %
| 67| (product_so_far, arr[so_far_start_i:so_far_end_i + 1]))
#========================================================================================================================
# :: dp/climbing_stairs.py ::
#========================================================================================================================
| 1| """
| 2| You are climbing a stair case.
| 3| It takes n steps to reach to the top.
| 4|
| 5| Each time you can either climb 1 or 2 steps.
| 6| In how many distinct ways can you climb to the top?
| 7|
| 8| Note: Given n will be a positive integer.
| 9| """
| 10|
| 11|
| 12| # O(n) space
| 13|
| 14| def climb_stairs(n):
| 15| """
| 16| :type n: int
| 17| :rtype: int
| 18| """
| 19| arr = [1, 1]
| 20| for _ in range(1, n):
| 21| arr.append(arr[-1] + arr[-2])
| 22| return arr[-1]
| 23|
| 24|
| 25| # the above function can be optimized as:
| 26| # O(1) space
| 27|
| 28| def climb_stairs_optimized(n):
| 29| a = b = 1
| 30| for _ in range(n):
| 31| a, b = b, a + b
| 32| return a
#========================================================================================================================
# :: dp/word_break.py ::
#========================================================================================================================
| 1| """
| 2| Given a non-empty string s and a dictionary wordDict
| 3| containing a list of non-empty words,
| 4| determine if s can be segmented into a space-separated
| 5| sequence of one or more dictionary words.
| 6| You may assume the dictionary does not contain duplicate words.
| 7|
| 8| For example, given
| 9| s = "leetcode",
| 10| dict = ["leet", "code"].
| 11|
| 12| Return true because "leetcode" can be segmented as "leet code".
| 13| """
| 14|
| 15|
| 16| """
| 17| s = abc word_dict = ["a","bc"]
| 18| True False False False
| 19|
| 20| """
| 21|
| 22|
| 23| # TC: O(N^2) SC: O(N)
| 24| def word_break(s, word_dict):
| 25| """
| 26| :type s: str
| 27| :type word_dict: Set[str]
| 28| :rtype: bool
| 29| """
| 30| dp = [False] * (len(s)+1)
| 31| dp[0] = True
| 32| for i in range(1, len(s)+1):
| 33| for j in range(0, i):
| 34| if dp[j] and s[j:i] in word_dict:
| 35| dp[i] = True
| 36| break
| 37| return dp[-1]
| 38|
| 39|
| 40| if __name__ == "__main__":
| 41| s = "keonkim"
| 42| dic = ["keon", "kim"]
| 43|
| 44| print(word_break(s, dic))
#========================================================================================================================
# :: dp/hosoya_triangle.py ::
#========================================================================================================================
| 1| """
| 2| Hosoya triangle (originally Fibonacci triangle) is a triangular arrangement
| 3| of numbers, where if you take any number it is the sum of 2 numbers above.
| 4| First line is always 1, and second line is always {1 1}.
| 5|
| 6| This printHosoya function takes argument n which is the height of the triangle
| 7| (number of lines).
| 8|
| 9| For example:
| 10| printHosoya( 6 ) would return:
| 11| 1
| 12| 1 1
| 13| 2 1 2
| 14| 3 2 2 3
| 15| 5 3 4 3 5
| 16| 8 5 6 6 5 8
| 17|
| 18| The complexity is O(n^3).
| 19|
| 20| """
| 21|
| 22|
| 23| def hosoya(n, m):
| 24| if ((n == 0 and m == 0) or (n == 1 and m == 0) or
| 25| (n == 1 and m == 1) or (n == 2 and m == 1)):
| 26| return 1
| 27| if n > m:
| 28| return hosoya(n - 1, m) + hosoya(n - 2, m)
| 29| elif m == n:
| 30| return hosoya(n - 1, m - 1) + hosoya(n - 2, m - 2)
| 31| else:
| 32| return 0
| 33|
| 34| def print_hosoya(n):
| 35| for i in range(n):
| 36| for j in range(i + 1):
| 37| print(hosoya(i, j) , end = " ")
| 38| print ("\n", end = "")
| 39|
| 40| def hosoya_testing(n):
| 41| x = []
| 42| for i in range(n):
| 43| for j in range(i + 1):
| 44| x.append(hosoya(i, j))
| 45| return x
#========================================================================================================================
# :: dp/min_cost_path.py ::
#========================================================================================================================
| 1| """
| 2| author @goswami-rahul
| 3|
| 4| To find minimum cost path
| 5| from station 0 to station N-1,
| 6| where cost of moving from ith station to jth station is given as:
| 7|
| 8| Matrix of size (N x N)
| 9| where Matrix[i][j] denotes the cost of moving from
| 10| station i --> station j for i < j
| 11|
| 12| NOTE that values where Matrix[i][j] and i > j does not
| 13| mean anything, and hence represented by -1 or INF
| 14|
| 15| For the input below (cost matrix),
| 16| Minimum cost is obtained as from { 0 --> 1 --> 3}
| 17| = cost[0][1] + cost[1][3] = 65
| 18| the Output will be:
| 19|
| 20| The Minimum cost to reach station 4 is 65
| 21|
| 22| Time Complexity: O(n^2)
| 23| Space Complexity: O(n)
| 24| """
| 25|
| 26| INF = float("inf")
| 27|
| 28| def min_cost(cost):
| 29|
| 30| n = len(cost)
| 31| # dist[i] stores minimum cost from 0 --> i.
| 32| dist = [INF] * n
| 33|
| 34| dist[0] = 0 # cost from 0 --> 0 is zero.
| 35|
| 36| for i in range(n):
| 37| for j in range(i+1,n):
| 38| dist[j] = min(dist[j], dist[i] + cost[i][j])
| 39|
| 40| return dist[n-1]
| 41|
| 42| if __name__ == '__main__':
| 43|
| 44| cost = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of
| 45| [-1, 0, 40, 50], # going from i --> j
| 46| [-1, -1, 0, 70],
| 47| [-1, -1, -1, 0] ] # cost[i][j] = -1 for i > j
| 48| total_len = len(cost)
| 49|
| 50| mcost = min_cost(cost)
| 51| assert mcost == 65
| 52|
| 53| print("The Minimum cost to reach station %d is %d" % (total_len, mcost))
#========================================================================================================================
# :: dp/combination_sum.py ::
#========================================================================================================================
| 1| """
| 2| Given an integer array with all positive numbers and no duplicates,
| 3| find the number of possible combinations that
| 4| add up to a positive integer target.
| 5|
| 6| Example:
| 7|
| 8| nums = [1, 2, 3]
| 9| target = 4
| 10|
| 11| The possible combination ways are:
| 12| (1, 1, 1, 1)
| 13| (1, 1, 2)
| 14| (1, 2, 1)
| 15| (1, 3)
| 16| (2, 1, 1)
| 17| (2, 2)
| 18| (3, 1)
| 19|
| 20| Note that different sequences are counted as different combinations.
| 21|
| 22| Therefore the output is 7.
| 23| Follow up:
| 24| What if negative numbers are allowed in the given array?
| 25| How does it change the problem?
| 26| What limitation we need to add to the question to allow negative numbers?
| 27|
| 28| """
| 29|
| 30| dp = None
| 31|
| 32|
| 33| def helper_topdown(nums, target):
| 34| global dp
| 35| if dp[target] != -1:
| 36| return dp[target]
| 37| res = 0
| 38| for i in range(0, len(nums)):
| 39| if target >= nums[i]:
| 40| res += helper_topdown(nums, target - nums[i])
| 41| dp[target] = res
| 42| return res
| 43|
| 44|
| 45| def combination_sum_topdown(nums, target):
| 46| global dp
| 47| dp = [-1] * (target + 1)
| 48| dp[0] = 1
| 49| return helper_topdown(nums, target)
| 50|
| 51|
| 52| # EDIT: The above solution is top-down. How about a bottom-up one?
| 53| def combination_sum_bottom_up(nums, target):
| 54| comb = [0] * (target + 1)
| 55| comb[0] = 1
| 56| for i in range(0, len(comb)):
| 57| for j in range(len(nums)):
| 58| if i - nums[j] >= 0:
| 59| comb[i] += comb[i - nums[j]]
| 60| return comb[target]
#========================================================================================================================
# :: dp/egg_drop.py ::
#========================================================================================================================
| 1| """
| 2| You are given K eggs, and you have access to a building with N floors
| 3| from 1 to N. Each egg is identical in function, and if an egg breaks,
| 4| you cannot drop it again. You know that there exists a floor F with
| 5| 0 <= F <= N such that any egg dropped at a floor higher than F will
| 6| break, and any egg dropped at or below floor F will not break.
| 7| Each move, you may take an egg (if you have an unbroken one) and drop
| 8| it from any floor X (with 1 <= X <= N). Your goal is to know with
| 9| certainty what the value of F is. What is the minimum number of moves
| 10| that you need to know with certainty what F is, regardless of the
| 11| initial value of F?
| 12|
| 13| Example:
| 14| Input: K = 1, N = 2
| 15| Output: 2
| 16| Explanation:
| 17| Drop the egg from floor 1. If it breaks, we know with certainty that F = 0.
| 18| Otherwise, drop the egg from floor 2. If it breaks, we know with
| 19| certainty that F = 1.
| 20| If it didn't break, then we know with certainty F = 2.
| 21| Hence, we needed 2 moves in the worst case to know what F is with certainty.
| 22| """
| 23|
| 24| # A Dynamic Programming based Python Program for the Egg Dropping Puzzle
| 25| INT_MAX = 32767
| 26|
| 27| def egg_drop(n, k):
| 28| # A 2D table where entery eggFloor[i][j] will represent minimum
| 29| # number of trials needed for i eggs and j floors.
| 30| egg_floor = [[0 for x in range(k+1)] for x in range(n+1)]
| 31|
| 32| # We need one trial for one floor and 0 trials for 0 floors
| 33| for i in range(1, n+1):
| 34| egg_floor[i][1] = 1
| 35| egg_floor[i][0] = 0
| 36|
| 37| # We always need j trials for one egg and j floors.
| 38| for j in range(1, k+1):
| 39| egg_floor[1][j] = j
| 40|
| 41| # Fill rest of the entries in table using optimal substructure
| 42| # property
| 43| for i in range(2, n+1):
| 44| for j in range(2, k+1):
| 45| egg_floor[i][j] = INT_MAX
| 46| for x in range(1, j+1):
| 47| res = 1 + max(egg_floor[i-1][x-1], egg_floor[i][j-x])
| 48| if res < egg_floor[i][j]:
| 49| egg_floor[i][j] = res
| 50|
| 51| # eggFloor[n][k] holds the result
| 52| return egg_floor[n][k]
#========================================================================================================================
# :: queues/queue.py ::
#========================================================================================================================
| 1| """
| 2| Queue Abstract Data Type (ADT)
| 3| * Queue() creates a new queue that is empty.
| 4| It needs no parameters and returns an empty queue.
| 5| * enqueue(item) adds a new item to the rear of the queue.
| 6| It needs the item and returns nothing.
| 7| * dequeue() removes the front item from the queue.
| 8| It needs no parameters and returns the item. The queue is modified.
| 9| * isEmpty() tests to see whether the queue is empty.
| 10| It needs no parameters and returns a boolean value.
| 11| * size() returns the number of items in the queue.
| 12| It needs no parameters and returns an integer.
| 13| * peek() returns the front element of the queue.
| 14| """
| 15| from abc import ABCMeta, abstractmethod
| 16| class AbstractQueue(metaclass=ABCMeta):
| 17|
| 18| def __init__(self):
| 19| self._size = 0
| 20|
| 21| def __len__(self):
| 22| return self._size
| 23|
| 24| def is_empty(self):
| 25| return self._size == 0
| 26|
| 27| @abstractmethod
| 28| def enqueue(self, value):
| 29| pass
| 30|
| 31| @abstractmethod
| 32| def dequeue(self):
| 33| pass
| 34|
| 35| @abstractmethod
| 36| def peek(self):
| 37| pass
| 38|
| 39| @abstractmethod
| 40| def __iter__(self):
| 41| pass
| 42|
| 43|
| 44| class ArrayQueue(AbstractQueue):
| 45|
| 46| def __init__(self, capacity=10):
| 47| """
| 48| Initialize python List with capacity of 10 or user given input.
| 49| Python List type is a dynamic array, so we have to restrict its
| 50| dynamic nature to make it work like a static array.
| 51| """
| 52| super().__init__()
| 53| self._array = [None] * capacity
| 54| self._front = 0
| 55| self._rear = 0
| 56|
| 57| def __iter__(self):
| 58| probe = self._front
| 59| while True:
| 60| if probe == self._rear:
| 61| return
| 62| yield self._array[probe]
| 63| probe += 1
| 64|
| 65| def enqueue(self, value):
| 66| if self._rear == len(self._array):
| 67| self._expand()
| 68| self._array[self._rear] = value
| 69| self._rear += 1
| 70| self._size += 1
| 71|
| 72| def dequeue(self):
| 73| if self.is_empty():
| 74| raise IndexError("Queue is empty")
| 75| value = self._array[self._front]
| 76| self._array[self._front] = None
| 77| self._front += 1
| 78| self._size -= 1
| 79| return value
| 80|
| 81| def peek(self):
| 82| """returns the front element of queue."""
| 83| if self.is_empty():
| 84| raise IndexError("Queue is empty")
| 85| return self._array[self._front]
| 86|
| 87| def _expand(self):
| 88| """expands size of the array.
| 89| Time Complexity: O(n)
| 90| """
| 91| self._array += [None] * len(self._array)
| 92|
| 93|
| 94| class QueueNode:
| 95| def __init__(self, value):
| 96| self.value = value
| 97| self.next = None
| 98|
| 99| class LinkedListQueue(AbstractQueue):
|100|
|101| def __init__(self):
|102| super().__init__()
|103| self._front = None
|104| self._rear = None
|105|
|106| def __iter__(self):
|107| probe = self._front
|108| while True:
|109| if probe is None:
|110| return
|111| yield probe.value
|112| probe = probe.next
|113|
|114| def enqueue(self, value):
|115| node = QueueNode(value)
|116| if self._front is None:
|117| self._front = node
|118| self._rear = node
|119| else:
|120| self._rear.next = node
|121| self._rear = node
|122| self._size += 1
|123|
|124| def dequeue(self):
|125| if self.is_empty():
|126| raise IndexError("Queue is empty")
|127| value = self._front.value
|128| if self._front is self._rear:
|129| self._front = None
|130| self._rear = None
|131| else:
|132| self._front = self._front.next
|133| self._size -= 1
|134| return value
|135|
|136| def peek(self):
|137| """returns the front element of queue."""
|138| if self.is_empty():
|139| raise IndexError("Queue is empty")
|140| return self._front.value
#========================================================================================================================
# :: queues/reconstruct_queue.py ::
#========================================================================================================================
| 1| # Suppose you have a random list of people standing in a queue.
| 2| # Each person is described by a pair of integers (h, k),
| 3| # where h is the height of the person and k is the number of people
| 4| # in front of this person who have a height greater than or equal to h.
| 5| # Write an algorithm to reconstruct the queue.
| 6|
| 7| # Note:
| 8| # The number of people is less than 1,100.
| 9|
| 10| # Example
| 11|
| 12| # Input:
| 13| # [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
| 14|
| 15| # Output:
| 16| # [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
| 17|
| 18| def reconstruct_queue(people):
| 19| """
| 20| :type people: List[List[int]]
| 21| :rtype: List[List[int]]
| 22| """
| 23| queue = []
| 24| people.sort(key=lambda x: (-x[0], x[1]))
| 25| for h, k in people:
| 26| queue.insert(k, [h, k])
| 27| return queue
#========================================================================================================================
# :: queues/max_sliding_window.py ::
#========================================================================================================================
| 1| """
| 2| Given an array and a number k
| 3| Find the max elements of each of its sub-arrays of length k.
| 4|
| 5| Keep indexes of good candidates in deque d.
| 6| The indexes in d are from the current window, they're increasing,
| 7| and their corresponding nums are decreasing.
| 8| Then the first deque element is the index of the largest window value.
| 9|
| 10| For each index i:
| 11|
| 12| 1. Pop (from the end) indexes of smaller elements (they'll be useless).
| 13| 2. Append the current index.
| 14| 3. Pop (from the front) the index i - k, if it's still in the deque
| 15| (it falls out of the window).
| 16| 4. If our window has reached size k,
| 17| append the current window maximum to the output.
| 18| """
| 19|
| 20| import collections
| 21| def max_sliding_window(arr, k):
| 22| qi = collections.deque() # queue storing indexes of elements
| 23| result = []
| 24| for i, n in enumerate(arr):
| 25| while qi and arr[qi[-1]] < n:
| 26| qi.pop()
| 27| qi.append(i)
| 28| if qi[0] == i - k:
| 29| qi.popleft()
| 30| if i >= k - 1:
| 31| result.append(arr[qi[0]])
| 32| return result
#========================================================================================================================
# :: queues/moving_average.py ::
#========================================================================================================================
| 1| from __future__ import division
| 2| from collections import deque
| 3|
| 4|
| 5| class MovingAverage(object):
| 6| def __init__(self, size):
| 7| """
| 8| Initialize your data structure here.
| 9| :type size: int
| 10| """
| 11| self.queue = deque(maxlen=size)
| 12|
| 13| def next(self, val):
| 14| """
| 15| :type val: int
| 16| :rtype: float
| 17| """
| 18| self.queue.append(val)
| 19| return sum(self.queue) / len(self.queue)
| 20|
| 21|
| 22| # Given a stream of integers and a window size,
| 23| # calculate the moving average of all integers in the sliding window.
| 24| if __name__ == '__main__':
| 25| m = MovingAverage(3)
| 26| assert m.next(1) == 1
| 27| assert m.next(10) == (1 + 10) / 2
| 28| assert m.next(3) == (1 + 10 + 3) / 3
| 29| assert m.next(5) == (10 + 3 + 5) / 3
#========================================================================================================================
# :: queues/__init__.py ::
#========================================================================================================================
| 1| from .queue import *
| 2| from .max_sliding_window import *
| 3| from .reconstruct_queue import *
| 4| from .priority_queue import *
#========================================================================================================================
# :: queues/zigzagiterator.py ::
#========================================================================================================================
| 1| class ZigZagIterator:
| 2| def __init__(self, v1, v2):
| 3| """
| 4| Initialize your data structure here.
| 5| :type v1: List[int]
| 6| :type v2: List[int]
| 7| """
| 8| self.queue=[_ for _ in (v1,v2) if _]
| 9| print(self.queue)
| 10|
| 11| def next(self):
| 12| """
| 13| :rtype: int
| 14| """
| 15| v=self.queue.pop(0)
| 16| ret=v.pop(0)
| 17| if v: self.queue.append(v)
| 18| return ret
| 19|
| 20| def has_next(self):
| 21| """
| 22| :rtype: bool
| 23| """
| 24| if self.queue: return True
| 25| return False
| 26|
| 27| l1 = [1, 2]
| 28| l2 = [3, 4, 5, 6]
| 29| it = ZigZagIterator(l1, l2)
| 30| while it.has_next():
| 31| print(it.next())
#========================================================================================================================
# :: queues/priority_queue.py ::
#========================================================================================================================
| 1| """
| 2| Implementation of priority queue using linear array.
| 3| Insertion - O(n)
| 4| Extract min/max Node - O(1)
| 5| """
| 6| import itertools
| 7|
| 8|
| 9| class PriorityQueueNode:
| 10| def __init__(self, data, priority):
| 11| self.data = data
| 12| self.priority = priority
| 13|
| 14| def __repr__(self):
| 15| return "{}: {}".format(self.data, self.priority)
| 16|
| 17|
| 18| class PriorityQueue:
| 19| def __init__(self, items=None, priorities=None):
| 20| """Create a priority queue with items (list or iterable).
| 21| If items is not passed, create empty priority queue."""
| 22| self.priority_queue_list = []
| 23| if items is None:
| 24| return
| 25| if priorities is None:
| 26| priorities = itertools.repeat(None)
| 27| for item, priority in zip(items, priorities):
| 28| self.push(item, priority=priority)
| 29|
| 30| def __repr__(self):
| 31| return "PriorityQueue({!r})".format(self.priority_queue_list)
| 32|
| 33| def size(self):
| 34| """Return size of the priority queue.
| 35| """
| 36| return len(self.priority_queue_list)
| 37|
| 38| def push(self, item, priority=None):
| 39| """Push the item in the priority queue.
| 40| if priority is not given, priority is set to the value of item.
| 41| """
| 42| priority = item if priority is None else priority
| 43| node = PriorityQueueNode(item, priority)
| 44| for index, current in enumerate(self.priority_queue_list):
| 45| if current.priority < node.priority:
| 46| self.priority_queue_list.insert(index, node)
| 47| return
| 48| # when traversed complete queue
| 49| self.priority_queue_list.append(node)
| 50|
| 51| def pop(self):
| 52| """Remove and return the item with the lowest priority.
| 53| """
| 54| # remove and return the first node from the queue
| 55| return self.priority_queue_list.pop().data
| 56|
#========================================================================================================================
# :: stack/remove_min.py ::
#========================================================================================================================
| 1| """
| 2| Given a stack, a function remove_min accepts a stack as a parameter
| 3| and removes the smallest value from the stack.
| 4|
| 5| For example:
| 6| bottom [2, 8, 3, -6, 7, 3] top
| 7| After remove_min(stack):
| 8| bottom [2, 8, 3, 7, 3] top
| 9|
| 10| """
| 11| def remove_min(stack):
| 12| storage_stack = []
| 13| if len(stack) == 0: # Stack is empty
| 14| return stack
| 15| # Find the smallest value
| 16| min = stack.pop()
| 17| stack.append(min)
| 18| for i in range(len(stack)):
| 19| val = stack.pop()
| 20| if val <= min:
| 21| min = val
| 22| storage_stack.append(val)
| 23| # Back up stack and remove min value
| 24| for i in range(len(storage_stack)):
| 25| val = storage_stack.pop()
| 26| if val != min:
| 27| stack.append(val)
| 28| return stack
#========================================================================================================================
# :: stack/switch_pairs.py ::
#========================================================================================================================
| 1| """
| 2| Given a stack, switch_pairs function takes a stack as a parameter and that
| 3| switches successive pairs of numbers starting at the bottom of the stack.
| 4|
| 5| For example, if the stack initially stores these values:
| 6| bottom [3, 8, 17, 9, 1, 10] top
| 7| Your function should switch the first pair (3, 8), the second pair (17, 9), ...:
| 8| bottom [8, 3, 9, 17, 10, 1] top
| 9|
| 10| if there are an odd number of values in the stack, the value at the top of the
| 11| stack is not moved: For example:
| 12| bottom [3, 8, 17, 9, 1] top
| 13| It would again switch pairs of values, but the value at the top of the stack (1)
| 14| would not be moved
| 15| bottom [8, 3, 9, 17, 1] top
| 16|
| 17| Note: There are 2 solutions:
| 18| first_switch_pairs: it uses a single stack as auxiliary storage
| 19| second_switch_pairs: it uses a single queue as auxiliary storage
| 20| """
| 21| import collections
| 22|
| 23| def first_switch_pairs(stack):
| 24| storage_stack = []
| 25| for i in range(len(stack)):
| 26| storage_stack.append(stack.pop())
| 27| for i in range(len(storage_stack)):
| 28| if len(storage_stack) == 0:
| 29| break
| 30| first = storage_stack.pop()
| 31| if len(storage_stack) == 0: # case: odd number of values in stack
| 32| stack.append(first)
| 33| break
| 34| second = storage_stack.pop()
| 35| stack.append(second)
| 36| stack.append(first)
| 37| return stack
| 38|
| 39| def second_switch_pairs(stack):
| 40| q = collections.deque()
| 41| # Put all values into queue from stack
| 42| for i in range(len(stack)):
| 43| q.append(stack.pop())
| 44| # Put values back into stack from queue
| 45| for i in range(len(q)):
| 46| stack.append(q.pop())
| 47| # Now, stack is reverse, put all values into queue from stack
| 48| for i in range(len(stack)):
| 49| q.append(stack.pop())
| 50| # Swap pairs by appending the 2nd value before appending 1st value
| 51| for i in range(len(q)):
| 52| if len(q) == 0:
| 53| break
| 54| first = q.pop()
| 55| if len(q) == 0: # case: odd number of values in stack
| 56| stack.append(first)
| 57| break
| 58| second = q.pop()
| 59| stack.append(second)
| 60| stack.append(first)
| 61|
| 62| return stack
#========================================================================================================================
# :: stack/valid_parenthesis.py ::
#========================================================================================================================
| 1| """
| 2| Given a string containing just the characters
| 3| '(', ')', '{', '}', '[' and ']',
| 4| determine if the input string is valid.
| 5|
| 6| The brackets must close in the correct order,
| 7| "()" and "()[]{}" are all valid but "(]" and "([)]" are not.
| 8| """
| 9| def is_valid(s: str) -> bool:
| 10| stack = []
| 11| dic = {")": "(",
| 12| "}": "{",
| 13| "]": "["}
| 14| for char in s:
| 15| if char in dic.values():
| 16| stack.append(char)
| 17| elif char in dic:
| 18| if not stack or dic[char] != stack.pop():
| 19| return False
| 20| return not stack
#========================================================================================================================
# :: stack/__init__.py ::
#========================================================================================================================
| 1| from .stack import *
| 2| from .is_consecutive import *
| 3| from .is_sorted import *
| 4| from .remove_min import *
| 5| from .stutter import *
| 6| from .switch_pairs import *
| 7| from .valid_parenthesis import *
| 8| from .simplify_path import *
| 9| from .stack import *
| 10| from .ordered_stack import *
#========================================================================================================================
# :: stack/is_consecutive.py ::
#========================================================================================================================
| 1| """
| 2| Given a stack, a function is_consecutive takes a stack as a parameter and that
| 3| returns whether or not the stack contains a sequence of consecutive integers
| 4| starting from the bottom of the stack (returning true if it does, returning
| 5| false if it does not).
| 6|
| 7| For example:
| 8| bottom [3, 4, 5, 6, 7] top
| 9| Then the call of is_consecutive(s) should return true.
| 10| bottom [3, 4, 6, 7] top
| 11| Then the call of is_consecutive(s) should return false.
| 12| bottom [3, 2, 1] top
| 13| The function should return false due to reverse order.
| 14|
| 15| Note: There are 2 solutions:
| 16| first_is_consecutive: it uses a single stack as auxiliary storage
| 17| second_is_consecutive: it uses a single queue as auxiliary storage
| 18| """
| 19| import collections
| 20|
| 21| def first_is_consecutive(stack):
| 22| storage_stack = []
| 23| for i in range(len(stack)):
| 24| first_value = stack.pop()
| 25| if len(stack) == 0: # Case odd number of values in stack
| 26| return True
| 27| second_value = stack.pop()
| 28| if first_value - second_value != 1: # Not consecutive
| 29| return False
| 30| stack.append(second_value) # Backup second value
| 31| storage_stack.append(first_value)
| 32|
| 33| # Back up stack from storage stack
| 34| for i in range(len(storage_stack)):
| 35| stack.append(storage_stack.pop())
| 36| return True
| 37|
| 38| def second_is_consecutive(stack):
| 39| q = collections.deque()
| 40| for i in range(len(stack)):
| 41| first_value = stack.pop()
| 42| if len(stack) == 0: # Case odd number of values in stack
| 43| return True
| 44| second_value = stack.pop()
| 45| if first_value - second_value != 1: # Not consecutive
| 46| return False
| 47| stack.append(second_value) # Backup second value
| 48| q.append(first_value)
| 49|
| 50| # Back up stack from queue
| 51| for i in range(len(q)):
| 52| stack.append(q.pop())
| 53| for i in range(len(stack)):
| 54| q.append(stack.pop())
| 55| for i in range(len(q)):
| 56| stack.append(q.pop())
| 57|
| 58| return True
#========================================================================================================================
# :: stack/ordered_stack.py ::
#========================================================================================================================
| 1| #The stack remains always ordered such that the highest value is at the top and the lowest at the bottom
| 2|
| 3| class OrderedStack:
| 4| def __init__(self):
| 5| self.items = []
| 6|
| 7| def is_empty(self):
| 8| return self.items == []
| 9|
| 10| def push_t(self, item):
| 11| self.items.append(item)
| 12|
| 13| def push(self, item): #push method to maintain order when pushing new elements
| 14| temp_stack = OrderedStack()
| 15| if self.is_empty() or item > self.peek():
| 16| self.push_t(item)
| 17| else:
| 18| while item < self.peek() and not self.is_empty():
| 19| temp_stack.push_t(self.pop())
| 20| self.push_t(item)
| 21| while not temp_stack.is_empty():
| 22| self.push_t(temp_stack.pop())
| 23|
| 24| def pop(self):
| 25| if self.is_empty():
| 26| raise IndexError("Stack is empty")
| 27| return self.items.pop()
| 28|
| 29| def peek(self):
| 30| return self.items[len(self.items) - 1]
| 31|
| 32| def size(self):
| 33| return len(self.items)
#========================================================================================================================
# :: stack/stack.py ::
#========================================================================================================================
| 1| """
| 2| Stack Abstract Data Type (ADT)
| 3| Stack() creates a new stack that is empty.
| 4| It needs no parameters and returns an empty stack.
| 5| push(item) adds a new item to the top of the stack.
| 6| It needs the item and returns nothing.
| 7| pop() removes the top item from the stack.
| 8| It needs no parameters and returns the item. The stack is modified.
| 9| peek() returns the top item from the stack but does not remove it.
| 10| It needs no parameters. The stack is not modified.
| 11| isEmpty() tests to see whether the stack is empty.
| 12| It needs no parameters and returns a boolean value.
| 13| size() returns the number of items on the stack.
| 14| It needs no parameters and returns an integer.
| 15| """
| 16| from abc import ABCMeta, abstractmethod
| 17| class AbstractStack(metaclass=ABCMeta):
| 18| """Abstract Class for Stacks."""
| 19| def __init__(self):
| 20| self._top = -1
| 21|
| 22| def __len__(self):
| 23| return self._top + 1
| 24|
| 25| def __str__(self):
| 26| result = " ".join(map(str, self))
| 27| return 'Top-> ' + result
| 28|
| 29| def is_empty(self):
| 30| return self._top == -1
| 31|
| 32| @abstractmethod
| 33| def __iter__(self):
| 34| pass
| 35|
| 36| @abstractmethod
| 37| def push(self, value):
| 38| pass
| 39|
| 40| @abstractmethod
| 41| def pop(self):
| 42| pass
| 43|
| 44| @abstractmethod
| 45| def peek(self):
| 46| pass
| 47|
| 48|
| 49| class ArrayStack(AbstractStack):
| 50| def __init__(self, size=10):
| 51| """
| 52| Initialize python List with size of 10 or user given input.
| 53| Python List type is a dynamic array, so we have to restrict its
| 54| dynamic nature to make it work like a static array.
| 55| """
| 56| super().__init__()
| 57| self._array = [None] * size
| 58|
| 59| def __iter__(self):
| 60| probe = self._top
| 61| while True:
| 62| if probe == -1:
| 63| return
| 64| yield self._array[probe]
| 65| probe -= 1
| 66|
| 67| def push(self, value):
| 68| self._top += 1
| 69| if self._top == len(self._array):
| 70| self._expand()
| 71| self._array[self._top] = value
| 72|
| 73| def pop(self):
| 74| if self.is_empty():
| 75| raise IndexError("stack is empty")
| 76| value = self._array[self._top]
| 77| self._top -= 1
| 78| return value
| 79|
| 80| def peek(self):
| 81| """returns the current top element of the stack."""
| 82| if self.is_empty():
| 83| raise IndexError("stack is empty")
| 84| return self._array[self._top]
| 85|
| 86| def _expand(self):
| 87| """
| 88| expands size of the array.
| 89| Time Complexity: O(n)
| 90| """
| 91| self._array += [None] * len(self._array) # double the size of the array
| 92|
| 93|
| 94| class StackNode:
| 95| """Represents a single stack node."""
| 96| def __init__(self, value):
| 97| self.value = value
| 98| self.next = None
| 99|
|100|
|101| class LinkedListStack(AbstractStack):
|102|
|103| def __init__(self):
|104| super().__init__()
|105| self.head = None
|106|
|107| def __iter__(self):
|108| probe = self.head
|109| while True:
|110| if probe is None:
|111| return
|112| yield probe.value
|113| probe = probe.next
|114|
|115| def push(self, value):
|116| node = StackNode(value)
|117| node.next = self.head
|118| self.head = node
|119| self._top += 1
|120|
|121| def pop(self):
|122| if self.is_empty():
|123| raise IndexError("Stack is empty")
|124| value = self.head.value
|125| self.head = self.head.next
|126| self._top -= 1
|127| return value
|128|
|129| def peek(self):
|130| if self.is_empty():
|131| raise IndexError("Stack is empty")
|132| return self.head.value
#========================================================================================================================
# :: stack/stutter.py ::
#========================================================================================================================
| 1| """
| 2| Given a stack, stutter takes a stack as a parameter and replaces every value
| 3| in the stack with two occurrences of that value.
| 4|
| 5| For example, suppose the stack stores these values:
| 6| bottom [3, 7, 1, 14, 9] top
| 7| Then the stack should store these values after the method terminates:
| 8| bottom [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] top
| 9|
| 10| Note: There are 2 solutions:
| 11| first_stutter: it uses a single stack as auxiliary storage
| 12| second_stutter: it uses a single queue as auxiliary storage
| 13| """
| 14| import collections
| 15|
| 16| def first_stutter(stack):
| 17| storage_stack = []
| 18| for i in range(len(stack)):
| 19| storage_stack.append(stack.pop())
| 20| for i in range(len(storage_stack)):
| 21| val = storage_stack.pop()
| 22| stack.append(val)
| 23| stack.append(val)
| 24|
| 25| return stack
| 26|
| 27| def second_stutter(stack):
| 28| q = collections.deque()
| 29| # Put all values into queue from stack
| 30| for i in range(len(stack)):
| 31| q.append(stack.pop())
| 32| # Put values back into stack from queue
| 33| for i in range(len(q)):
| 34| stack.append(q.pop())
| 35| # Now, stack is reverse, put all values into queue from stack
| 36| for i in range(len(stack)):
| 37| q.append(stack.pop())
| 38| # Put 2 times value into stack from queue
| 39| for i in range(len(q)):
| 40| val = q.pop()
| 41| stack.append(val)
| 42| stack.append(val)
| 43|
| 44| return stack
#========================================================================================================================
# :: stack/simplify_path.py ::
#========================================================================================================================
| 1| """
| 2| Given an absolute path for a file (Unix-style), simplify it.
| 3|
| 4| For example,
| 5| path = "/home/", => "/home"
| 6| path = "/a/./b/../../c/", => "/c"
| 7|
| 8| * Did you consider the case where path = "/../"?
| 9| In this case, you should return "/".
| 10| * Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/".
| 11| In this case, you should ignore redundant slashes and return "/home/foo".
| 12| """
| 13| def simplify_path(path):
| 14| """
| 15| :type path: str
| 16| :rtype: str
| 17| """
| 18| skip = {'..', '.', ''}
| 19| stack = []
| 20| paths = path.split('/')
| 21| for tok in paths:
| 22| if tok == '..':
| 23| if stack:
| 24| stack.pop()
| 25| elif tok not in skip:
| 26| stack.append(tok)
| 27| return '/' + '/'.join(stack)
#========================================================================================================================
# :: stack/longest_abs_path.py ::
#========================================================================================================================
| 1| # def lengthLongestPath(input):
| 2| # maxlen = 0
| 3| # pathlen = {0: 0}
| 4| # for line in input.splitlines():
| 5| # print("---------------")
| 6| # print("line:", line)
| 7| # name = line.strip('\t')
| 8| # print("name:", name)
| 9| # depth = len(line) - len(name)
| 10| # print("depth:", depth)
| 11| # if '.' in name:
| 12| # maxlen = max(maxlen, pathlen[depth] + len(name))
| 13| # else:
| 14| # pathlen[depth + 1] = pathlen[depth] + len(name) + 1
| 15| # print("maxlen:", maxlen)
| 16| # return maxlen
| 17|
| 18| # def lengthLongestPath(input):
| 19| # paths = input.split("\n")
| 20| # level = [0] * 10
| 21| # maxLength = 0
| 22| # for path in paths:
| 23| # print("-------------")
| 24| # levelIdx = path.rfind("\t")
| 25| # print("Path: ", path)
| 26| # print("path.rfind(\\t)", path.rfind("\t"))
| 27| # print("levelIdx: ", levelIdx)
| 28| # print("level: ", level)
| 29| # level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1
| 30| # print("level: ", level)
| 31| # if "." in path:
| 32| # maxLength = max(maxLength, level[levelIdx+1] - 1)
| 33| # print("maxlen: ", maxLength)
| 34| # return maxLength
| 35|
| 36| def length_longest_path(input):
| 37| """
| 38| :type input: str
| 39| :rtype: int
| 40| """
| 41| curr_len, max_len = 0, 0 # running length and max length
| 42| stack = [] # keep track of the name length
| 43| for s in input.split('\n'):
| 44| print("---------")
| 45| print("<path>:", s)
| 46| depth = s.count('\t') # the depth of current dir or file
| 47| print("depth: ", depth)
| 48| print("stack: ", stack)
| 49| print("curlen: ", curr_len)
| 50| while len(stack) > depth: # go back to the correct depth
| 51| curr_len -= stack.pop()
| 52| stack.append(len(s.strip('\t'))+1) # 1 is the length of '/'
| 53| curr_len += stack[-1] # increase current length
| 54| print("stack: ", stack)
| 55| print("curlen: ", curr_len)
| 56| if '.' in s: # update maxlen only when it is a file
| 57| max_len = max(max_len, curr_len-1) # -1 is to minus one '/'
| 58| return max_len
| 59|
| 60| st= "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"
| 61| st2 = "a\n\tb1\n\t\tf1.txt\n\taaaaa\n\t\tf2.txt"
| 62| print("path:", st2)
| 63|
| 64| print("answer:", length_longest_path(st2))
| 65|
#========================================================================================================================
# :: stack/is_sorted.py ::
#========================================================================================================================
| 1| """
| 2| Given a stack, a function is_sorted accepts a stack as a parameter and returns
| 3| true if the elements in the stack occur in ascending increasing order from
| 4| bottom, and false otherwise. That is, the smallest element should be at bottom
| 5|
| 6| For example:
| 7| bottom [6, 3, 5, 1, 2, 4] top
| 8| The function should return false
| 9| bottom [1, 2, 3, 4, 5, 6] top
| 10| The function should return true
| 11| """
| 12| def is_sorted(stack):
| 13| storage_stack = []
| 14| for i in range(len(stack)):
| 15| if len(stack) == 0:
| 16| break
| 17| first_val = stack.pop()
| 18| if len(stack) == 0:
| 19| break
| 20| second_val = stack.pop()
| 21| if first_val < second_val:
| 22| return False
| 23| storage_stack.append(first_val)
| 24| stack.append(second_val)
| 25|
| 26| # Backup stack
| 27| for i in range(len(storage_stack)):
| 28| stack.append(storage_stack.pop())
| 29|
| 30| return True
#========================================================================================================================
# :: set/__init__.py ::
#========================================================================================================================
| 1| from .find_keyboard_row import *
#========================================================================================================================
# :: set/set_covering.py ::
#========================================================================================================================
| 1| from itertools import chain, combinations
| 2|
| 3| """
| 4| Universe *U* of n elements
| 5| Collection of subsets of U:
| 6| S = S1,S2...,Sm
| 7| Where every substet Si has an associated cost.
| 8|
| 9| Find a minimum cost subcollection of S that covers all elements of U
| 10|
| 11| Example:
| 12| U = {1,2,3,4,5}
| 13| S = {S1,S2,S3}
| 14|
| 15| S1 = {4,1,3}, Cost(S1) = 5
| 16| S2 = {2,5}, Cost(S2) = 10
| 17| S3 = {1,4,3,2}, Cost(S3) = 3
| 18|
| 19| Output:
| 20| Set cover = {S2, S3}
| 21| Min Cost = 13
| 22| """
| 23|
| 24|
| 25| def powerset(iterable):
| 26| """Calculate the powerset of any iterable.
| 27|
| 28| For a range of integers up to the length of the given list,
| 29| make all possible combinations and chain them together as one object.
| 30| From https://docs.python.org/3/library/itertools.html#itertools-recipes
| 31| """
| 32| "list(powerset([1,2,3])) --> [(), (1,), (2,), (3,), (1,2), (1,3), (2,3), (1,2,3)]"
| 33| s = list(iterable)
| 34| return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
| 35|
| 36|
| 37| def optimal_set_cover(universe, subsets, costs):
| 38| """ Optimal algorithm - DONT USE ON BIG INPUTS - O(2^n) complexity!
| 39| Finds the minimum cost subcollection os S that covers all elements of U
| 40|
| 41| Args:
| 42| universe (list): Universe of elements
| 43| subsets (dict): Subsets of U {S1:elements,S2:elements}
| 44| costs (dict): Costs of each subset in S - {S1:cost, S2:cost...}
| 45| """
| 46| pset = powerset(subsets.keys())
| 47| best_set = None
| 48| best_cost = float("inf")
| 49| for subset in pset:
| 50| covered = set()
| 51| cost = 0
| 52| for s in subset:
| 53| covered.update(subsets[s])
| 54| cost += costs[s]
| 55| if len(covered) == len(universe) and cost < best_cost:
| 56| best_set = subset
| 57| best_cost = cost
| 58| return best_set
| 59|
| 60|
| 61| def greedy_set_cover(universe, subsets, costs):
| 62| """Approximate greedy algorithm for set-covering. Can be used on large
| 63| inputs - though not an optimal solution.
| 64|
| 65| Args:
| 66| universe (list): Universe of elements
| 67| subsets (dict): Subsets of U {S1:elements,S2:elements}
| 68| costs (dict): Costs of each subset in S - {S1:cost, S2:cost...}
| 69| """
| 70| elements = set(e for s in subsets.keys() for e in subsets[s])
| 71| # elements don't cover universe -> invalid input for set cover
| 72| if elements != universe:
| 73| return None
| 74|
| 75| # track elements of universe covered
| 76| covered = set()
| 77| cover_sets = []
| 78|
| 79| while covered != universe:
| 80| min_cost_elem_ratio = float("inf")
| 81| min_set = None
| 82| # find set with minimum cost:elements_added ratio
| 83| for s, elements in subsets.items():
| 84| new_elements = len(elements - covered)
| 85| # set may have same elements as already covered -> new_elements = 0
| 86| # check to avoid division by 0 error
| 87| if new_elements != 0:
| 88| cost_elem_ratio = costs[s] / new_elements
| 89| if cost_elem_ratio < min_cost_elem_ratio:
| 90| min_cost_elem_ratio = cost_elem_ratio
| 91| min_set = s
| 92| cover_sets.append(min_set)
| 93| # union
| 94| covered |= subsets[min_set]
| 95| return cover_sets
| 96|
| 97|
| 98| if __name__ == '__main__':
| 99| universe = {1, 2, 3, 4, 5}
|100| subsets = {'S1': {4, 1, 3}, 'S2': {2, 5}, 'S3': {1, 4, 3, 2}}
|101| costs = {'S1': 5, 'S2': 10, 'S3': 3}
|102|
|103| optimal_cover = optimal_set_cover(universe, subsets, costs)
|104| optimal_cost = sum(costs[s] for s in optimal_cover)
|105|
|106| greedy_cover = greedy_set_cover(universe, subsets, costs)
|107| greedy_cost = sum(costs[s] for s in greedy_cover)
|108|
|109| print('Optimal Set Cover:')
|110| print(optimal_cover)
|111| print('Cost = %s' % optimal_cost)
|112|
|113| print('Greedy Set Cover:')
|114| print(greedy_cover)
|115| print('Cost = %s' % greedy_cost)
#========================================================================================================================
# :: set/randomized_set.py ::
#========================================================================================================================
| 1| #! /usr/bin/env python3
| 2|
| 3| """
| 4| Design a data structure that supports all following operations
| 5| in average O(1) time.
| 6|
| 7| insert(val): Inserts an item val to the set if not already present.
| 8| remove(val): Removes an item val from the set if present.
| 9| random_element: Returns a random element from current set of elements.
| 10| Each element must have the same probability of being returned.
| 11| """
| 12|
| 13| import random
| 14|
| 15|
| 16| class RandomizedSet():
| 17| """
| 18| idea: shoot
| 19| """
| 20|
| 21| def __init__(self):
| 22| self.elements = []
| 23| self.index_map = {} # element -> index
| 24|
| 25| def insert(self, new_one):
| 26| if new_one in self.index_map:
| 27| return
| 28| self.index_map[new_one] = len(self.elements)
| 29| self.elements.append(new_one)
| 30|
| 31| def remove(self, old_one):
| 32| if not old_one in self.index_map:
| 33| return
| 34| index = self.index_map[old_one]
| 35| last = self.elements.pop()
| 36| self.index_map.pop(old_one)
| 37| if index == len(self.elements):
| 38| return
| 39| self.elements[index] = last
| 40| self.index_map[last] = index
| 41|
| 42| def random_element(self):
| 43| return random.choice(self.elements)
| 44|
| 45|
| 46| def __test():
| 47| rset = RandomizedSet()
| 48| ground_truth = set()
| 49| n = 64
| 50|
| 51| for i in range(n):
| 52| rset.insert(i)
| 53| ground_truth.add(i)
| 54|
| 55| # Remove a half
| 56| for i in random.sample(range(n), n // 2):
| 57| rset.remove(i)
| 58| ground_truth.remove(i)
| 59|
| 60| print(len(ground_truth), len(rset.elements), len(rset.index_map))
| 61| for i in ground_truth:
| 62| assert(i == rset.elements[rset.index_map[i]])
| 63|
| 64| for i in range(n):
| 65| print(rset.random_element(), end=' ')
| 66| print()
| 67|
| 68|
| 69| if __name__ == "__main__":
| 70| __test()
#========================================================================================================================
# :: set/find_keyboard_row.py ::
#========================================================================================================================
| 1| """
| 2| Given a List of words, return the words that can be typed using letters of
| 3| alphabet on only one row's of American keyboard.
| 4|
| 5| For example:
| 6| Input: ["Hello", "Alaska", "Dad", "Peace"]
| 7| Output: ["Alaska", "Dad"]
| 8|
| 9| Reference: https://leetcode.com/problems/keyboard-row/description/
| 10| """
| 11| def find_keyboard_row(words):
| 12| """
| 13| :type words: List[str]
| 14| :rtype: List[str]
| 15| """
| 16| keyboard = [
| 17| set('qwertyuiop'),
| 18| set('asdfghjkl'),
| 19| set('zxcvbnm'),
| 20| ]
| 21| result = []
| 22| for word in words:
| 23| for key in keyboard:
| 24| if set(word.lower()).issubset(key):
| 25| result.append(word)
| 26| return result
#========================================================================================================================
# :: matrix/search_in_sorted_matrix.py ::
#========================================================================================================================
| 1| #
| 2| # Search a key in a row wise and column wise sorted (non-decreasing) matrix.
| 3| # m- Number of rows in the matrix
| 4| # n- Number of columns in the matrix
| 5| # T(n)- O(m+n)
| 6| #
| 7|
| 8|
| 9| def search_in_a_sorted_matrix(mat, m, n, key):
| 10| i, j = m-1, 0
| 11| while i >= 0 and j < n:
| 12| if key == mat[i][j]:
| 13| print ('Key %s found at row- %s column- %s' % (key, i+1, j+1))
| 14| return
| 15| if key < mat[i][j]:
| 16| i -= 1
| 17| else:
| 18| j += 1
| 19| print ('Key %s not found' % (key))
| 20|
| 21|
| 22| def main():
| 23| mat = [
| 24| [2, 5, 7],
| 25| [4, 8, 13],
| 26| [9, 11, 15],
| 27| [12, 17, 20]
| 28| ]
| 29| key = 13
| 30| print (mat)
| 31| search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), key)
| 32|
| 33|
| 34| if __name__ == '__main__':
| 35| main()
#========================================================================================================================
# :: matrix/count_paths.py ::
#========================================================================================================================
| 1| #
| 2| # Count the number of unique paths from a[0][0] to a[m-1][n-1]
| 3| # We are allowed to move either right or down from a cell in the matrix.
| 4| # Approaches-
| 5| # (i) Recursion- Recurse starting from a[m-1][n-1], upwards and leftwards,
| 6| # add the path count of both recursions and return count.
| 7| # (ii) Dynamic Programming- Start from a[0][0].Store the count in a count
| 8| # matrix. Return count[m-1][n-1]
| 9| # T(n)- O(mn), S(n)- O(mn)
| 10| #
| 11|
| 12|
| 13| def count_paths(m, n):
| 14| if m < 1 or n < 1:
| 15| return -1
| 16| count = [[None for j in range(n)] for i in range(m)]
| 17|
| 18| # Taking care of the edge cases- matrix of size 1xn or mx1
| 19| for i in range(n):
| 20| count[0][i] = 1
| 21| for j in range(m):
| 22| count[j][0] = 1
| 23|
| 24| for i in range(1, m):
| 25| for j in range(1, n):
| 26| # Number of ways to reach a[i][j] = number of ways to reach
| 27| # a[i-1][j] + a[i][j-1]
| 28| count[i][j] = count[i - 1][j] + count[i][j - 1]
| 29|
| 30| print(count[m - 1][n - 1])
| 31|
| 32|
| 33| def main():
| 34| m, n = map(int, input('Enter two positive integers: ').split())
| 35| count_paths(m, n)
| 36|
| 37|
| 38| if __name__ == '__main__':
| 39| main()
#========================================================================================================================
# :: matrix/multiply.py ::
#========================================================================================================================
| 1| """
| 2| This algorithm takes two compatible two dimensional matrix
| 3| and return their product
| 4| Space complexity: O(n^2)
| 5| Possible edge case: the number of columns of multiplicand not consistent with
| 6| the number of rows of multiplier, will raise exception
| 7| """
| 8|
| 9|
| 10| def multiply(multiplicand: list, multiplier: list) -> list:
| 11| """
| 12| :type A: List[List[int]]
| 13| :type B: List[List[int]]
| 14| :rtype: List[List[int]]
| 15| """
| 16| multiplicand_row, multiplicand_col = len(
| 17| multiplicand), len(multiplicand[0])
| 18| multiplier_row, multiplier_col = len(multiplier), len(multiplier[0])
| 19| if(multiplicand_col != multiplier_row):
| 20| raise Exception(
| 21| "Multiplicand matrix not compatible with Multiplier matrix.")
| 22| # create a result matrix
| 23| result = [[0] * multiplier_col for i in range(multiplicand_row)]
| 24| for i in range(multiplicand_row):
| 25| for j in range(multiplier_col):
| 26| for k in range(len(multiplier)):
| 27| result[i][j] += multiplicand[i][k] * multiplier[k][j]
| 28| return result
#========================================================================================================================
# :: matrix/sparse_mul.py ::
#========================================================================================================================
| 1| """
| 2| Given two sparse matrices A and B, return the result of AB.
| 3|
| 4| You may assume that A's column number is equal to B's row number.
| 5|
| 6| Example:
| 7|
| 8| A = [
| 9| [ 1, 0, 0],
| 10| [-1, 0, 3]
| 11| ]
| 12|
| 13| B = [
| 14| [ 7, 0, 0 ],
| 15| [ 0, 0, 0 ],
| 16| [ 0, 0, 1 ]
| 17| ]
| 18|
| 19|
| 20| | 1 0 0 | | 7 0 0 | | 7 0 0 |
| 21| AB = | -1 0 3 | x | 0 0 0 | = | -7 0 3 |
| 22| | 0 0 1 |
| 23| """
| 24|
| 25|
| 26| # Python solution without table (~156ms):
| 27| def multiply(self, a, b):
| 28| """
| 29| :type A: List[List[int]]
| 30| :type B: List[List[int]]
| 31| :rtype: List[List[int]]
| 32| """
| 33| if a is None or b is None: return None
| 34| m, n, l = len(a), len(b[0]), len(b[0])
| 35| if len(b) != n:
| 36| raise Exception("A's column number must be equal to B's row number.")
| 37| c = [[0 for _ in range(l)] for _ in range(m)]
| 38| for i, row in enumerate(a):
| 39| for k, eleA in enumerate(row):
| 40| if eleA:
| 41| for j, eleB in enumerate(b[k]):
| 42| if eleB: c[i][j] += eleA * eleB
| 43| return c
| 44|
| 45|
| 46| # Python solution with only one table for B (~196ms):
| 47| def multiply(self, a, b):
| 48| """
| 49| :type A: List[List[int]]
| 50| :type B: List[List[int]]
| 51| :rtype: List[List[int]]
| 52| """
| 53| if a is None or b is None: return None
| 54| m, n, l = len(a), len(a[0]), len(b[0])
| 55| if len(b) != n:
| 56| raise Exception("A's column number must be equal to B's row number.")
| 57| c = [[0 for _ in range(l)] for _ in range(m)]
| 58| table_b = {}
| 59| for k, row in enumerate(b):
| 60| table_b[k] = {}
| 61| for j, eleB in enumerate(row):
| 62| if eleB: table_b[k][j] = eleB
| 63| for i, row in enumerate(a):
| 64| for k, eleA in enumerate(row):
| 65| if eleA:
| 66| for j, eleB in table_b[k].iteritems():
| 67| c[i][j] += eleA * eleB
| 68| return c
| 69|
| 70| # Python solution with two tables (~196ms):
| 71| def multiply(self, a, b):
| 72| """
| 73| :type A: List[List[int]]
| 74| :type B: List[List[int]]
| 75| :rtype: List[List[int]]
| 76| """
| 77| if a is None or b is None: return None
| 78| m, n = len(a), len(b[0])
| 79| if len(b) != n:
| 80| raise Exception("A's column number must be equal to B's row number.")
| 81| l = len(b[0])
| 82| table_a, table_b = {}, {}
| 83| for i, row in enumerate(a):
| 84| for j, ele in enumerate(row):
| 85| if ele:
| 86| if i not in table_a: table_a[i] = {}
| 87| table_a[i][j] = ele
| 88| for i, row in enumerate(b):
| 89| for j, ele in enumerate(row):
| 90| if ele:
| 91| if i not in table_b: table_b[i] = {}
| 92| table_b[i][j] = ele
| 93| c = [[0 for j in range(l)] for i in range(m)]
| 94| for i in table_a:
| 95| for k in table_a[i]:
| 96| if k not in table_b: continue
| 97| for j in table_b[k]:
| 98| c[i][j] += table_a[i][k] * table_b[k][j]
| 99| return c
#========================================================================================================================
# :: matrix/__init__.py ::
#========================================================================================================================
| 1|
#========================================================================================================================
# :: matrix/rotate_image.py ::
#========================================================================================================================
| 1| """
| 2| You are given an n x n 2D mat representing an image.
| 3|
| 4| Rotate the image by 90 degrees (clockwise).
| 5|
| 6| Follow up:
| 7| Could you do this in-place?
| 8| """
| 9|
| 10|
| 11| # clockwise rotate
| 12| # first reverse up to down, then swap the symmetry
| 13| # 1 2 3 7 8 9 7 4 1
| 14| # 4 5 6 => 4 5 6 => 8 5 2
| 15| # 7 8 9 1 2 3 9 6 3
| 16|
| 17| def rotate(mat):
| 18| if not mat:
| 19| return mat
| 20| mat.reverse()
| 21| for i in range(len(mat)):
| 22| for j in range(i):
| 23| mat[i][j], mat[j][i] = mat[j][i], mat[i][j]
| 24| return mat
| 25|
| 26|
| 27| if __name__ == "__main__":
| 28| mat = [[1,2,3],
| 29| [4,5,6],
| 30| [7,8,9]]
| 31| print(mat)
| 32| rotate(mat)
| 33| print(mat)
#========================================================================================================================
# :: matrix/spiral_traversal.py ::
#========================================================================================================================
| 1| """
| 2| Given a matrix of m x n elements (m rows, n columns),
| 3| return all elements of the matrix in spiral order.
| 4| For example,
| 5| Given the following matrix:
| 6| [
| 7| [ 1, 2, 3 ],
| 8| [ 4, 5, 6 ],
| 9| [ 7, 8, 9 ]
| 10| ]
| 11|
| 12|
| 13| You should return [1,2,3,6,9,8,7,4,5].
| 14| """
| 15|
| 16|
| 17| def spiral_traversal(matrix):
| 18| res = []
| 19| if len(matrix) == 0:
| 20| return res
| 21| row_begin = 0
| 22| row_end = len(matrix) - 1
| 23| col_begin = 0
| 24| col_end = len(matrix[0]) - 1
| 25|
| 26| while row_begin <= row_end and col_begin <= col_end:
| 27| for i in range(col_begin, col_end+1):
| 28| res.append(matrix[row_begin][i])
| 29| row_begin += 1
| 30|
| 31| for i in range(row_begin, row_end+1):
| 32| res.append(matrix[i][col_end])
| 33| col_end -= 1
| 34|
| 35| if row_begin <= row_end:
| 36| for i in range(col_end, col_begin-1, -1):
| 37| res.append(matrix[row_end][i])
| 38| row_end -= 1
| 39|
| 40| if col_begin <= col_end:
| 41| for i in range(row_end, row_begin-1, -1):
| 42| res.append(matrix[i][col_begin])
| 43| col_begin += 1
| 44|
| 45| return res
| 46|
| 47|
| 48| if __name__ == "__main__":
| 49| mat = [[1, 2, 3],
| 50| [4, 5, 6],
| 51| [7, 8, 9]]
| 52| print(spiral_traversal(mat))
#========================================================================================================================
# :: matrix/bomb_enemy.py ::
#========================================================================================================================
| 1| """
| 2| Given a 2D grid, each cell is either a wall 'W',
| 3| an enemy 'E' or empty '0' (the number zero),
| 4| return the maximum enemies you can kill using one bomb.
| 5| The bomb kills all the enemies in the same row and column from
| 6| the planted point until it hits the wall since the wall is too strong
| 7| to be destroyed.
| 8| Note that you can only put the bomb at an empty cell.
| 9|
| 10| Example:
| 11| For the given grid
| 12|
| 13| 0 E 0 0
| 14| E 0 W E
| 15| 0 E 0 0
| 16|
| 17| return 3. (Placing a bomb at (1,1) kills 3 enemies)
| 18| """
| 19|
| 20| def max_killed_enemies(grid):
| 21| if not grid: return 0
| 22| m, n = len(grid), len(grid[0])
| 23| max_killed = 0
| 24| row_e, col_e = 0, [0] * n
| 25| # iterates over all cells in the grid
| 26| for i in range(m):
| 27| for j in range(n):
| 28| # makes sure we are next to a wall.
| 29| if j == 0 or grid[i][j-1] == 'W':
| 30| row_e = row_kills(grid, i, j)
| 31| # makes sure we are next to a wall.
| 32| if i == 0 or grid[i-1][j] == 'W':
| 33| col_e[j] = col_kills(grid, i, j)
| 34| # makes sure the cell contains a 0
| 35| if grid[i][j] == '0':
| 36| # updates the variable
| 37| max_killed = max(max_killed, row_e + col_e[j])
| 38|
| 39| return max_killed
| 40|
| 41| # calculate killed enemies for row i from column j
| 42| def row_kills(grid, i, j):
| 43| num = 0
| 44| len_row = len(grid[0])
| 45| while j < len_row and grid[i][j] != 'W':
| 46| if grid[i][j] == 'E':
| 47| num += 1
| 48| j += 1
| 49| return num
| 50|
| 51| # calculate killed enemies for column j from row i
| 52| def col_kills(grid, i, j):
| 53| num = 0
| 54| len_col = len(grid)
| 55| while i < len_col and grid[i][j] != 'W':
| 56| if grid[i][j] == 'E':
| 57| num += 1
| 58| i += 1
| 59| return num
| 60|
| 61|
| 62|
| 63| # ----------------- TESTS -------------------------
| 64|
| 65| """
| 66| Testsuite for the project
| 67| """
| 68|
| 69| import unittest
| 70|
| 71| class TestBombEnemy(unittest.TestCase):
| 72| def test_3x4(self):
| 73| grid1 = [["0","E","0","0"],
| 74| ["E","0","W","E"],
| 75| ["0","E","0","0"]]
| 76| self.assertEqual(3,max_killed_enemies(grid1))
| 77| def test_4x4(self):
| 78| grid1 = [
| 79| ["0", "E", "0", "E"],
| 80| ["E", "E", "E", "0"],
| 81| ["E", "0", "W", "E"],
| 82| ["0", "E", "0", "0"]]
| 83| grid2 = [
| 84| ["0", "0", "0", "E"],
| 85| ["E", "0", "0", "0"],
| 86| ["E", "0", "W", "E"],
| 87| ["0", "E", "0", "0"]]
| 88| self.assertEqual(5,max_killed_enemies(grid1))
| 89| self.assertEqual(3,max_killed_enemies(grid2))
| 90|
| 91| if __name__ == "__main__":
| 92| unittest.main()
| 93|
#========================================================================================================================
# :: matrix/sparse_dot_vector.py ::
#========================================================================================================================
| 1| #! /usr/bin/env python3
| 2|
| 3| """
| 4| Suppose we have very large sparse vectors, which contains a lot of zeros and double .
| 5|
| 6| find a data structure to store them
| 7| get the dot product of them
| 8| """
| 9|
| 10|
| 11| def vector_to_index_value_list(vector):
| 12| return [(i, v) for i, v in enumerate(vector) if v != 0.0]
| 13|
| 14|
| 15| def dot_product(iv_list1, iv_list2):
| 16|
| 17| product = 0
| 18| p1 = len(iv_list1) - 1
| 19| p2 = len(iv_list2) - 1
| 20|
| 21| while p1 >= 0 and p2 >= 0:
| 22| i1, v1 = iv_list1[p1]
| 23| i2, v2 = iv_list2[p2]
| 24|
| 25| if i1 < i2:
| 26| p1 -= 1
| 27| elif i2 < i1:
| 28| p2 -= 1
| 29| else:
| 30| product += v1 * v2
| 31| p1 -= 1
| 32| p2 -= 1
| 33|
| 34| return product
| 35|
| 36|
| 37| def __test_simple():
| 38| print(dot_product(vector_to_index_value_list([1., 2., 3.]),
| 39| vector_to_index_value_list([0., 2., 2.])))
| 40| # 10
| 41|
| 42|
| 43| def __test_time():
| 44| vector_length = 1024
| 45| vector_count = 1024
| 46| nozero_counut = 10
| 47|
| 48| def random_vector():
| 49| import random
| 50| vector = [0 for _ in range(vector_length)]
| 51| for i in random.sample(range(vector_length), nozero_counut):
| 52| vector[i] = random.random()
| 53| return vector
| 54|
| 55| vectors = [random_vector() for _ in range(vector_count)]
| 56| iv_lists = [vector_to_index_value_list(vector) for vector in vectors]
| 57|
| 58| import time
| 59|
| 60| time_start = time.time()
| 61| for i in range(vector_count):
| 62| for j in range(i):
| 63| dot_product(iv_lists[i], iv_lists[j])
| 64| time_end = time.time()
| 65|
| 66| print(time_end - time_start, 'seconds')
| 67|
| 68|
| 69| if __name__ == '__main__':
| 70| __test_simple()
| 71| __test_time()
#========================================================================================================================
# :: matrix/crout_matrix_decomposition.py ::
#========================================================================================================================
| 1| """
| 2| Crout matrix decomposition is used to find two matrices that, when multiplied
| 3| give our input matrix, so L * U = A.
| 4| L stands for lower and L has non-zero elements only on diagonal and below.
| 5| U stands for upper and U has non-zero elements only on diagonal and above.
| 6|
| 7| This can for example be used to solve systems of linear equations.
| 8| The last if is used if to avoid dividing by zero.
| 9|
| 10| Example:
| 11| We input the A matrix:
| 12| [[1,2,3],
| 13| [3,4,5],
| 14| [6,7,8]]
| 15|
| 16| We get:
| 17| L = [1.0, 0.0, 0.0]
| 18| [3.0, -2.0, 0.0]
| 19| [6.0, -5.0, 0.0]
| 20| U = [1.0, 2.0, 3.0]
| 21| [0.0, 1.0, 2.0]
| 22| [0.0, 0.0, 1.0]
| 23|
| 24| We can check that L * U = A.
| 25|
| 26| I think the complexity should be O(n^3).
| 27| """
| 28|
| 29| def crout_matrix_decomposition(A):
| 30| n = len(A)
| 31| L = [[0.0] * n for i in range(n)]
| 32| U = [[0.0] * n for i in range(n)]
| 33| for j in range(n):
| 34| U[j][j] = 1.0
| 35| for i in range(j, n):
| 36| alpha = float(A[i][j])
| 37| for k in range(j):
| 38| alpha -= L[i][k]*U[k][j]
| 39| L[i][j] = float(alpha)
| 40| for i in range(j+1, n):
| 41| tempU = float(A[j][i])
| 42| for k in range(j):
| 43| tempU -= float(L[j][k]*U[k][i])
| 44| if int(L[j][j]) == 0:
| 45| L[j][j] = float(0.1**40)
| 46| U[j][i] = float(tempU/L[j][j])
| 47| return (L,U)
#========================================================================================================================
# :: matrix/sudoku_validator.py ::
#========================================================================================================================
| 1| """
| 2| Write a function validSolution/ValidateSolution/valid_solution() that accepts a 2D array representing a Sudoku board, and returns true if it is a valid solution, or false otherwise. The cells of the sudoku board may also contain 0's, which will represent empty cells. Boards containing one or more zeroes are considered to be invalid solutions.
| 3| The board is always 9 cells by 9 cells, and every cell only contains integers from 0 to 9.
| 4|
| 5| (More info at: http://en.wikipedia.org/wiki/Sudoku)
| 6| """
| 7|
| 8| # Using dict/hash-table
| 9| from collections import defaultdict
| 10| def valid_solution_hashtable(board):
| 11| for i in range(len(board)):
| 12| dict_row = defaultdict(int)
| 13| dict_col = defaultdict(int)
| 14| for j in range(len(board[0])):
| 15| value_row = board[i][j]
| 16| value_col = board[j][i]
| 17| if not value_row or value_col == 0:
| 18| return False
| 19| if value_row in dict_row:
| 20| return False
| 21| else:
| 22| dict_row[value_row] += 1
| 23|
| 24| if value_col in dict_col:
| 25| return False
| 26| else:
| 27| dict_col[value_col] += 1
| 28|
| 29| for i in range(3):
| 30| for j in range(3):
| 31| grid_add = 0
| 32| for k in range(3):
| 33| for l in range(3):
| 34| grid_add += board[i*3+k][j*3+l]
| 35| if grid_add != 45:
| 36| return False
| 37| return True
| 38|
| 39|
| 40| # Without hash-table/dict
| 41| def valid_solution(board):
| 42| correct = [1, 2, 3, 4, 5, 6, 7, 8, 9]
| 43| # check rows
| 44| for row in board:
| 45| if sorted(row) != correct:
| 46| return False
| 47|
| 48| # check columns
| 49| for column in zip(*board):
| 50| if sorted(column) != correct:
| 51| return False
| 52|
| 53| # check regions
| 54| for i in range(3):
| 55| for j in range(3):
| 56| region = []
| 57| for line in board[i*3:(i+1)*3]:
| 58| region += line[j*3:(j+1)*3]
| 59|
| 60| if sorted(region) != correct:
| 61| return False
| 62|
| 63| # if everything correct
| 64| return True
| 65|
| 66|
| 67| # Using set
| 68| def valid_solution_set (board):
| 69| valid = set(range(1, 10))
| 70|
| 71| for row in board:
| 72| if set(row) != valid:
| 73| return False
| 74|
| 75| for col in [[row[i] for row in board] for i in range(9)]:
| 76| if set(col) != valid:
| 77| return False
| 78|
| 79| for x in range(3):
| 80| for y in range(3):
| 81| if set(sum([row[x*3:(x+1)*3] for row in board[y*3:(y+1)*3]], [])) != valid:
| 82| return False
| 83|
| 84| return True
#========================================================================================================================
# :: matrix/copy_transform.py ::
#========================================================================================================================
| 1| def rotate_clockwise(matrix):
| 2| new = []
| 3| for row in reversed(matrix):
| 4| for i, elem in enumerate(row):
| 5| try:
| 6| new[i].append(elem)
| 7| except IndexError:
| 8| new.insert(i, [])
| 9| new[i].append(elem)
| 10| return new
| 11|
| 12| def rotate_counterclockwise(matrix):
| 13| new = []
| 14| for row in matrix:
| 15| for i, elem in enumerate(reversed(row)):
| 16| try:
| 17| new[i].append(elem)
| 18| except IndexError:
| 19| new.insert(i, [])
| 20| new[i].append(elem)
| 21| return new
| 22|
| 23| def top_left_invert(matrix):
| 24| new = []
| 25| for row in matrix:
| 26| for i, elem in enumerate(row):
| 27| try:
| 28| new[i].append(elem)
| 29| except IndexError:
| 30| new.insert(i, [])
| 31| new[i].append(elem)
| 32| return new
| 33|
| 34| def bottom_left_invert(matrix):
| 35| new = []
| 36| for row in reversed(matrix):
| 37| for i, elem in enumerate(reversed(row)):
| 38| try:
| 39| new[i].append(elem)
| 40| except IndexError:
| 41| new.insert(i, [])
| 42| new[i].append(elem)
| 43| return new
| 44|
| 45| if __name__ == '__main__':
| 46| def print_matrix(matrix, name):
| 47| print('{}:\n['.format(name))
| 48| for row in matrix:
| 49| print(' {}'.format(row))
| 50| print(']\n')
| 51|
| 52| matrix = [
| 53| [1, 2, 3],
| 54| [4, 5, 6],
| 55| [7, 8, 9],
| 56| ]
| 57|
| 58| print_matrix(matrix, 'initial')
| 59| print_matrix(rotate_clockwise(matrix), 'clockwise')
| 60| print_matrix(rotate_counterclockwise(matrix), 'counterclockwise')
| 61| print_matrix(top_left_invert(matrix), 'top left invert')
| 62| print_matrix(bottom_left_invert(matrix), 'bottom left invert')
@iamaziz
Copy link
Author

iamaziz commented Sep 21, 2019

Script to produce the above book from source files:

from glob import glob

files = [f for f in glob("**/*.py", recursive=True) if not f.startswith("__")]
output = "code_book.py"

with open(output, "w") as out:
    for f in files:
        out.write(f'\n#{"="*120}\n#\t\t:: {f} ::\n#{"="*120}\n')
        with open(f, "r") as r:
            lines = r.readlines()
            [out.write(f"|{i:>3}| {line}") for i, line in enumerate(lines,1)]
            out.write(f"\n")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment