leetcode
Table of Contents
- 1. leetcode
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])
其中一般情况下外层循环是背包或物品并没有限制, 除非:
- 排列问题需要背包放在外层循环
- 组合问题需要背包放在内层循环
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/
完全背包, 但是是一个组合问题 (而不是排列问题), 需要把背包放在内层循环
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
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 可以工作
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
使用 stack 来处理乘除法的优化级:
例如针对 1+2*3, stack 的情况为:
[1] -> [1,2] -> [1, 6]
- 使用 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)