leetcode

Table of Contents

1. leetcode

1.1. Backtracking

backtracking 的一般框架为:

def dfs(curr, i):
     if ending_cond:
        ans.append(curr)
        return

    if visited(i):
        return
    visited.add(i)

    for x in branches:
        dfs(curr + x, ...)

    visited.remove(i)

它与普通 dfs 的区别主要是需要 `visited.remove` 而 dfs 通常不需要

1.1.1. N-Queens

https://leetcode.com/problems/n-queens/

from collections import defaultdict


class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        rows = defaultdict(int)
        cols = defaultdict(int)
        left = defaultdict(int)
        right = defaultdict(int)
        ans = []

        def put(output, i, j):
            rows[i] = 1
            cols[j] = 1
            left[i + j] = 1
            right[i - j] = 1
            output[i][j] = "Q"

        def clear(output, i, j):
            rows[i] = 0
            cols[j] = 0
            left[i + j] = 0
            right[i - j] = 0
            output[i][j] = "."

        def isValid(i, j):
            return (
                rows[i] == 0 and cols[j] == 0 and left[i + j] == 0 and right[i - j] == 0
            )

        def dfs(output, i):
            if i == n:
                ans.append(["".join(x) for x in output])
                return
            for j in range(n):
                if isValid(i, j):
                    put(output, i, j)
                    dfs(output, i + 1)
                    clear(output, i, j)

        output = [["." for _ in range(n)] for _ in range(n)]
        dfs(output, 0)
        return ans

1.1.2. Permutations II

https://leetcode.com/problems/permutations-ii/

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        visited = set()
        ans = []

        def dfs(curr):
            if len(curr) == len(nums):
                ans.append(curr)
                return
            used = set()
            for i, x in enumerate(nums):
                if i in visited:
                    continue
                if x in used:
                    continue
                used.add(x)
                visited.add(i)
                dfs(curr + [x])
                visited.remove(i)

        dfs([])
        return ans

1.2. Binary Search

1.2.1. Search a 2D Matrix II

https://leetcode.com/problems/search-a-2d-matrix-ii/

关键问题在于确定二维的 lo, hi 的值, 例如下图中比 e 小的范围为 abc, adg, 比 e 大的范围为 cfk, ghk. c, g 与 e 的大小并不确定

a b c
d e f
g h k
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        M, N = len(matrix), len(matrix[0])

        def solve(lo, hi, target):
            if lo[0] >= hi[0] or lo[1] >= hi[1]:
                return False
            midx, midy = (lo[0] + hi[0]) // 2, (lo[1] + hi[1]) // 2
            if matrix[midx][midy] == target:
                return True
            if matrix[midx][midy] > target:
                return solve(lo, (midx, hi[1]), target) or solve(
                    lo, (hi[0], midy), target
                )
            return solve((lo[0], midy + 1), hi, target) or solve(
                (midx + 1, lo[1]), hi, target
            )

        return solve((0, 0), (M, N), target)

或者采用非 bisect 的算法 (O(m+n)):

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        M, N = len(matrix), len(matrix[0])
        x, y = 0, N - 1
        while x < M and y >= 0:
            if matrix[x][y] == target:
                return True
            if matrix[x][y] > target:
                y -= 1
            else:
                x += 1
        return False

1.2.2. Find Minimum in Rotated Sorted Array

https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

这里使用 hi 为 len(nums)-1 而不是 len(nums). 正常的二分查找并不关注这个值, 但这个问题中 mid 本身要参与到下一次迭代: (lo,mid)/(mid+1, hi), 导致 N=2 时出现死循环.

假设 nums 为 [0,1], 若迭代公式为 (lo,mid)/(mid+1,hi), 若 mid 选 1, 则会死循环, 若 mid 选 0, 则没有问题.

同理, 若迭代公式为 (lo,mid-1)/(mid,hi), 若 mid 选 0, 则死循环, 若 mid 选 1, 则没问题.

使用 N 还是 N-1 实际上代表剩余两个元素 [0,1] 时 mid 选 0 还是 1.

class Solution:
    def findMin(self, nums: List[int]) -> int:
        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) // 2
            if nums[lo] > nums[mid]:
                hi = mid
            elif nums[mid] > nums[hi]:
                lo = mid + 1
            else:
                hi = mid
        return nums[lo]

如果不想考虑前面的问题, 也可以这样, 更简单一些:

class Solution:
    def findMin(self, nums: List[int]) -> int:
        lo, hi = 0, len(nums)
        ans = 1 << 31
        while lo < hi:
            mid = (lo + hi) // 2
            ans = min(ans, nums[mid])
            if nums[lo] > nums[mid]:
                hi = mid
            elif nums[mid] > nums[hi - 1]:
                lo = mid + 1
            else:
                hi = mid
        return ans

1.2.3. Search in Rotated Sorted Array

https://leetcode.com/problems/search-in-rotated-sorted-array/

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        lo, hi = 0, len(nums)
        while lo < hi:
            mid = (lo + hi) // 2
            if nums[mid] == target:
                return mid
            if nums[lo] < nums[mid]:
                if target >= nums[lo] and target < nums[mid]:
                    hi = mid
                else:
                    lo = mid + 1
            else:
                if target > nums[mid] and target <= nums[hi - 1]:
                    lo = mid + 1
                else:
                    hi = mid
        return -1

1.2.4. Sqrt

