import collections.abc
import copy
import random
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]


def h_index(citations: list[int]) -> int:
    last_qualified = None

    for i, citation_count in enumerate(list(sorted(citations, reverse=True))):
        if citation_count >= i + 1:
            last_qualified = i + 1
        else:
            break

    return last_qualified or 0


class SlowRandomizedSet:
    def __init__(self):
        self._i: set[int] = set()

    def insert(self, val: int) -> bool:
        ok = val not in self._i
        self._i.add(val)
        return ok

    def remove(self, val: int) -> bool:
        if val in self._i:
            self._i.remove(val)
            return True
        return False

    def getRandom(self) -> int:
        return random.choice(list(self._i))


class RandomizedSet:
    def __init__(self):
        self._l: list[int] = []
        self._m: dict[int, int] = {}

    def insert(self, val: int) -> bool:
        if val in self._m:
            return False

        self._m[val] = len(self._l)
        self._l.append(val)

        return True

    def remove(self, val: int) -> bool:
        if val not in self._m:
            return False

        val_loc = self._m[val]
        last_val = self._l[-1]
        self._l[val_loc] = last_val
        self._m[last_val] = val_loc

        self._l.pop()
        self._m.pop(val)

        return True

    def getRandom(self) -> int:
        return random.choice(self._l)


class TrieNode(typing.NamedTuple):
    value: str
    kids: dict[str, "TrieNode"]

    @property
    def is_leaf(self) -> bool:
        return "__self__" in self.kids

    @classmethod
    def leaf(cls) -> "TrieNode":
        return cls("__self__", {})


class Trie:
    def __init__(self):
        self._root_node = TrieNode("", {})

    def insert(self, word: str) -> None:
        if len(word) == 0:
            return

        current_node = self._root_node

        for prefix in [word[: i + 1] for i in range(len(word))]:
            current_node.kids.setdefault(prefix, TrieNode(prefix, {}))
            current_node = current_node.kids[prefix]

        leaf = TrieNode.leaf()
        current_node.kids[leaf.value] = leaf

    def search(self, word: str) -> bool:
        return self._has(word, prefix_ok=False)

    def startsWith(self, prefix: str) -> bool:
        return self._has(prefix, prefix_ok=True)

    def _has(self, word: str, prefix_ok: bool) -> bool:
        if len(word) == 0:
            return True

        reverse_path = [word[: i + 1] for i in range(len(word))][::-1]
        current_node = self._root_node

        while reverse_path and current_node is not None:
            current_node = current_node.kids.get(reverse_path.pop())

        return (
            current_node is not None
            and (current_node.is_leaf or prefix_ok)
            and current_node.value == word
        )


def count_factorial_trailing_zeroes(number: int) -> int:
    divisor: int = 5
    zeroes_count: int = 0

    while divisor <= number:
        zeroes_count += number // divisor
        divisor *= 5

    return zeroes_count


def copy_random_list(
    head: stdlib.ListNodeRandom | None,
) -> stdlib.ListNodeRandom | None:
    if head is None:
        return None

    ordered = []
    cur = head
    while cur is not None:
        ordered.append(cur)
        cur = cur.next

    ordered_copy = [stdlib.ListNodeRandom(entry.val) for entry in ordered]

    hash_idx = {hash(n): i for i, n in enumerate(ordered)}

    for i, entry in enumerate(ordered):
        if i + 1 < len(ordered_copy):
            ordered_copy[i].next = ordered_copy[i + 1]

        if entry.random is not None:
            ordered_copy[i].random = ordered_copy[hash_idx[hash(entry.random)]]

    return ordered_copy[0]


def sum_max_sub_array(nums: list[int]) -> int:
    mmax = last = prev = nums[0]

    for i in range(1, len(nums)):
        prev = nums[i] + last
        last = max(nums[i], prev)
        mmax = max(mmax, last)

    return mmax


def sum_max_sub_array_i(nums: list[int]) -> tuple[int, int]:
    mmax_i: int = 0
    mmax = last = prev = nums[0]

    for i in range(1, len(nums)):
        prev = nums[i] + last
        last = max(nums[i], prev)
        mmax_i = i if last > mmax else mmax_i
        mmax = max(mmax, last)

    return mmax_i, mmax


def sum_max_sub_array_accum(nums: list[int]) -> int:
    accum: list[int] = [nums[0]]

    for i in range(1, len(nums)):
        prev: int = nums[i] + accum[-1]
        accum.append(max(nums[i], prev))

    return max(accum)


def accum_sub_array_maxes(nums: list[int]) -> list[int]:
    accum: list[int] = [nums[0]]

    for i in range(1, len(nums)):
        prev: int = nums[i] + accum[-1]
        accum.append(max(nums[i], prev))

    return accum


def neighborly_node_from_list(inlist: list[list[int]]):
    # Alias "Node" type for leetcode compat
    Node = stdlib.NeighborlyNodeNicely

    if len(inlist) == 0:
        return None

    outlist = [Node(i + 1, []) for i in range(len(inlist))]

    for i in range(len(inlist)):
        outlist[i].neighbors[:] = []

        for neighbor_val in inlist[i]:
            outlist[i].neighbors.append(outlist[neighbor_val - 1])

    return outlist[0]


def neighborly_node_to_list(node) -> list[list[int]]:
    serialized: dict[int, list[int]] = {}

    for cur in traverse_neighborly_node(node, serialized):
        if cur is None:
            break

        serialized[cur.val] = [n.val for n in cur.neighbors]

    return [v for _, v in sorted(serialized.items())]


def traverse_neighborly_node(
    node: stdlib.NeighborlyNodeNicely, memo: collections.abc.Container[int]
) -> typing.Iterator[stdlib.NeighborlyNodeNicely | None]:
    yield node

    if node is None:
        return

    for neighbor in node.neighbors:
        if neighbor.val in memo:
            continue

        yield from traverse_neighborly_node(neighbor, memo)


def find_min_in_rotated_array(nums: list[int]) -> int:
    if nums[0] <= nums[-1]:
        return nums[0]

    if len(nums) <= 3:
        return min(nums)

    if nums[len(nums) // 2] > nums[-1]:
        return find_min_in_rotated_array(nums[len(nums) // 2 :])

    return find_min_in_rotated_array(nums[: (len(nums) // 2) + 1])
