Practice #005: Leetcode Weekly Contest 300

Leetcode: 2325. Decode the Message

The solution consist on following the rules. Keep a pointer to the next unused letter of the alphabet (starting from a) and bumped it after assign to the next non-mapped letter from the key string. After that, iterate over the message decrypting it. Time and space complexity: \(O(n+m)\) where \(n\) is the size of the key and \(m\) is the size of the message.

class Solution:
    def decodeMessage(self, key: str, message: str) -> str:
        sub = {" ": " "}
        next_letter = "a"
        for c in key:
            if c in sub:
                continue
            sub[c] = next_letter
            next_letter = chr(ord(next_letter) + 1)
        ans = ""
        for c in message:
            ans += sub[c]
        return ans


assert (
    Solution().decodeMessage(
        "the quick brown fox jumps over the lazy dog", "vkbs bs t suepuv"
    )
    == "this is a secret"
)
assert (
    Solution().decodeMessage(
        "eljuxhpwnyrdgtqkviszcfmabo", "zwx hnfx lqantp mnoeius ycgk vcnjrdb"
    )
    == "the five boxing wizards jump quickly"
)

Leetcode: 2326. Spiral Matrix IV

To transverse the grid doing a spiral starting from \((0,0)\), you have to go to the left as much as possible, then go down as much as possible, then go right as much as possible and then go top as much as possible. The definition of "as much as possible" is (1) the next position is inside the grid and it hasn't a value assigned to it. Time and space complexity is \(O(m * n)\).

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def spiralMatrix(self, m: int, n: int, head: Optional[ListNode]) -> List[List[int]]:
        ans = [[-1] * n for _ in range(m)]
        i = j = d = 0
        dir = [[+0, +1], [+1, +0], [+0, -1], [-1, +0]]

        def valid(i, j):
            return 0 <= i < m and 0 <= j < n and ans[i][j] == -1

        while head is not None and valid(i, j):
            ans[i][j] = head.val
            head = head.next
            if valid(i + dir[d][0], j + dir[d][1]):
                i += dir[d][0]
                j += dir[d][1]
            else:
                d = (d + 1) % 4
                i += dir[d][0]
                j += dir[d][1]
        return ans

Leetcode: 2327. Number of People Aware of a Secret

Retrospective: My intuition was that the problem had a recurrence like $f(n)=f(n-1)+f(n-2)+…$, and I spent a long time trying to make one big recurrence to work. I only unblocked myself when I decided to break the formula in parts that made more sense like "how can I define the number of people alive?" and "how can I define the number of people born on this day?" and so on.

Be \(d(n)\) and \(b(n)\) the number of people that died (don't pass the secret anymore) and are born (people that started to spread the secret) on the \(n\)th day. We can compute \(a(n)=a(n-1)-d(n)+b(n)\) as the number of people that are alive (knows the secret) on the \(n\)th day. The number of people that died on the \(n\)th is the number of people that were born \(forget\) days ago: \(d(n)=b(n-forget)\). The trick part of the problem is to compute \(b(n)\) which is the people that were born \(n-forget+1\) days ago (\(n\)th is the last day to tell the secret to someone else), \(n-forget+2\), …, \(n-delay\) (\(n\) is the first day that they told the secret to someone else): \(b(n)=b(n-forget+1)+b(n-forget+2)+...+b(n-delay+1)\). Time and space complexity is \(O(n)\).

from functools import cache

class Solution:
    def peopleAwareOfSecret(self, n: int, delay: int, forget: int) -> int:
        M = 10 ** 9 + 7

        @cache
        def born(n):
            if n == 1:
                return 1
            if n < 1:
                return 0
            ans = 0
            for i in range(n - forget + 1, n - delay + 1):
                ans = (ans + born(i)) % M
            return ans

        def die(n):
            return born(n - forget) % M

        @cache
        def alive(n):
            if n == 0:
                return 0
            return (alive(n - 1) - die(n) + born(n)) % M

        return alive(n) % M


assert Solution().peopleAwareOfSecret(6, 2, 4) == 5
assert Solution().peopleAwareOfSecret(4, 1, 3) == 6

Leetcode: 2328. Number of Increasing Paths in a Grid

Retrospective: My intuition pointed to process the cell from the smaller to the bigger values (I used a similar trick on Leetcode: 329. Longest Increasing Path in a Matrix). With this, the solution was straight-forward.

Can we pre-process the input in a way to make easy to solve the problem? Be \((i, j)\) the coordinate of the cell with the greatest value on the grid. If we knew the number of paths for all its neighbors \(k\), then it is easy to compute the number of path that end on \((i, j)\): \(1 + k\). If we remove this cell from the grid, we can compute the same for the second largest cell up to the point where there is only one cell which the number of paths that end on it is 1. From this observation we can derive the following algorithm, from cell with the smallest value to the greatest values, compute the number of path that end on each one of them. On the end, sum the number of paths that end on each cell on the grid and you have the answer for the problem. Time and space complexity is \(O(n \times m)\) where \(n\) is the number of rows of the grid and \(m\) is the number of columns.

class Solution:
    def countPaths(self, grid: List[List[int]]) -> int:
        MOD = 10**9 + 7
        n = len(grid)
        m = len(grid[0])
        pos = []
        for i in range(n):
            for j in range(m):
                pos.append((grid[i][j], i, j))

        pos = list(sorted(pos))
        dp = [[1] * m for _ in range(n)]
        dir = [[+0, +1], [+0, -1], [+1, +0], [-1, +0]]
        ans = 0
        for _, i, j in pos:
            for d in dir:
                ni = i + d[0]
                nj = j + d[1]
                if 0 <= ni < n and 0 <= nj < m and grid[ni][nj] < grid[i][j]:
                    dp[i][j] += dp[ni][nj]
            ans = (ans + dp[i][j]) % MOD

        return ans


assert Solution().countPaths([[1, 1], [3, 4]]) == 8
assert Solution().countPaths([[1], [2]]) == 3