https://leetcode.com/problems/sqrtx/

class Solution:
    def mySqrt(self, x: int) -> int:
        lo = 1
        hi = x
        while lo <= hi:
            mid = (lo + hi) // 2
            if mid * mid <= x and (mid + 1) * (mid + 1) > x:
                return mid
            if mid * mid > x:
                hi = mid - 1
            else:
                lo = mid + 1
        return 0

1.2.5. Minimize Maximum of Array

https://leetcode.com/problems/minimize-maximum-of-array/

与 Sqrt 问题类似, 需要在一个有限的解空间内寻找答案, 且寻找过程符合 bisect 的性质

class Solution:
    def minimizeArrayValue(self, nums: List[int]) -> int:
        N = len(nums)

        def ok(n):
            t = nums[::]
            for i in reversed(range(1, N)):
                if t[i] > n:
                    t[i - 1] += t[i] - n
            return t[0] <= n

        lo, hi = min(nums), max(nums)
        ans = 0
        while lo <= hi:
            mid = (lo + hi) // 2
            if ok(mid):
                ans = mid
                hi = mid - 1
            else:
                lo = mid + 1
        return ans

1.2.6. Single Element in a Sorted Array

https://leetcode.com/problems/single-element-in-a-sorted-array/

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        lo = 0
        hi = len(nums)
        N = len(nums)
        while lo < hi:
            mid = (lo + hi) // 2
            if (mid == 0 or nums[mid] != nums[mid - 1]) and (
                mid == N - 1 or nums[mid] != nums[mid + 1]
            ):
                return nums[mid]
            pair = mid + 1 if mid % 2 == 0 else mid - 1
            if nums[mid] == nums[pair]:
                lo = mid + 1
            else:
                hi = mid

1.3. knapsack

1.3.1. Partition Equal Subset Sum

https://leetcode.com/problems/partition-equal-subset-sum/

01 背包, sum 是背包, nums 是物品

其框架为:

for i in range(N):
    for k,v in dp[i-1].items():
        dp[i][k+nums[i]]= ...
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2 != 0:
            return False
        _dp = [{} for _ in range(len(nums))]
        _dp[0][0] = 1
        _dp[0][nums[0]] = 1
        for i in range(1, len(nums)):
            for k, v in _dp[i - 1].items():
                _dp[i][k + nums[i]] = 1
                _dp[i][k] = 1
        return total // 2 in _dp[-1]

1.3.2. Coin Change

https://leetcode.com/problems/coin-change/

完全背包问题, 不限制 coins 的数量, amount 是背包, coins 是物品

其框架为:

for i in range(amount):
    for x in range(N):
        dp[i]=f(x,dp[i-1])

其中一般情况下外层循环是背包或物品并没有限制, 除非:

  1. 排列问题需要背包放在外层循环
  2. 组合问题需要背包放在内层循环
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        _dp = [0 for _ in range(amount + 1)]
        MAX = 1 << 31

        def dp(i):
            if i < 0:
                return MAX
            return _dp[i]

        for i in range(1, amount + 1):
            tmp = MAX
            for c in coins:
                tmp = min(tmp, dp(i - c) + 1)
            _dp[i] = tmp

        if _dp[-1] == MAX:
            return -1
        return _dp[-1]

1.3.3. Coin Change II

https://leetcode.com/problems/coin-change-ii/

完全背包, 但是是一个组合问题 (而不是排列问题), 需要把背包放在内层循环

https://leetcode.com/problems/coin-change-ii/solutions/5023598/beginner-mistake-permutations-vs-combination/

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        _dp = [0 for _ in range(amount + 1)]
        _dp[0] = 1

        def dp(i):
            if i < 0:
                return 0
            return _dp[i]

        for c in coins:
            for i in range(1, amount + 1):
                _dp[i] += dp(i - c)

        return _dp[-1]

1.3.4. Combination Sum IV

https://leetcode.com/problems/combination-sum-iv/

与 Coin Change II 基本相同, 但是是一个排列问题, 需要把背包放在外层循环

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        _dp = [0 for _ in range(target + 1)]
        _dp[0] = 1

        def dp(i):
            if i < 0:
                return 0
            return _dp[i]

        for i in range(1, target + 1):
            for x in nums:
                _dp[i] += dp(i - x)
        return _dp[-1]

1.3.5. Word Break

https://leetcode.com/problems/word-break/

完全背包, 不限制 words 的数量, s 是背包, words 是物品

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        N = len(s)
        wordDict = set(wordDict)

        _dp = [False for _ in range(N)]

        def dp(i):
            if i == -1:
                return True
            return _dp[i]

        for i in range(N):
            for j in range(i + 1):
                x = s[j : i + 1]
                tmp = False
                if x in wordDict:
                    tmp |= dp(j - 1)
                if tmp:
                    _dp[i] = True
                    break
        return _dp[-1]

1.4. DP

1.4.1. Stone Game

https://leetcode.com/problems/stone-game/description/

这个问题的子问题对应中间一段子区间, 这种形式的 DP 需要使用从小的 subarray 扩展到大的 subarray 的形式, 二维 DP 需要使用 distance 来迭代, 即:

for d in range(1,N):
    for i in range(N-d):
        ...

且初始化条件为 d=0

类似的问题还有 https://leetcode.com/problems/palindromic-substrings/

