Leetcode

Minimum Operations to Make All Array Elements Equal - LeetCode

minimum operations to make all array elements equal

Minimum Operations to Make All Array Elements Equal - LeetCode

Solution
from typing import List
from itertools import groupby
import bisect

class Solution:
    def minOperations(self, nums: List[int], queries: List[int]) -> List[int]:
        ans = []
        n = len(nums)
        nums = sorted(nums)

        cum_sum = [0]
        so_far = 0
        for num in nums:
            so_far += num
            cum_sum.append(so_far)

        for q in queries:
            """
            nums =       [1, 3, 6,  8]
            cum_sum = [0, 1, 4, 10, 18]
            query = 5
            left_idx = 2

            left_total : 5 * 2 - cum_sum[2] = 10 - 4 = 6
            right_total : cum_sum[-1] - cum_sum[left_idx] - 5 * (n - left_idx) = 18 - 4 - 5 * (4-2) = 14 - 10 = 4
            total : 6 + 4 = 10
            """

            # left total : q * left_idx - cum_sum[left_idx]
            # right todal : cum_sum[n+1] - cum_sum[left_idx+1] - q * (n - left_idx)

            # find the mid point
            # left_idx = bisect.bisect_left(nums, q)
            l, r = 0, n-1
            while l <= r:
                mid = (l+r) // 2
                mid_num = nums[mid]
                if mid_num <= q:
                    l = mid + 1
                else:
                    r = mid - 1
            left_idx = l
            left_total = q * left_idx - cum_sum[left_idx]
            right_total = cum_sum[-1] - cum_sum[left_idx] - q * (n - left_idx)
            tot_diff = left_total + right_total
            ans.append(tot_diff)


        return ans


print(Solution().minOperations(nums = [3,1,6,8], queries = [1,5])) # [14,10]
print(Solution().minOperations(nums = [2,9,6,3], queries = [10])) # [20]

shopping offers

Shopping Offers - LeetCode

Solution
from functools import lru_cache
from collections import Counter, defaultdict
from typing import List
from operator import mul, sub
import numpy as np

class Solution:
    def shoppingOffers(self, price: List[int], specials: List[List[int]], needs: List[int]) -> int:
        @lru_cache(None)
        def dfs(needs):
            cost = sum(map(mul, needs, price))          # cost with no specials applied
            for special in specials:
                # specPrice = special.pop()
                offer, special_price = special[:-1], special[-1]
                tmp = tuple(map(sub, needs, offer))   # reset need if special applied

                if tmp and min(tmp) < 0: continue               # special cannot be applied

                cost = min(cost, min(cost, dfs(tmp)) + special_price)  # id special best buy?
            return cost                                 # return best cost for needs
        return dfs(tuple(needs))

print(Solution().shoppingOffers(price = [2,5], specials = [[3,0,5],[1,2,10]], needs = [3,2]))
print(Solution().shoppingOffers(price = [2,3,4], specials = [[1,1,0,4],[2,2,1,9]], needs = [1,2,1]))
print(Solution().shoppingOffers(price = [3,4], specials = [[1,2,3],[1,2,5]], needs = [2,2]))

additive number

sol#1

Solution
class Solution:
    def isAdditiveNumber(self, num: str) -> bool:
        seq = []
        def backtrack(num: str, cur: List[int]):
            if not num:
                return len(cur) >= 3

            for i in range(1,len(num)+1):
                if i > 1 and num[0] == "0": break
                if len(cur) < 2 or int(num[:i]) == cur[-1] + cur[-2]:
                    seq.append(int(num[:i]))
                    if backtrack(num[i:],seq): return True
                    seq.remove(int(num[:i]))

            return False

        return backtrack(num,[])

sol#2

Solution
from functools import lru_cache

class Solution:
    def isAdditiveNumber(self, num: str) -> bool:

        slen = len(num)
        ans = False

        @lru_cache
        def recur(n1, n2, start_idx):
            nonlocal ans
            # print(f"{n1, n2, num[start_idx:] = }")
            if start_idx == slen:
                ans = True
                return True

            min_len = len(str(n1 + n2))
            for end_idx in range(start_idx+min_len, slen+1):
                next_num = int(num[start_idx:end_idx])
                if len(str(next_num)) != end_idx - start_idx:
                    continue

                if n1 + n2 != next_num:
                    continue

                recur(n2, next_num, end_idx)
                # if not recur(n2, next_num, end_idx):
                #     return False

            return False

        for n1_end_idx in range(1, slen):
            for n2_end_idx in range(n1_end_idx+1, slen):
                n1 = int(num[:n1_end_idx])
                if len(str(n1)) != n1_end_idx:
                    continue

                n2 = int(num[n1_end_idx:n2_end_idx])
                if len(str(n2)) != n2_end_idx - n1_end_idx:
                    continue

                if not ans:
                    recur(n1, n2, n2_end_idx)
        return ans

