import copy import typing import stdlib def yep(s: str) -> bool: return s.strip().lower().startswith("y") def guess_bisect_repl(lower: int, upper: int) -> int: mid = lower + ((upper - lower) // 2) if yep(input(f"is it {mid}? ")): return mid if yep(input(f"higher than {mid}? ")): return guess_bisect_repl(mid, upper) return guess_bisect_repl(lower, mid) def find_sqrt_ish(n: int) -> int: return int(find_bisect(0, n, gen_sqrt_check(n))) def gen_sqrt_check(n: int) -> typing.Callable[[float], int]: def check(mid: float) -> int: mid_sq: float = mid * mid if mid_sq == n: return 0 if mid_sq < n: return 1 return -1 return check def find_bisect( lower: float, upper: float, check: typing.Callable[[float], int] ) -> float: mid: float = lower + ((upper - lower) / 2) print(f"lower={lower} mid={mid} upper={upper}") if mid == lower or mid == upper or check(mid) == 0: return mid if check(mid) == 1: return find_bisect(mid, upper, check) return find_bisect(lower, mid, check) def cartesian_path(p0: tuple[int, int], p1: tuple[int, int]) -> list[tuple[int, int]]: path: list[tuple[int, int]] = [] if p0 < p1: for i in range(p0[1], p1[1]): path.append((i, p0[0])) for i in range(p0[0], p1[0]): path.append((p1[1], i)) else: for i in range(p0[1], p1[1] - 1, -1): path.append((i, p0[0])) for i in range(p0[0] - 1, p1[0], -1): path.append((p1[1], i)) return path def gen_matrix(width: int, height: int) -> list[list[int]]: return [list(range(width)) for _ in range(height)] class MinStack: def __init__(self): self._v: list[int] = [] self._min: list[int] = [] def push(self, val: int) -> None: self._v.append(val) self._min.append(min(val, self._min[-1] if self._min else val)) def pop(self) -> None: self._v.pop(-1) self._min.pop(-1) def top(self) -> int: return self._v[-1] def getMin(self) -> int: # no qa return self._min[-1] def linked_list_to_list(head: stdlib.LinkedListNode | None) -> list[int]: seen: set[int] = set() ret: list[int] = [] while head is not None: if hash(head) in seen: return ret seen.add(hash(head)) ret.append(head.val) head = head.next return ret def sort_linked_list(head: stdlib.LinkedListNode | None) -> stdlib.LinkedListNode | None: by_val: list[tuple[int, stdlib.LinkedListNode]] = [] ret: stdlib.LinkedListNode | None = None while head is not None: by_val.append((head.val, head)) head = head.next cur = ret for _, node in sorted(by_val, key=lambda v: v[0]): if cur is None: cur = ret = node continue cur.next = node cur = cur.next if cur is not None: cur.next = None return ret def connect_binary_tree_right( root: stdlib.ConnectableBinaryTreeNode | None, ) -> tuple[stdlib.ConnectableBinaryTreeNode | None, list[int | None]]: if root is None: return None, [] by_level = binary_tree_by_level(copy.deepcopy(root)) by_level = typing.cast(dict[int, list[stdlib.ConnectableBinaryTreeNode]], by_level) serialized: list[int | None] = [] print("") if 0 not in by_level or len(by_level[0]) == 0: return None, [] connected_root = by_level[0][0] for level, nodes in sorted(by_level.items(), key=lambda p: p[0]): for i in range(len(nodes)): serialized.append(nodes[i].val) if len(nodes) > i + 1: print(f"{'-' * level}> connecting {nodes[i].val} -> {nodes[i + 1].val}") nodes[i].next = nodes[i + 1] serialized.append(None) return connected_root, serialized def binary_tree_by_level( root: stdlib.BinaryTreeNode, ) -> dict[int, list[stdlib.BinaryTreeNode]]: combined: dict[int, list[stdlib.BinaryTreeNode]] = {} for path in collect_binary_tree_levels(0, root): level, node = path combined.setdefault(level, []) combined[level].insert(0, node) return combined def collect_binary_tree_levels( level: int, node: stdlib.BinaryTreeNode | None ) -> typing.Iterator[tuple[int, stdlib.BinaryTreeNode]]: if node is None: return yield (level, node) yield from collect_binary_tree_levels(level + 1, node.right) yield from collect_binary_tree_levels(level + 1, node.left) def sum_binary_tree_path_ints(root: stdlib.BinaryTreeNode | None) -> int: path_ints: list[int] = [] for path in collect_binary_tree_paths(root): path_ints.append(int("".join([str(node.val) for node in path]))) return sum(path_ints) def binary_tree_paths_as_lists( paths: list[list[stdlib.BinaryTreeNode]], ) -> list[list[int]]: paths_vals: list[list[int]] = [] for path in paths: paths_vals.append([node.val for node in path]) return paths_vals def collect_binary_tree_paths( node: stdlib.BinaryTreeNode | None, ) -> typing.Iterator[list[stdlib.BinaryTreeNode]]: if node is None: return if node.right is None and node.left is None: yield [node] return if node.right is not None: for path in collect_binary_tree_paths(node.right): yield [node] + path if node.left is not None: for path in collect_binary_tree_paths(node.left): yield [node] + path def binary_tree_from_list(inlist: list[int | None]) -> stdlib.BinaryTreeNode | None: if len(inlist) == 0: return None nodes: list[stdlib.BinaryTreeNode | None] = [ typing.cast(stdlib.BinaryTreeNode | None, stdlib.TreeNode.from_int(i)) for i in inlist ] nodes_copy = nodes[::-1] root = nodes_copy.pop() for node in nodes: if node is None: continue if len(nodes_copy) == 0: break node.left = nodes_copy.pop() if len(nodes_copy) > 0: node.right = nodes_copy.pop() return root def binary_tree_from_preorder_inorder( preorder: list[int], inorder: list[int] ) -> stdlib.BinaryTreeNode | None: preorder_reversed = preorder[::-1] def subtree(left: list[int], right: list[int]) -> stdlib.BinaryTreeNode: root: stdlib.BinaryTreeNode = typing.cast( stdlib.BinaryTreeNode, stdlib.TreeNode(preorder_reversed.pop()) ) if len(left) > 1: split_pos = left.index(preorder_reversed[-1]) root.left = subtree(left[:split_pos], left[split_pos + 1 :]) elif len(left) == 1: preorder_reversed.remove(left[0]) root.left = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(left[0])) if len(right) > 1: split_pos = right.index(preorder_reversed[-1]) root.right = subtree(right[:split_pos], right[split_pos + 1 :]) elif len(right) == 1: preorder_reversed.remove(right[0]) root.right = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(right[0])) return root split_pos = inorder.index(preorder[0]) return subtree(inorder[:split_pos], inorder[split_pos + 1 :]) class JumpSpace(typing.NamedTuple): pos: int val: int moves: list["JumpSpace"] @classmethod def from_board( cls, pos: int = 0, board: typing.Iterable[int] = () ) -> typing.Optional["JumpSpace"]: board = list(board) if len(board) == 0: return None space = cls(pos, board[pos], []) space.collect(board) return space def collect(self, board: list[int]) -> None: del self.moves[:] if self.pos > len(board) or len(board) == 0: return for n in range(self.pos + 1, self.pos + self.val + 1): if n >= len(board): break self.moves.append(typing.cast(JumpSpace, JumpSpace.from_board(n, board))) def jump_paths(self) -> list[list[int]]: ret: list[list[int]] = [[self.pos]] for next_space in self.moves: for path in next_space.jump_paths(): ret.append([self.pos] + path) return ret def collect_complete_jump_paths_from_board(board: list[int]) -> list[list[int]]: return [ p for p in collect_jump_paths_from_board(board) if len(p) > 0 and p[-1] >= len(board) - 1 ] def collect_jump_paths_from_board(board: list[int]) -> list[list[int]]: space = JumpSpace.from_board(0, board) if space is None: return [] return space.jump_paths() # NOTE: the expensive way goes like this # complete_paths = collect_complete_jump_paths_from_board(board) # if len(complete_paths) == 0: # return -1 # return min([len(p) - 1 for p in complete_paths]) def count_min_jumps_from_board(board: list[int]) -> int: return len(collect_min_jumps_from_board(board)) def collect_min_jumps_from_board(board: list[int]) -> list[int]: if len(board) < 3: return list(range(1, len(board))) jumps: list[int] = [] range_begin: int = 0 val = board[range_begin] range_end: int = range_begin + val + 1 while range_end < len(board): potential_jumps = board[range_begin:range_end] scored_jumps = [ (val + range_begin + i, val, range_begin + i) for i, val in enumerate(potential_jumps) ] _, val, space = max(scored_jumps) jumps.append(space) range_begin = space range_end = range_begin + val + 1 return jumps + [len(board) - 1]