class Solution:
    def stoneGame(self, piles: List[int]) -> bool:
        N = len(piles)
        # 5 3 4 5
        #   i j
        dp = [[0 for _ in range(N)] for _ in range(N)]
        for i in range(N):
            dp[i][i] = piles[i]
        for d in range(1, N):
            for i in range(N - d):
                dp[i][i + d] = max(
                    piles[i] - dp[i + 1][i + d], piles[i + d] - dp[i][i + d - 1]
                )
        return dp[0][-1] > 0

1.4.2. Maximum Sum Circular Subarray

https://leetcode.com/problems/maximum-sum-circular-subarray/

class Solution:
    def maxSubarraySumCircular(self, nums: List[int]) -> int:
        # 5 -3 5
        # 5 -3 5 5 -3 5
        def getMax(nums):
            ans = nums[0]
            dp = nums[:]
            for i in range(1, len(nums)):
                dp[i] = max(dp[i - 1] + nums[i], nums[i])
                ans = max(ans, dp[i])
            return ans

        def getMin(nums):
            ans = nums[0]
            dp = nums[:]
            for i in range(1, len(nums)):
                dp[i] = min(dp[i - 1] + nums[i], nums[i])
                ans = min(ans, dp[i])
            return ans

        a = getMax(nums)
        b = getMin(nums)
        if b == sum(nums):
            return a
        return max(a, sum(nums) - b)

1.4.3. Champagne Tower

https://leetcode.com/problems/champagne-tower/

class Solution:
    def champagneTower(self, poured: int, query_row: int, query_glass: int) -> float:
        _dp = [[0 for _ in range(query_row + 1)] for _ in range(query_row + 1)]
        _dp[0][0] = poured

        def get(i, j):
            if j < 0 or j > i:
                return 0
            return (_dp[i][j] - 1) / 2 if _dp[i][j] >= 1 else 0

        for i in range(1, query_row + 1):
            goon = False
            for j in range(i + 1):
                _dp[i][j] = get(i - 1, j - 1) + get(i - 1, j)
                if _dp[i][j] > 1:
                    goon = True
            if not goon:
                break
        return min(_dp[query_row][query_glass], 1)

1.4.4. Maximum Alternating Subsequence Sum

https://leetcode.com/problems/maximum-alternating-subsequence-sum/

类似的问题还有: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/

该问题和`最大的子数组`类似, 但元素如何 take 取决于当前子序列长度是奇数还是偶数, 因此需要维护 even 和 odd 两个 sum

class Solution:
    def maxAlternatingSum(self, nums: List[int]) -> int:
        # 4 2 5 3
        N = len(nums)
        # subseq 中元素为偶数个
        _dpEven = [0 for _ in range(N)]
        # subseq 中元素为奇数个
        _dpOdd = [0 for _ in range(N)]

        _dpOdd[0] = nums[0]

        for i in range(1, N):
            _dpEven[i] = max(_dpEven[i - 1], _dpOdd[i - 1] - nums[i])
            _dpOdd[i] = max(_dpOdd[i - 1], _dpEven[i - 1] + nums[i])
        return max(_dpEven[-1], _dpOdd[-1])

1.5. Graph

1.5.1. Find the City With the Smallest Number of Neighbors at a Threshold Distance

https://leetcode.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/

floyd warshall

class Solution:
    def findTheCity(
        self, n: int, edges: List[List[int]], distanceThreshold: int
    ) -> int:
        dist = [[1 << 31 for _ in range(n)] for _ in range(n)]
        for x, y, d in edges:
            dist[x][y] = d
            dist[y][x] = d
        for k in range(n):
            for i in range(n):
                for j in range(n):
                    if i == j:
                        continue
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

        count = [len(list(filter(lambda x: x <= distanceThreshold, x))) for x in dist]
        target = min(count)
        for i in reversed(range(n)):
            if count[i] == target:
                return i

1.5.2. Cheapest Flights Within K Stops

https://leetcode.com/problems/cheapest-flights-within-k-stops/

dijkstra

和正常 bfs 的区别在于 bfs 使用 visited 标记已经遍历过的节点, 但如果边带权, 则无法使用. 这里使用 shortest 来代替 visited

from collections import deque


class Solution:
    def findCheapestPrice(
        self, n: int, flights: List[List[int]], src: int, dst: int, k: int
    ) -> int:
        q = deque()
        q.append((src, 0))
        q.append(None)
        stops = 0
        ans = 0
        shortest = {}
        conn = {}
        for f, t, p in flights:
            if f not in conn:
                conn[f] = []
            conn[f].append([t, p])
        while q:
            top = q.popleft()
            if top is None:
                if q:
                    q.append(None)
                stops += 1
                if stops == k + 2:
                    break
                continue
            shortest[top[0]] = min(shortest.get(top[0], 1 << 31), top[1])
            for t, p in conn.get(top[0], []):
                if t not in shortest or shortest[t] > p + top[1]:
                    q.append((t, top[1] + p))
        if dst in shortest:
            return shortest[dst]
        return -1

1.5.3. Course Schedule

https://leetcode.com/problems/course-schedule/

topo sort

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        dep = [[] for _ in range(numCourses)]
        order = [0 for _ in range(numCourses)]
        for a, b in prerequisites:
            dep[b].append(a)
            order[a] += 1
        q = deque()
        count = 0
        for i in range(numCourses):
            if order[i] == 0:
                count += 1
                q.append(i)
        while q:
            top = q.popleft()
            for x in dep[top]:
                order[x] -= 1
                if order[x] == 0:
                    count += 1
                    q.append(x)
        return count == numCourses