Count Good Numbers

Count Good Numbers - LeetCode

Solution
from collections import deque
from math import ceil
from typing import List

class Solution:
    def countGoodNumbers(self, n: int) -> int:
        MOD = 10**9 + 7
        return (pow(5, (n + 1) // 2, MOD) * pow(4, n // 2, MOD)) % MOD


print(Solution().countGoodNumbers(110000000000))

word pattern 2

Word Pattern II - LeetCode

sol#1

Solution
from functools import lru_cache

class Solution:
    def wordPatternMatch(self, pattern: str, target_str: str) -> bool:

        mapping = {}
        slen = len(target_str)
        pattern_len = len(pattern)

        def backtrack(pattern_i, s_idx):
            if s_idx == slen and pattern_i == pattern_len:
                return True
            if s_idx == slen or pattern_i == pattern_len:
                return False

            cur_pattern = pattern[pattern_i]
            if cur_pattern in mapping:
                pattern_str = mapping[cur_pattern]
                if not target_str[s_idx:].startswith(pattern_str):
                    return False

                return backtrack(pattern_i+1, s_idx + len(pattern_str))

            for s_end_idx in range(s_idx+1, slen+1):
                cur_pattern_str = target_str[s_idx:s_end_idx]
                if cur_pattern_str in list(mapping.values()):
                    continue

                mapping[cur_pattern] = cur_pattern_str
                if backtrack(pattern_i+1, s_idx+len(cur_pattern_str)):
                    return True
                del mapping[cur_pattern]

            return False

        return backtrack(0, 0)

print(Solution().wordPatternMatch("abab", "redblueredblue"))
print(Solution().wordPatternMatch("aaaa", "asdasdasdasd"))
print(Solution().wordPatternMatch("aabb", "xyzabcxzyabc"))

Matchsticks to square

Matchsticks to Square - LeetCode

sol#1

Solution
from typing import List
from collections import Counter
from functools import lru_cache

class Solution:
    def makesquare(self, matchsticks: List[int]) -> bool:
        ss = matchsticks
        if sum(ss) % 4 != 0:
            return False
        width = sum(ss) // 4
        slen = len(ss)
        used = [False for _ in range(slen)]

        c = Counter(ss)
        clen = len(c)
        ckeys = sorted(list(c.keys()))

        def backtrack(side_cnt, need_val, last_idx):

            # finish condition
            if side_cnt == 0:
                return True

            # deeper backtrack
            if need_val == 0:
                return backtrack(side_cnt - 1, width, 0)

            for i in range(last_idx, clen):
                key = ckeys[i]
                if c[key] == 0: continue
                if key > need_val: break

                # try
                c[key] -= 1

                # deeper backtrack
                if backtrack(side_cnt, need_val - key, i):
                    return True

                # cancel
                c[key] += 1

            return False

        return backtrack(4, width, 0)

print(Solution().makesquare([1,1,2,2,2])) # True
print(Solution().makesquare([5,5,5,5,4,4,4,4,3,3,3,3])) # True
print(Solution().makesquare([4,4,4,4,7,7,7,7,1,1,1,1,4,4,8])) # True
print(Solution().makesquare([4,13,1,1,14,15,1,3,13,1,3,5,2,8,12])) # False

N-Queens

sol#1

Solution
from typing import List
from collections import Counter
from functools import lru_cache
from copy import deepcopy

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        ans = []
        def col_avail(cur_sol, coord):
            y, x = coord

            # y,x increase
            for i in range(1, n):
                if y+i > len(cur_sol) -1 or x+i > n-1:
                    break

                if cur_sol[y+i][x+i] == 'Q':
                    return False

            # y,x decrease
            for i in range(1, n):
                if y-i < 0 or x-i < 0:
                    break
                if not (y-i < len(cur_sol)):
                    continue
                if cur_sol[y-i][x-i] == 'Q':
                    return False

            # y increase, x decrease
            for i in range(1, n):
                if y+i > len(cur_sol)-1 or x-i < 0:
                    break
                if cur_sol[y+i][x-i] == 'Q':
                    return False

            # y decrease, x increase
            for i in range(1, n):
                if y-i < 0 or x+i > n-1:
                    break
                if not (y-i < len(cur_sol)):
                    continue
                if cur_sol[y-i][x+i] == 'Q':
                    return False

            return True

        def bt(cur_sol, cur_row, poss_col):
            if cur_row == n:
                ans.append(cur_sol)
                return True

            if not poss_col:
                return False

            for col in poss_col:
                # make new poss_col for next
                if not col_avail(cur_sol, (cur_row, col)):
                    continue
                new_row = "." * (col) + "Q" + "." * (n-col-1)

                bt(cur_sol + [new_row], cur_row+1, poss_col - set([col]))
            return False

        bt([], 0, set(range(0, n)))
        return ans

#     n = 4
# Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]

# print(Solution().solveNQueens(4))
# print(Solution().solveNQueens(1))
# print(Solution().solveNQueens(8))
print(Solution().solveNQueens(9))

Number of Spaces Cleaning Robot Cleaned

Number of Spaces Cleaning Robot Cleaned - LeetCode

Solution

from typing import List
import numpy as np
class Solution:
    def numberOfCleanRooms(self, room: List[List[int]]) -> int:

        H, W = len(room), len(room[0])

        visited = [[0 for _ in range(W)] for _ in range(H)]

        dys = [0, 1, 0, -1]
        dxs = [1, 0, -1, 0]
        ans = 0

        def recur(y, x, d):
            if visited[y][x] > 4:
                return

            if visited[y][x] == 0:
                nonlocal ans
                ans += 1
            visited[y][x] += 1

            ny, nx = y + dys[d], x + dxs[d]

            # in room
            if (0 <= ny < H and 0 <= nx < W) and room[ny][nx] == 0:
                recur(ny, nx, d)
                return

            for i in range(1, 5):
                next_d = (d + i) % 4
                ny, nx = y  + dys[next_d], x + dxs[next_d]
                if (0 <= ny < H and 0 <= nx < W) and room[ny][nx] == 0:
                    recur(ny, nx, next_d)
                    return

        recur(0, 0, 0)
        return ans



# print(Solution().numberOfCleanRooms(room = [[0,0,0],[1,1,0],[0,0,0]])) # 7
# print(Solution().numberOfCleanRooms(room = [[0,1,0],[1,0,0],[0,0,0]])) # 1
# print(Solution().numberOfCleanRooms(room = [[0,0,0],[0,0,0],[0,0,0]])) # 8

# room = [[0,0,0,1],[0,1,0,1],[1,0,0,0]]
# print(np.array(room))
# print(Solution().numberOfCleanRooms(room)) # 7

number of squareful arrays

Number of Squareful Arrays - LeetCode

Solution
from typing import List
from collections import Counter
import math

class Solution:
    def numSquarefulPerms(self, nums: List[int]) -> int:
        def is_valid(a, b):
             return int(math.sqrt(a+b)) ** 2 == a + b

        ans = 0
        c = Counter(nums)
        lnum = len(nums)
        nums = list(c.keys())

        def recur(l):
            # print(l)
            if len(l) == lnum:
                nonlocal ans
                ans += 1
                return

            for num in nums:
                if c[num] == 0: continue

                c[num] -= 1 # update state (for backtracking)
                if not l:
                    recur(l + [num])
                else:
                    if is_valid(l[-1], num):
                        # if this recursion ends up
                        # successfully, we only visit this path once
                        recur(l + [num])
                c[num] += 1 # backtrack (return to previous state)
        recur([])

        return ans

# print(Solution().numSquarefulPerms([1,17,8]))
# print(Solution().numSquarefulPerms([1,1]))
print(Solution().numSquarefulPerms([2,2,2,2,2,2,2,2,2,2,2,2]))

gcd(greatest common divisor), HCF(highest common factor)

making least common multiple with gcd

Solution
from math import gcd
def nlcm(num): # least common multiple
    answer = num[0]
    for n in num:
        answer = n * answer // int(gcd(n, answer))

    return answer

# 아래는 테스트로 출력해 보기 위한 코드입니다.
print(nlcm([2,6,8,14]));
# {2:3, 3:1, 7:1} => 8 * 3 * 7 = 168
168

count nodes equal to average of subtree

Count Nodes Equal to Average of Subtree - LeetCode

Solution
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def averageOfSubtree(self, root: Optional[TreeNode]) -> int:

        ans = 0

        def recur(node):

            if not node:
                return (0, 0)

            left_tot, left_cnt = recur(node.left)
            right_tot, right_cnt = recur(node.right)

            cnt = left_cnt + right_cnt + 1
            tot = left_tot + right_tot + node.val

            nonlocal ans
            if tot // cnt == node.val:
                ans += 1

            return (tot, cnt)

        recur(root)
        return ans

BFS with two stacks(not queue) // normally BFS use queue

Solution
def test():
    mat = [[0] * 5 for _ in range(5)]
    seen = [[False] * 5 for _ in range(5)]
    start = (2, 2)
    st1 = list()
    st2 = list()
    st1.append(start)
    dy = [0, 0, 1, -1]
    dx = [-1, 1, 0, 0]
    depth = 1

    print()
    while (st1 or st2):
        print(f'depth: {depth}, stack1: {st1}, stack2: {st2}')
        stlen = len(st1) if depth % 2 == 1 else len(st2)
        while (stlen):
            stlen -= 1

            (y,x) = st1.pop() if depth % 2 == 1 else st2.pop()
            seen[y][x] = True
            mat[y][x] = depth

            for i in range(4):
                ny = y + dy[i]
                nx = x + dx[i]

                if ny < 0 or ny > 4 or nx < 0 or nx > 4: continue
                if seen[ny][nx]: continue

                seen[ny][nx] = True

                if depth % 2 == 1: st2.append((ny,nx))
                else: st1.append((ny,nx))
        depth += 1
    print()
    for i in mat:
        print(i)
    print()
test()

depth: 1, stack1: [(2, 2)], stack2: []
depth: 2, stack1: [], stack2: [(2, 1), (2, 3), (3, 2), (1, 2)]
depth: 3, stack1: [(1, 1), (1, 3), (0, 2), (3, 1), (3, 3), (4, 2), (2, 4), (2, 0)], stack2: []
depth: 4, stack1: [], stack2: [(3, 0), (1, 0), (3, 4), (1, 4), (4, 1), (4, 3), (0, 1), (0, 3)]
depth: 5, stack1: [(0, 4), (0, 0), (4, 4), (4, 0)], stack2: []

[5, 4, 3, 4, 5]
[4, 3, 2, 3, 4]
[3, 2, 1, 2, 3]
[4, 3, 2, 3, 4]
[5, 4, 3, 4, 5]

trie / tree

Implement Trie (Prefix Tree) - LeetCode

sol#1

Solution
from collections import defaultdict
class Trie:

    def __init__(self):
        self.d = defaultdict(dict)

    def insert(self, word: str) -> None:
        cursor = self.d
        for c in word:
            if c in cursor:
                cursor = cursor[c]
            else:
                cursor[c] = defaultdict(dict)
                cursor = cursor[c]
        cursor['end'] = True

    def search(self, word: str) -> bool:

        cursor = self.d
        for c in word:
            if c in cursor:
                cursor = cursor[c]
            else:
                return False

        if 'end' in cursor:
            return True
        else:
            return False



    def startsWith(self, prefix: str) -> bool:
        cursor = self.d
        for c in prefix:
            if c in cursor:
                cursor = cursor[c]
            else:
                return False
        return True


# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)

sol#2

Solution
class TrieNode:
    def __init__(self, char = ""):
        self.char = char
        self.children = {}
        self.is_end = False
        # self.counter = 0


class Trie:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        """
        node = self.root
        for char in word:
            if char in node.children:
                node = node.children[char]
            else:
                new_node = TrieNode(char)
                node.children[char] = new_node
                node = new_node
        node.is_end = True
        # node.counter += 1


    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        """
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]

        # Reached at the end of word
        # return True if word is present, i.e is_end = True else False
        return node.is_end


    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True

binary tree maximum path sum

Binary Tree Maximum Path Sum - LeetCode

Solution
# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def maxPathSum(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        def max_gain(node):
            nonlocal max_sum
            if not node:
                return 0

            # max sum on the left and right sub-trees of node
            left_gain = max(max_gain(node.left), 0) # this handles negative sum
            right_gain = max(max_gain(node.right), 0) # this handles negative sum

            # the price to start a new path where `node` is a highest node
            price_newpath = node.val + left_gain + right_gain

            # update max_sum if it's better to start a new path
            max_sum = max(max_sum, price_newpath)

            # for recursion :
            # return the max gain if continue the same path
            return node.val + max(left_gain, right_gain)

        max_sum = float('-inf')
        max_gain(root)
        return max_sum

# Input: root = [1,2,3]
# Output: 6
# Explanation: The optimal path is 2 -> 1 -> 3 with a path sum of 2 + 1 + 3 = 6.
def test1():

    n1 = TreeNode(1)
    n2 = TreeNode(2)
    n3 = TreeNode(3)

    n1.left = n2
    n1.right = n3

    assert 6 == Solution().maxPathSum(n1)

# Example 2:
# Input: root = [-10,9,20,null,null,15,7]
# Output: 42
# Explanation: The optimal path is 15 -> 20 -> 7 with a path sum of 15 + 20 + 7 = 42.
def test2():
    n1 = TreeNode(-10)
    n2 = TreeNode(9)
    n3 = TreeNode(20)
    n4 = TreeNode(15)
    n5 = TreeNode(7)

    n1.left = n2
    n1.right = n3
    n3.left = n4
    n3.right = n5

    assert 42 == Solution().maxPathSum(n1)


test1()
test2()

edit distance

brute force way

Solution
class Solution:

    def get_dist(self, s1, s2):
        # if s1 is empty string -> only choice is to insert all characters in s2
        if s1 == "" or s2 == "":
            return max(len(s2), len(s1))

        if s1[0] == s2[0]:
            return  self.get_dist(s1[1:], s2[1:])

        return min(
            1 + self.get_dist(s1, s2[1:]), # case 1 : insert to s1
            1 + self.get_dist(s1[1:], s2), # case 2 : delete first character of s1
            1 + self.get_dist(s1[1:], s2[1:]), # case 3 : replace a character in s1
        )

    def minDistance(self, word1: str, word2: str) -> int:
        # make word2 longer string always
        if len(word2) < len(word1):
            word1, word2 = word2, word1
        return self.get_dist(word1, word2)

dp solution

Solution
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        s1, s2 = word1, word2
        height, width = len(s1), len(s2)
        if not height or not width: return max(height, width)

        dp = [[0] * (width + 1) for _ in range(height + 1)]
        for x in range(width + 1):
            dp[height][x] = width - x
        for y in range(height + 1):
            dp[y][width] = height - y

        for y in range(height-1, -1, -1):
            for x in range(width-1, -1, -1):
                if s1[y] == s2[x]:
                    dp[y][x] = dp[y+1][x+1]
                else:
                    dp[y][x] = min(dp[y][x + 1], dp[y + 1][x], dp[y + 1][x + 1]) + 1
        return dp[0][0]

rand7 -> rand10

Implement Rand10() Using Rand7() - LeetCode

sol#1

Solution
# The rand7() API is already defined for you.
# def rand7():
# @return a random integer in the range 1 to 7


class Solution:

    def rand10(self):
        """
        :rtype: int
        """

        num = rand7() * 7  + rand7()
        num -= 7 # range [1, 49]

        if num > 40:
            return self.rand10()

        # range [1, 40]

        if num % 10 == 0:
            return 10
        else:
            return num % 10

sol#2

Solution
class Solution:
    def half(self):
        n = rand7()
        if n == 7:
            return self.half()
        else:
            if n < 4:
                return 1
            else: # n > 4
                return 0

    def rand10(self):
        """
        :rtype: int
        """

        bit1 = self.half()
        bit2 = self.half()
        bit3 = self.half()
        bit4 = self.half()

        num = bit1 + 2 * bit2 + 4 * bit3 + 8 * bit4

        if not (1 <= num <= 10):
            return self.rand10()
        return num

# The rand7() API is already defined for you.
# def rand7():
# @return a random integer in the range 1 to 7

minimum swaps to group all 1s together

Minimum Swaps to Group All 1&#x27;s Together - LeetCode

Solution
from collections import deque
from typing import List


class Solution:
    def minSwaps(self, data: List[int]) -> int:
        num_data = len(data)
        window_size = sum(data)

        if window_size == 0:
            return 0

        # precalculate number of zeros [0, i) (from idx 0 to idx i)
        # idx2zeros[1] : data[:1] 사이의 0의 개수
        # idx2zeros[num_data] : data[:num_data] 사이의 0의 개수
        idx2zeros = [0]
        zcnt = 0
        for num in data:
            if num == 0:
                zcnt += 1
            idx2zeros.append(zcnt)

        # have default value
        min_zeros_so_far = idx2zeros[window_size] - idx2zeros[0]
        for i in range(num_data - window_size + 1):

            # range : [i, i + window_size)
            cur_range_zeros = idx2zeros[i+window_size] - idx2zeros[i]

            # update min_zeros_so_far if we find better solution
            min_zeros_so_far = min(min_zeros_so_far, cur_range_zeros)
        return min_zeros_so_far


print(Solution().minSwaps([1, 0, 1, 0, 1]))
print(Solution().minSwaps([0, 0, 0, 1, 0]))
print(Solution().minSwaps([1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1]))

Robot Bounded In Circle

Robot Bounded In Circle - LeetCode

Solution
class Solution:
    def isRobotBounded(self, instructions: str) -> bool:
        # N E S W
        dys = [1, 0, -1, 0]
        dxs = [0, 1, 0, -1]

        def change_dir(direction, c):
            if c == "L":
                direction = (direction - 1) % 4
            else:
                direction = (direction + 1) % 4
            return direction
        y, x = 0, 0
        direction = 0
        dy, dx = dys[direction], dxs[direction]
        for inst in instructions * 4:
            if inst == 'G':
                y += dy
                x += dx
            else:
                direction = change_dir(direction, inst)
                dy, dx = dys[direction], dxs[direction]
        return True if y == 0 and x == 0  else False

Maximum Subarray Min-Product

Maximum Subarray Min-Product - LeetCode

sol#1

Solution
from collections import Counter, deque
from math import ceil
from typing import List


class Solution:
    def maxSumMinProduct(self, nums: List[int]) -> int:
        mod = 10**9 + 7
        prefix_sum = [0]
        stack = [-1]
        nums.append(0) # by adding smallest value (0) to the end of
                       # the nums, when we iterate nums, last element,
                       # the while loop will run for sure.
        max_so_far = 0

        for i in range(len(nums)):
            # nums increasing -> min value stay same -> don't need to pop
            # nums decreasing -> min value change -> pop and update min_val and update max_so_far
            while nums[stack[-1]] > nums[i]: # we don't do anything
                                             # until nums is
                                             # increasing but if it's
                                             # decreasing, we pop the
                                             # stack
                min_val = nums[stack.pop()]
                range_sum = prefix_sum[i] - prefix_sum[stack[-1] + 1]
                max_so_far = max(max_so_far, range_sum * min_val)
            stack.append(i)
            prefix_sum.append(prefix_sum[-1] + nums[i])
        return max_so_far % mod


print(Solution().maxSumMinProduct([1, 2, 3, 2]))
# print(Solution().maxSumMinProduct([2, 3, 3, 1, 2]))

Inorder Successor in BST

Solution
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:

        ans = None

        while root:
            if p.val >= root.val: # if p.val is higher or equal to root.val -> go right
                root = root.right
            else: # if p.val is smaller than root.val -> go left and current node as successor
                ans = root
                root = root.left

        return ans

Find the Duplicate Number

Solution
class Solution:
    def findDuplicate(self, nums):
        # Find the intersection point of the two runners.
        tortoise = hare = nums[0]
        while True:
            tortoise = nums[tortoise]
            hare = nums[nums[hare]]
            if tortoise == hare:
                break

        # Find the "entrance" to the cycle.
        tortoise = nums[0]
        while tortoise != hare:
            tortoise = nums[tortoise]
            hare = nums[hare]

        return hare

Range Frequency Queries

Solution
from collections import defaultdict
class RangeFreqQuery:

    def __init__(self, arr: List[int]):

        self.num2indices = defaultdict(list)
        for idx, num in enumerate(arr):
            self.num2indices[num].append(idx)

    def query(self, left: int, right: int, value: int) -> int:

        # find how many indices are in range [left, right]
        indices = self.num2indices[value]
        if not indices:
            return 0

        llen = len(indices)
        # find lower bound
        # find indices that satisfy the condition but among them, lowest index
        lo, up = 0, llen
        while lo < up:
            mid = (lo+up) // 2
            mid_val = indices[mid]

            if left <= mid_val:
                up = mid
            else:
                lo = mid + 1
        lower_bound = lo

        # find uppper bound
        lo, up = 0, llen
        while lo < up:
            mid = (lo + up) // 2
            mid_val = indices[mid]

            if right < mid_val:
                up = mid
            else:
                lo = mid + 1
        upper_bound = lo
        return upper_bound - lower_bound

# Your RangeFreqQuery object will be instantiated and called as such:
# obj = RangeFreqQuery(arr)
# param_1 = obj.query(left,right,value)

using bisect

Solution
class RangeFreqQuery:
    def __init__(self, arr: List[int]):
        self.l = [[] for _ in range(10001)]
        for i, v in enumerate(arr):
            self.l[v].append(i)
    def query(self, left: int, right: int, v: int) -> int:
        return bisect_right(self.l[v], right) - bisect_left(self.l[v], left)

Find K Pairs with Smallest Sums

Find K Pairs with Smallest Sums - LeetCode

Solution
from collections import Counter
from typing import List
from heapq import *

class Solution:
    def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:

        heap = []

        for i in range(min(k, len(nums1))):
            for j in range(min(k, len(nums2))):
                n1, n2 = nums1[i], nums2[j]
                tot = n1 + n2

                if len(heap) < k:
                    heappush(heap, (-tot, n1, n2))
                else: # already heap size condition is satisfied
                    if tot > -heap[0][0]: # if tot (newly added) is greater than the largest element in the heap
                        break
                    else: # updating heap with the new element (because we found a smaller tot)
                        heappop(heap)
                        heappush(heap, (-tot, n1, n2))

        return [[n1, n2] for _, n1, n2 in heap]


# print(Solution().kSmallestPairs(nums1 = [1,1,2], nums2 = [1,2,3], k = 10))
print(Solution().kSmallestPairs(nums1 = [1,1,2], nums2 = [1,2,3], k = 2))
# print(Solution().kSmallestPairs(nums1 = [1,7,11], nums2 = [2,4,6], k = 3))
# print(Solution().kSmallestPairs(nums1 = [1,2], nums2 = [3], k = 3))

Verify Preorder Sequence in Binary Search Tree

Verify Preorder Sequence in Binary Search Tree - LeetCode

Solution
from collections import Counter
from typing import List
from heapq import *

inf = float('inf')

class Solution:
    def verifyPreorder(self, preorder: List[int]) -> bool:
        gi = 0
        def helper(min_limit, max_limit):
            nonlocal gi
            if gi == len(preorder):
                return True

            root = preorder[gi]
            print(f'{root, min_limit, max_limit = }')
            if not min_limit < root < max_limit:
                return False

            gi += 1
            left = helper(min_limit, root)
            right = helper(root, max_limit)

            return left or right

        return helper(-inf, inf)

class Solution2:
    def verifyPreorder(self, preorder: List[int]) -> bool:
        min_limit = -inf
        stack = []

        for num in preorder:
            print(f'{stack, num = }')
            while stack and stack[-1] < num:
                min_limit = stack.pop()

            print(f'{stack, min_limit = }')
            if num <= min_limit:
                return False

            stack.append(num)

        return True

print(Solution().verifyPreorder([5,2,1,3,6])) # True
print(Solution().verifyPreorder([5,2,6,1,3])) # True
root, min_limit, max_limit = (5, -inf, inf)
root, min_limit, max_limit = (2, -inf, 5)
root, min_limit, max_limit = (1, -inf, 2)
root, min_limit, max_limit = (3, -inf, 1)
root, min_limit, max_limit = (3, 1, 2)
root, min_limit, max_limit = (3, 2, 5)
root, min_limit, max_limit = (6, 2, 3)
root, min_limit, max_limit = (6, 3, 5)
root, min_limit, max_limit = (6, 5, inf)
True
root, min_limit, max_limit = (5, -inf, inf)
root, min_limit, max_limit = (2, -inf, 5)
root, min_limit, max_limit = (6, -inf, 2)
root, min_limit, max_limit = (6, 2, 5)
root, min_limit, max_limit = (6, 5, inf)
root, min_limit, max_limit = (1, 5, 6)
root, min_limit, max_limit = (1, 6, inf)
False