1.5.4. Surrounded Regions

https://leetcode.com/problems/surrounded-regions/

从边缘的 O 开始 dfs, 标记连通的点.

如果不从边缘开始 dfs 而是采用任意起点 dfs 然后判断是否与边缘连通则会 TLE

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        M, N = len(board), len(board[0])

        visited = {}

        def dfs(x, y):
            if x < 0 or y < 0 or x >= M or y >= N:
                return
            if board[x][y] != "O":
                return
            if (x, y) in visited:
                return
            visited[(x, y)] = 1
            board[x][y] = "Y"
            dir = [[0, 1], [0, -1], [1, 0], [-1, 0]]
            for xx, yy in dir:
                dfs(x + xx, y + yy)

        for x in [0, M - 1]:
            for y in range(N):
                dfs(x, y)
        for y in [0, N - 1]:
            for x in range(M):
                dfs(x, y)

        for x in range(M):
            for y in range(N):
                if board[x][y] == "O":
                    board[x][y] = "X"
        for x in range(M):
            for y in range(N):
                if board[x][y] == "Y":
                    board[x][y] = "O"

1.5.5. 01 Matrix

https://leetcode.com/problems/01-matrix/

from collections import deque


class Solution:
    def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
        # 000
        # 010
        # 111
        q = deque()
        M, N = len(mat), len(mat[0])
        ans = [[-1 for _ in range(N)] for _ in range(M)]
        visited = set()
        for i in range(M):
            for j in range(N):
                if mat[i][j] == 0:
                    ans[i][j] = 0
                    q.append((i, j))
                    visited.add((i, j))

        q.append(None)
        distance = 1
        while q:
            top = q.popleft()
            if top is None:
                if q:
                    q.append(None)
                distance += 1
                continue
            for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
                x, y = top[0] + d[0], top[1] + d[1]
                if x < 0 or x >= M or y < 0 or y >= N:
                    continue
                if (x, y) in visited:
                    continue
                ans[x][y] = distance
                visited.add((x, y))
                q.append((x, y))
        return ans

1.6. Hash Map

1.6.1. Two Sum

https://leetcode.com/problems/two-sum/

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        m = {}
        for i, v in enumerate(nums):
            if target - v in m:
                return [m[target - v], i]
            m[v] = i
        return None

1.7. Heap

1.7.1. Find K Pairs with Smallest Sums

https://leetcode.com/problems/find-k-pairs-with-smallest-sums/

无论用最小堆直接求解或者用大小为 k 的最大堆保留 k 个最小值的解法都会超时

class Solution:
    def kSmallestPairs(self, nums1, nums2, k):
        q = [(nums1[0] + nums2[0], [0, 0])]
        visited = {}
        A, B = len(nums1), len(nums2)
        ans = []
        while k > 0:
            a, b = heappop(q)[1]
            if (a, b) in visited:
                continue
            k -= 1
            visited[(a, b)] = 1
            ans.append([nums1[a], nums2[b]])
            if a + 1 < A:
                heappush(q, (nums1[a + 1] + nums2[b], [a + 1, b]))
            if b + 1 < B:
                heappush(q, (nums1[a] + nums2[b + 1], [a, b + 1]))
        return ans

1.7.2. Find Median from Data Stream

https://leetcode.com/problems/find-median-from-data-stream/

class MedianFinder:

    def __init__(self):
        self.lq = []  # maxheap
        self.rq = []  # minheap

    def addNum(self, num: int) -> None:
        if len(self.lq) == len(self.rq):
            heappush(self.lq, -heappushpop(self.rq, num))
        else:
            heappush(self.rq, -heappushpop(self.lq, -num))

    def findMedian(self) -> float:
        if len(self.lq) == len(self.rq):
            return (-self.lq[0] + self.rq[0]) / 2
        else:
            return -self.lq[0]

1.8. Interval

1.8.1. Insert Interval

https://leetcode.com/problems/insert-interval/

在一个循环中需要分段做不同的事, 可以把它变成多个循环

class Solution:
    def insert(self, intervals, newInterval):
        intervals.sort(key=lambda x: x[0])
        ans = []
        lo, hi = newInterval
        i = 0
        while i < len(intervals) and intervals[i][1] < lo:
            ans.append(intervals[i])
            i += 1
        while i < len(intervals) and intervals[i][0] <= hi:
            lo, hi = min(lo, intervals[i][0]), max(hi, intervals[i][1])
            i += 1
        ans.append([lo, hi])
        while i < len(intervals):
            ans.append(intervals[i])
            i += 1
        return ans

1.9. Linked List

1.9.1. LRU Cache

https://leetcode.com/problems/lru-cache/

使用双向链表维护 LRU 信息

class Node:
    def __init__(self, k=0, v=0):
        self.prev = self
        self.next = self
        self.val = v
        self.key = k

    def insert(self, b):
        # a b c
        a = self
        c = self.next
        a.next, b.next = b, c
        c.prev, b.prev = b, a

    def delete(self):
        # a b c
        a = self.prev
        c = self.next
        a.next, c.prev = c, a


class LRUCache:

    def __init__(self, capacity: int):
        self.cap = capacity
        self.m = {}  # map int to node
        self.head = Node()
        self.tail = Node()
        self.head.next, self.head.prev = self.tail, self.tail
        self.tail.next, self.tail.prev = self.head, self.head

    def get(self, key: int) -> int:
        if key not in self.m:
            return -1
        node = self.m[key]
        node.delete()
        self.head.insert(node)
        return node.val

    def put(self, key: int, value: int) -> None:
        if key in self.m:
            node = self.m[key]
            node.val = value
            node.delete()
            self.head.insert(node)
            return
        if len(self.m) == self.cap:
            evict = self.tail.prev
            self.m.pop(evict.key)
            evict.delete()
        node = Node(key, value)
        self.m[key] = node
        self.head.insert(node)

1.9.2. Rotate List

https://leetcode.com/problems/rotate-list/

原链表首尾相接变成循环链表后再处理

class Solution:
    def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        if not head:
            return head

        prev, curr = head, head
        N = 0
        while curr:
            prev, curr = curr, curr.next
            N += 1
        k %= N
        prev.next = head

        prev = head
        for _ in range(N - k):
            prev, head = head, head.next
        prev.next = None
        return head

1.10. LIS

1.10.1. Longest Increasing Subsequence

https://leetcode.com/problems/longest-increasing-subsequence/

标准的使用 DP 的算法是 O(n^2)

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        N = len(nums)
        _dp = [1 for _ in range(N)]
        ans = 1
        for i in range(N):
            for j in range(i):
                if nums[i] > nums[j]:
                    _dp[i] = max(_dp[i], _dp[j] + 1)
            ans = max(ans, _dp[i])
        return ans

使用 bisect 的算法是 O(nlog(n))

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        def bisect_left(nums, target):
            lo, hi = 0, len(nums)
            while lo < hi:
                mid = (lo + hi) // 2
                if target <= nums[mid]:
                    hi = mid
                else:
                    lo = mid + 1
            return lo

        lis = []
        for x in nums:
            if not lis or x > lis[-1]:
                lis.append(x)
            else:
                idx = bisect_left(lis, x)
                lis[idx] = x
        return len(lis)

1.10.2. Longest Ideal Subsequence

https://leetcode.com/problems/longest-ideal-subsequence/

本质上也是 LIS 问题, 但直接使用类似于 LIS 的 DP 算法会 LTE, 观察到 dp 实际只有 26 种不同的值, 转而对 26 个字母而不是针对 n 进行 dp

class Solution:
    def longestIdealString(self, s: str, k: int) -> int:
        ans = 1
        _dp = {}
        for x in s:
            t = 1
            for y, l in _dp.items():
                if abs(ord(x) - ord(y)) <= k:
                    t = max(t, l + 1)
            _dp[x] = t
            ans = max(ans, t)
        return ans

1.10.3. Flip String to Monotone Increasing

https://leetcode.com/problems/flip-string-to-monotone-increasing/

与 Longest Ideal Subsequence 类似, dp 只有两种状态: 0/1, dp[0] 表示目前为止看到的以 0 结尾的非递减序列的最大长度

class Solution:
    def minFlipsMonoIncr(self, s: str) -> int:
        dp = [0, 0]
        for x in s:
            if x == "0":
                dp[0] += 1
            else:
                dp[1] = max(dp[0], dp[1]) + 1
        return len(s) - max(dp)

1.11. Matrix

1.11.1. Rotate Image

https://leetcode.com/problems/rotate-image/

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """

        def transpose(matrix):
            N = len(matrix)
            for i in range(N):
                # NOTE: inplace transpose 时注意 j 的范围
                for j in range(i, N):
                    matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

        def reverse(matrix):
            N = len(matrix)
            for i in range(N // 2):
                matrix[i], matrix[N - 1 - i] = matrix[N - 1 - i], matrix[i]

        reverse(matrix)
        transpose(matrix)

1.12. Monotonic Stack

mono stack 通常用来处理序列上的 first larger/smaller 的问题, 其代码框架为:

for i in range(N):
    # > 对应 first larger 类的问题
    # < 对应 first smaller 类的问题
    while stack and nums[i] > stack[-1]:
        stack.pop()
    stack.append(i)

1.12.1. Daily Temperatures

https://leetcode.com/problems/daily-temperatures/

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        stack = []
        N = len(temperatures)
        ans = [0 for _ in range(N)]
        for i in range(N):
            while stack and temperatures[i] > temperatures[stack[-1]]:
                t = stack.pop()
                ans[t] = i - t
            stack.append(i)
        return ans

1.12.2. Sum of Subarray Minimums

https://leetcode.com/problems/sum-of-subarray-minimums/

class Solution:
    def sumSubarrayMins(self, arr: List[int]) -> int:
        # 3 1 2 4
        N = len(arr)

        left = [0 for _ in range(N)]
        stack = []
        for i in range(N):
            while stack and arr[i] <= arr[stack[-1]]:
                stack.pop()
            left[i] = stack[-1] if stack else -1
            stack.append(i)

        right = [0 for _ in range(N)]
        stack = []
        for i in reversed(range(N)):
            while stack and arr[i] < arr[stack[-1]]:
                stack.pop()
            right[i] = stack[-1] if stack else N
            stack.append(i)

        ans = 0
        for i in range(N):
            ans += arr[i] * (i - left[i]) * (right[i] - i)
        return ans % (10**9 + 7)

1.12.3. 132 Pattern

https://leetcode.com/problems/132-pattern/

class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        stack = []
        third = -1 << 31
        for i in reversed(range(len(nums))):
            if nums[i] < third:
                return True
            while stack and nums[i] > stack[-1]:
                third = stack.pop()
            stack.append(nums[i])
        return False

1.13. Recursive

1.13.1. Minimum Cost For Tickets

https://leetcode.com/problems/minimum-cost-for-tickets/

class Solution:
    def mincostTickets(self, days: List[int], costs: List[int]) -> int:
        N = len(days)

        @cache
        def solve(start):
            if start >= N:
                return 0

            A = costs[0] + solve(start + 1)

            idx = bisect_left(days, days[start] + 7)
            B = costs[1] + solve(idx)

            idx = bisect_left(days, days[start] + 30)
            C = costs[2] + solve(idx)

            return min(A, B, C)

        return solve(0)

1.14. Sliding Window

sliding window 的代码的一般框架为:

lo=0
for hi in range():
  # update with nums[hi]
  while cond:
      # update with nums[lo]
      lo+=1

1.14.1. Substring with Concatenation of All Words

https://leetcode.com/problems/substring-with-concatenation-of-all-words/

如果 words 较少, 可以生成 words 的所有组合, 并使用 map 在 s 里查找, 这里的 words 很多, 无法使用该算法

为了应用 sliding window, 无法按字符来 slide, 只能按 word 来 slide

from collections import Counter
from collections import defaultdict


class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        c = Counter(words)
        width = len(words[0])

        def solve(s):
            x = []
            for i in range(0, len(s), width):
                x.append(s[i : i + width])
            N = len(x)
            ans = []
            lo = 0
            window = len(words)
            m = defaultdict(int)
            for hi in range(N):
                m[x[hi]] += 1
                if hi - lo + 1 == window:
                    if all([m[x] == c[x] for x in c.keys()]):
                        ans.append(lo * width)
                    m[x[lo]] -= 1
                    lo += 1
            return ans

        ans = []
        for i in range(width):
            ans.extend([x + i for x in solve(s[i:])])
        return ans

1.14.2. Longest Repeating Character Replacement

https://leetcode.com/problems/longest-repeating-character-replacement/

from collections import defaultdict


class Solution:
    def test(self, counter, k):
        x = list(counter.values())
        return sum(x) - max(x) == k + 1

    def characterReplacement(self, s: str, k: int) -> int:
        counter = defaultdict(int)
        lo = 0
        ans = 0
        for hi in range(len(s)):
            x = s[hi]
            counter[x] += 1
            while self.test(counter, k):
                counter[s[lo]] -= 1
                lo += 1
            ans = max(ans, hi - lo + 1)
        ans = max(ans, hi - lo + 1)
        return ans

1.14.3. Longest Substring with At Least K Repeating Characters

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/

正常的 sliding window 无法工作: at most k 可以工作, 但 at least k 不可以. 通过添加一个额外的 max char 条件让 sliding windows 可以工作

solution

from collections import defaultdict
from collections import Counter


class Solution:
    def longestSubstring(self, s: str, k: int) -> int:
        maxChar = len(Counter(s))
        ans = 0
        for i in range(1, maxChar + 1):
            uniqueCounter = defaultdict(int)
            lo = 0
            for hi in range(len(s)):
                uniqueCounter[s[hi]] += 1
                while len(uniqueCounter) > i:
                    uniqueCounter[s[lo]] -= 1
                    if uniqueCounter[s[lo]] == 0:
                        uniqueCounter.pop(s[lo])
                    lo += 1
                if all([x >= k for x in uniqueCounter.values()]):
                    ans = max(ans, hi - lo + 1)
            if all([x >= k for x in uniqueCounter.values()]):
                ans = max(ans, hi - lo + 1)
        return ans

1.14.4. Longest Nice Subarray

https://leetcode.com/problems/longest-nice-subarray/

class Solution:
    def longestNiceSubarray(self, nums: List[int]) -> int:
        lo = 0
        used = 0
        ans = 0
        for hi in range(len(nums)):
            while used & nums[hi] != 0:
                used ^= nums[lo]
                lo += 1
            used |= nums[hi]
            ans = max(ans, hi - lo + 1)
        return ans

1.14.5. Count Complete Subarrays in an Array

https://leetcode.com/problems/count-complete-subarrays-in-an-array/

当 window 中不同的元素个数达到 b 时, 感觉并不能开始 shrink, 因为 hi 还能增长. 但实际上后续 N-hi 个位置不需要判断, 因为它们肯定是符合条件的

from collections import defaultdict


class Solution:
    def countCompleteSubarrays(self, nums: List[int]) -> int:
        b = set(nums)
        a = defaultdict(int)
        lo = 0
        ans = 0
        for hi in range(len(nums)):
            a[nums[hi]] += 1
            while set(a.keys()) == b:
                ans += len(nums) - hi
                a[nums[lo]] -= 1
                if a[nums[lo]] == 0:
                    a.pop(nums[lo])
                lo += 1
        return ans

1.15. Stack

1.15.1. Basic Calculator

https://leetcode.com/problems/basic-calculator

简单的计算器不需要使用 stack, 例如 1+2-3, 其代码大约是:

def solve(tokens):
    ans = 0
    sign = 1
    for x in tokens:
        if x == "+":
            sign = 1
        elif x == "-":
            sign = -1
        else:
            ans += int(x) * sign
    return ans
  1. 使用 stack 来处理乘除法的优化级:

    例如针对 1+2*3, stack 的情况为:

    [1] -> [1,2] -> [1, 6]
    
  2. 使用 stack 找到对应的括号
class Solution:
    def calculate(self, s: str) -> int:
        def getTokens(s):
            ans = []
            i = 0
            digit = ""
            while i < len(s):
                if s[i] == " ":
                    i += 1
                    continue
                if s[i].isdigit():
                    while i < len(s) and s[i].isdigit():
                        digit += s[i]
                        i += 1
                    ans.append(digit)
                    digit = ""
                elif s[i] in "+-*/":
                    ans.append(s[i])
                    i += 1
                else:
                    balance = 0
                    tmp = ""
                    while True:
                        if s[i] == "(":
                            balance += 1
                        elif s[i] == ")":
                            balance -= 1
                        tmp += s[i]
                        i += 1
                        if balance == 0:
                            ans.append(getTokens(tmp[1:-1]))
                            break
            return ans

        def solve(tokens):
            sign = 1
            times = 0
            divid = 0
            stack = []
            for x in tokens:
                if x == "+":
                    sign = 1
                elif x == "-":
                    sign = -1
                elif x == "*":
                    times = 1
                elif x == "-":
                    divid = 1
                else:
                    if isinstance(x, list):
                        stack.append(solve(x) * sign)
                    elif x.isdigit():
                        stack.append(int(x) * sign)
                    if times == 1:
                        stack.append(stack.pop() * stack.pop())
                        times = 0
                    if divid == 1:
                        b, a = stack.pop(), stack.pop()
                        stack.append(a / b)
                        divid = 0
            return sum(stack)

        return solve(getTokens(s))


if __name__ == "__main__":
    S = Solution()
    print(S.calculate("1+2*2*(1+1*2)+3+(-1)*(-1-1)*(-2)"))

1.16. Tree

1.16.1. BST Iterator

https://leetcode.com/problems/binary-search-tree-iterator/

class BSTIterator:

    def __init__(self, root: Optional[TreeNode]):
        self.stack = []
        curr = root
        while curr:
            self.stack.append(curr)
            curr = curr.left

    def next(self) -> int:
        top = self.stack.pop()
        curr = top.right
        while curr:
            self.stack.append(curr)
            curr = curr.left
        return top.val

    def hasNext(self) -> bool:
        return len(self.stack) != 0

1.16.2. Symmetric Tree

https://leetcode.com/problems/symmetric-tree/

class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        def same(a, b):
            if not a and not b:
                return True
            if not a or not b:
                return False
            return a.val == b.val and same(a.left, b.right) and same(a.right, b.left)

        return same(root.left, root.right)

1.16.3. Validate BST

https://leetcode.com/problems/validate-binary-search-tree/

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def check(root):
            MIN, MAX = root.val, root.val
            if root.left:
                ans, x, y = check(root.left)
                if not ans:
                    return False, 0, 0
                if root.val <= y:
                    return False, 0, 0
                MIN = min(MIN, x)
                MAX = max(MAX, y)
            if root.right:
                ans, x, y = check(root.right)
                if not ans:
                    return False, 0, 0
                if root.val >= x:
                    return False, 0, 0
                MIN = min(MIN, x)
                MAX = max(MAX, y)
            return True, MIN, MAX

        if not root:
            return True
        ans, _, _ = check(root)
        return ans
class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        ans = []

        def inorder(root):
            if not root:
                return
            inorder(root.left)
            ans.append(root.val)
            inorder(root.right)

        def isSorted(nums):
            if len(nums) <= 1:
                return True
            for i in range(1, len(nums)):
                if nums[i] <= nums[i - 1]:
                    return False
            return True

        inorder(root)
        return isSorted(ans)

1.17. Trick

1.17.1. Pow

https://leetcode.com/problems/powx-n/

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if n < 0:
            n = -n
            x = 1 / x
            ans = 1
        while n > 1:
            if n % 2 == 1:
                ans *= x
                n -= 1
            else:
                x *= x
                n /= 2
        return ans * x

1.17.2. Roman To Integer

https://leetcode.com/problems/roman-to-integer/

假定输入是合法的. IIV 的值是是 V-I-I = 3, 因为较小的 I 出现在较大的 V 的左边

class Solution:
    def romanToInt(self, s: str) -> int:
        m = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}
        ans = 0
        MAX = 1
        for x in s[::-1]:
            v = m[x]
            if v >= MAX:
                ans += v
                MAX = v
            else:
                ans -= v
        return ans

1.17.3. Minimize Maximum of Array

https://leetcode.com/problems/minimize-maximum-of-array/

class Solution:
    def minimizeArrayValue(self, nums: List[int]) -> int:
        total = 0
        ans = 0
        for i, v in enumerate(nums):
            total += v
            ans = max(ans, math.ceil(total / (i + 1)))
        return ans

1.17.4. Count Negative Numbers in a Sorted Matrix

https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix/

class Solution:
    def countNegatives(self, grid: List[List[int]]) -> int:
        M, N = len(grid), len(grid[0])
        x, y = 0, N - 1
        ans = 0
        while x < M and y >= 0:
            if grid[x][y] >= 0:
                x += 1
            else:
                ans += M - x
                y -= 1
        return ans

1.17.5. Count of Smaller Numbers After Self

https://leetcode.com/problems/count-of-smaller-numbers-after-self/

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        ans = [0 for _ in range(len(nums))]

        def mergeSort(nums):
            # 5 2 6 1
            # 2 5 | 1 6
            if len(nums) <= 1:
                return nums
            mid = len(nums) // 2
            a = mergeSort(nums[:mid])
            b = mergeSort(nums[mid:])

            hi = 0
            for lo in range(len(a)):
                while hi < len(b) and a[lo][1] > b[hi][1]:
                    hi += 1
                ans[a[lo][0]] += hi

            ret = []
            ai, bi = 0, 0
            while ai < len(a) or bi < len(b):
                x = a[ai][1] if ai < len(a) else 1 << 31
                y = b[bi][1] if bi < len(b) else 1 << 31
                if x <= y:
                    ret.append(a[ai])
                    ai += 1
                else:
                    ret.append(b[bi])
                    bi += 1
            return ret

        mergeSort([(i, a) for i, a in enumerate(nums)])
        return ans

1.18. Trie

1.18.1. Word Search II

https://leetcode.com/problems/word-search-ii/

若直接使用 backtracking, 需要针对每个 word 都做一遍 backtracking, 或者 backtracking 时判断当前 prefix 是否是某些 word 的前缀. 通过 trie 树可以快速完成这个判断

class Node:
    def __init__(self):
        self.children = {}
        self.word = None


class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        def addWord(root, word):
            for w in word:
                if w not in root.children:
                    root.children[w] = Node()
                root = root.children[w]
            root.word = word

        trie = Node()
        for word in words:
            addWord(trie, word)

        ans = set()
        visited = set()
        M, N = len(board), len(board[0])

        def dfs(x, y, root):
            if x < 0 or x >= M or y < 0 or y >= N:
                return
            if board[x][y] not in root.children:
                return
            if (x, y) in visited:
                return
            visited.add((x, y))
            root = root.children[board[x][y]]
            if root.word:
                ans.add(root.word)
            for xx, yy in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
                dfs(x + xx, y + yy, root)
            visited.remove((x, y))

        for x in range(M):
            for y in range(N):
                dfs(x, y, trie)

        return list(ans)

1.19. Two Pointers

1.19.1. Heaters

https://leetcode.com/problems/heaters/

class Solution:
    def findRadius(self, houses: List[int], heaters: List[int]) -> int:
        houses.sort()
        heaters.sort()
        # 1 2 3 4
        # 1     4
        hidx = 0
        ans = 0
        for h in houses:
            while hidx != len(heaters) - 1 and abs(h - heaters[hidx]) >= abs(
                h - heaters[hidx + 1]
            ):
                hidx += 1
            ans = max(ans, abs(h - heaters[hidx]))
        return ans

1.19.2. Remove Duplicates From Sorted Array II

https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/

class Solution:
    def removeDuplicates(self, nums):
        lo = 2
        for x in nums[2:]:
            # nums[lo - 1] != x 代表 lo 设为 x 后 lo-1 和 lo 不重复
            #
            # nums[lo - 2] != x 代表 lo 设为 x 后 lo-1 和 lo 重复, 但 lo-2,
            # lo-1, lo 不重复
            #
            # 实际上 nums[lo - 1] != x or nums[lo - 2] != x 可以简化为 nums[lo -
            # 2] != x
            #
            # 因为 nums 是非递减序列, lo-1!=x <=> lo-1!=x and lo-2!=x, 所以
            #
            # lo-1 !=x or lo-2!=x
            # <=> (lo-1!=x and lo-2!=x) or (lo-1==x and lo-2!=x)
            # <=> lo-2!=x and 1
            # <=> lo-2!=x
            if nums[lo - 1] != x or nums[lo - 2] != x:
                nums[lo] = x
                lo += 1
        return lo


if __name__ == "__main__":
    S = Solution()
    nums = [0, 0, 1, 1, 1, 1, 2, 3, 3]
    assert nums[: S.removeDuplicates(nums)] == [0, 0, 1, 1, 2, 3, 3]

1.20. Rolling Hash

1.20.1. Repeated DNA Sequences

https://leetcode.com/problems/repeated-dna-sequences/

robin karp

\(hash=x_0*power^(k-1)+x_1*power^(k-2)....+x_k-1*power^0\)

k 为序列的长度, power 为字符的种类. 这个公式实现是在求 `k 位 power 进制的十进制数值`, 例如这里求的是 10 位 4 进制转换为十进制

如果 hash 结果不大, 不需要进行模运算, 则不会有 hash 冲突

class Solution:
    def findRepeatedDnaSequences(self, s: str) -> List[str]:
        power = 4
        k = 10
        mul = power ** (k - 1)

        ans = set()
        hashes = set()
        currHash = 0
        lo = 0

        value = {"A": 1, "T": 2, "C": 3, "G": 4}
        for hi in range(len(s)):
            currHash = currHash * power + value[s[hi]]
            if hi - lo + 1 == k:
                if currHash in hashes:
                    ans.add(s[lo : hi + 1])
                hashes.add(currHash)
                currHash -= value[s[lo]] * mul
                lo += 1
        return list(ans)

Author: [email protected]
Date: 2024-04-12 Fri 16:14
Last updated: 2024-08-01 Thu 16:15

知识共享许可协议