classSolution: deflengthOfLongestSubstring(self, s: str) -> int: left = 0 window = set() # 维护从下标 left 到下标 right 的字符 ans = 0 for right, c inenumerate(s): # 在加入 c 之前,先移出窗口内的 c while c in window: window.remove(s[left]) left += 1 window.add(c) ans = max(ans, right - left + 1) return ans
classSolution: deflongestOnes(self, nums: List[int], k: int) -> int: left = ans = 0 window_0 = 0 for right, num inenumerate(nums): # right 先进入窗口 if num == 0: window_0 += 1 while window_0 > k: if nums[left] == 0: window_0 -= 1 left += 1
classSolution: defminSubArrayLen(self, target: int, nums: list[int]) -> int: n = len(nums) left = cur = 0 ans = n + 1 for right, num inenumerate(nums): cur += num while cur >= target: ans = min(ans, right - left + 1) cur -= nums[left] left += 1 return ans if ans <= n else0
for _ inrange(k): if left < 0: right += 1 elif right >= n: left -= 1 elif x - arr[left] <= arr[right] - x: left -= 1 else: right += 1 return arr[left + 1 : right]
二分答案
答案本身是一个数,并且随着这个数变大或变小,是否可行呈现单调性
题目要求优化一个数值?
这个答案有没有明确范围?
假设答案是 mid,能不能写 check(mid)?
check(mid) 是否具有单调性?
如果都满足就是
求最小可行值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defcheck(val):
FalseFalseFalseTrueTrueTrue ↑ 找第一个 True left = 最小可能答案 right = 最大可能答案
classSolution: defsmallestDivisor(self, nums: list[int], threshold: int) -> int: defcheck(div): total = 0 for num in nums: total += (num + div - 1) // div if total > threshold: returnFalse returnTrue
left = 1 right = max(nums) while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return left
classSolution: defrepairCars(self, ranks: List[int], cars: int) -> int: defcheck(t): total = 0 for rank in ranks: total += isqrt(t // rank) if total >= cars: returnTrue returnFalse
left = 1 right = min(ranks) * cars * cars while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return left
求最大可行值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defcheck(val):
TrueTrueTrueFalseFalse ↑ 最后一个 True
left = 最小可能答案 right = 最大可能答案 while left < right: mid = (left + right + 1) // 2# 让mid偏右
classSolution: defmaximumCandies(self, candies: List[int], k: int) -> int: defcheck(num): cnt = 0 for x in candies: cnt += x // num if cnt >= k: returnTrue returnFalse
left = 0 right = max(candies) while left < right: mid = (left + right + 1) // 2 if check(mid): left = mid else: right = mid - 1 return left
classSolution: defminCost(self, n: int, edges: List[List[int]], k: int) -> int: defcheck(max_cost): uf = UnionFind(n) for u, v, w in edges: if w <= max_cost: if uf.union(u, v) and uf.count <= k: returnTrue returnFalse
if k == n: return0
left = 0 right = max(w for _, _, w in edges) while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return left
while i < len(matrix) and j > -1: if matrix[i][j] <= num: # 当前行 0~j 都 <= num cnt += j + 1 i += 1 # 已经至少有 k 个 if cnt >= k: returnTrue else: j -= 1 returnFalse
left, right = matrix[0][0], matrix[-1][-1] while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return left
classSolution: defmaxTotalValue(self, value: list[int], decay: list[int], m: int) -> int: defcheck(low): # >= low 的价值是否至少有 m 个 cnt = 0 for v, d inzip(value, decay): if v >= low: cnt += (v - low) // d + 1 if cnt >= m: returnTrue returnFalse
left = 0 MOD = 10**9 + 7 right = max(value) while left < right: mid = (left + right + 1) // 2# 找最大的 low if check(mid): left = mid else: right = mid - 1 low = left ans = 0 for v, d inzip(value, decay): if v > low: k = (v - low - 1) // d + 1 m -= k ans += (v * 2 - d * (k - 1)) * k ans //= 2 ans += m * low # 剩余的用low补齐 return ans % MOD
defcheck(mx): cnt = i = 0 for j, x inenumerate(nums): while x - nums[i] > mx: i += 1 cnt += j - i # 计算比 mx 更小的个数 # 这里 i 无需重置,因为得到的肯定比之前大 if cnt >= k: returnTrue returnFalse
left, right = 0, nums[-1] - nums[0] while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return left
classSolution: defcountBeautifulPairs(self, nums: List[int]) -> int: ans = 0
# cnt[d]:前面首位数字为 d 的数有多少个 cnt = [0] * 10
for num in nums: # 当前数作为右端点 j last = num % 10
# 枚举前面可能的首位数字 for first inrange(1, 10): if gcd(first, last) == 1: ans += cnt[first] first = num while first >= 10: first //= 10 # 当前数加入历史,供后面的 j 使用 cnt[first] += 1 return ans
classSolution: defsimilarPairs(self, words: list[str]) -> int: cnt = defaultdict(int) ans = 0 for word in words: # contain = frozenset(word) contain = 0 for c in word: contain |= 1 << (ord(c) - ord("a")) # 第 k 位为 1 的数 ans += cnt[contain] cnt[contain] += 1 return ans
classSolution: defmaxSubArray(self, nums: list[int]) -> int: ans = float("-inf") min_prefix = prefix_sum = 0 for x in nums: prefix_sum += x ans = max(ans, prefix_sum - min_prefix) min_prefix = min(min_prefix, prefix_sum) return ans
classSolution: deflongestBalanced(self, s: str) -> int: n = len(s)
# 一种字母 ans = i = 0 while i < n: start = i i += 1 while i < n and s[i] == s[i - 1]: i += 1 ans = max(ans, i - start)
# 两种字母 # 用+1 -1 的方式找平衡 deff(x, y): nonlocal ans i = 0 while i < n: pos = {0: i - 1} d = 0 while i < n and (s[i] == x or s[i] == y): d += 1if s[i] == x else -1 if d in pos: ans = max(ans, i - pos[d]) else: pos[d] = i i += 1 i += 1
f("a", "b") f("a", "c") f("b", "c")
# 三种字母 # 记录差值,就只有两项 pos = {(0, 0): -1} cnt = defaultdict(int) for i, c inenumerate(s): cnt[c] += 1 p = (cnt["a"] - cnt["b"], cnt["b"] - cnt["c"]) if p in pos: ans = max(ans, i - pos[p]) else: pos[p] = i return ans
classSolution: defdistance(self, nums: List[int]) -> List[int]: group = defaultdict(list) for i, x inenumerate(nums): group[x].append(i) # 相同元素分到同一组 ans = [0] * len(nums)
for a in group.values(): n = len(a) prefix = [0] * (n + 1) for i inrange(1, n + 1): prefix[i] = prefix[i - 1] + a[i - 1] for j, target inenumerate(a): left = j * target - prefix[j] right = prefix[n] - prefix[j] - (n - j) * target ans[target] = left + right return ans
classSolution: defmatrixBlockSum(self, mat: list[list[int]], k: int) -> list[list[int]]: m = len(mat) n = len(mat[0])
prefix = [[0] * (n + 1) for _ inrange(m + 1)]
for i inrange(m): for j inrange(n): prefix[i + 1][j + 1] = ( prefix[i][j + 1] + prefix[i + 1][j] - prefix[i][j] + mat[i][j] )
ans = [[0] * n for _ inrange(m)] for i inrange(m): for j inrange(n): r1 = max(0, i - k) c1 = max(0, j - k) r2 = min(i + k, m - 1) c2 = min(j + k, n - 1)
ans = [[0] * n for _ inrange(n)] for i inrange(n): for j inrange(n): up = ans[i - 1][j] if i > 0else0 left = ans[i][j - 1] if j > 0else0 left_up = ans[i - 1][j - 1] if i > 0and j > 0else0
ans[i][j] = diff[i][j] + up + left - left_up return ans
classSolution: defbackspaceCompare(self, s: str, t: str) -> bool: defbuild(s): st = [] for c in s: if c!= "#": st.append(c) elif st: st.pop() return st return build(s) == build(t)
for i inrange(n - 1, -1, -1): suffix_min[i] = min(s[i], suffix_min[i + 1])
st = [] ans = []
for i, c inenumerate(s): # 操作1:s 的首字符进入 t st.append(c) # s[0:i+1] 已经处理完了 # 所以后面剩余的是 s[i+1:] while st and st[-1] <= suffix_min[i + 1]: ans.append(st.pop())
classSolution: defclearStars(self, s: str) -> str: s = list(s) stack = [[] for _ inrange(26)] for i, c inenumerate(s): if c != "*": stack[ord(c) - ord("a")].append(i) continue for st in stack: if st: s[st.pop()] = "*" break
classSolution: defremoveSubstring(self, s: str, k: int) -> str: st = [] # 栈中保存 [字符, 连续出现次数] for b in s: if st and st[-1][0] == b: st[-1][1] += 1# 连续相同括号个数 +1 else: st.append([b, 1]) # 新的括号
# 栈顶的 k 个右括号与栈顶下面的 k 个左括号抵消 if b == ")"andlen(st) > 1and st[-1][1] == k and st[-2][1] >= k: st.pop() st[-1][1] -= k if st[-1][1] == 0: st.pop() return"".join(b * cnt for b, cnt in st)
classSolution: defasteroidCollision(self, asteroids: list[int]) -> list[int]: st = [] for x in asteroids: # 向右的小行星不会立刻和前面的碰撞 if x > 0: st.append(x) continue
# x < 0,只有栈顶 > 0 时才会发生碰撞 while st and st[-1] > 0: top = st[-1]
if top < -x: # 栈顶更小,被 x 撞掉 st.pop() continue
elif top == -x: # 一样大,两者都消失 st.pop()
# top >= -x 时,x 都没了 break
else: # while 没有通过 break 结束 # 说明 x 活下来了 st.append(x) return st
classSolution: defisValid(self, s: str) -> bool: iflen(s)%2: returnFalse mp = {")": "(", "]": "[", "}": "{"} st = [] for c in s: if c notin mp: st.append(c) elifnot st or st.pop() != mp[c]: returnFalse returnnot st
classSolution: deflongestValidParentheses(self, s: str) -> int: st = [-1] ans = 0 for i, c inenumerate(s): if c == "(": st.append(i) else: st.pop() if st: ans = max(i - st[-1], ans) else: st.append(i) return ans
classSolution: defclumsy(self, n: int) -> int: st = [n] op = 0# 记录下个符号,"+""-"就append for x inrange(n - 1, 0, -1): if op == 0: st[-1] *= x elif op == 1: st[-1] = int(st[-1] / x) elif op == 2: st.append(x) else: st.append(-x) op = (op + 1) % 4
classSolution: defmyAtoi(self, s: str) -> int: n = len(s) i = 0
# 1. 跳过前导空格 while i < n and s[i] == " ": i += 1 # 2. 处理符号 sign = 1 if i < n and s[i] in"+-": sign = 1if s[i] == "+"else -1 i += 1 MX = (1 << 31) - 1 num = 0
# 3. 连续读取数字 while i < n and"0" <= s[i] <= "9": num = num * 10 + int(s[i]) # 4. 截断 if num > MX: return MX if sign > 0else -(1 << 31) i += 1 return sign * num
classSolution: defcalculate(self, s: str) -> int: st = [] num = 0 sign = "+"
for i, c inenumerate(s): if c.isdigit(): num = num * 10 + int(c) if c in"+-*/"or i == len(s) - 1: # 这里的 sign 是num的前一个符号 if sign == "+": st.append(num) elif sign == "-": st.append(-num) elif sign == "*": st[-1] *= num else: st[-1] = int(st[-1] / num) sign = c num = 0 returnsum(st)
classSolution: defnextGreaterElement(self, nums1: list[int], nums2: list[int]) -> list[int]: st = [] mp = {} for num in nums2: while st and st[-1] < num: mp[st.pop()] = num st.append(num)
ans = [] for num in nums1: ans.append(mp.get(num,-1)) return ans
classSolution: defmaximumSumOfHeights(self, maxHeights: List[int]) -> int: n = len(maxHeights)
left = [0] * n right = [0] * n
st = []
for i, h inenumerate(maxHeights): while st and maxHeights[st[-1]] > h: st.pop() if st: j = st[-1] left[i] = left[j] + (i - j) * h else: left[i] = (i + 1) * h st.append(i)
st = []
for i inrange(n - 1, -1, -1): h = maxHeights[i] while st and maxHeights[st[-1]] > h: st.pop() if st: j = st[-1] right[i] = right[j] + (j - i) * h else: right[i] = (n - i) * h st.append(i)
ans = 0
for i inrange(n): ans = max(ans, left[i] + right[i] - maxHeights[i])
for right inrange(len(nums)): while dq and nums[right] >= nums[dq[-1]]: dq.pop() dq.append(right) left = right - k + 1 if left < 0: continue if dq[0] < left: dq.popleft() ans.append(nums[dq[0]]) return ans
classSolution: deflongestSubarray(self, nums: list[int], limit: int) -> int: mx = deque() mn = deque() left = 0 ans = 0
for right inrange(len(nums)): while mx and nums[right] >= nums[mx[-1]]: mx.pop() while mn and nums[right] <= nums[mn[-1]]: mn.pop() mx.append(right) mn.append(right) while nums[mx[0]] - nums[mn[0]] > limit: left += 1 # 判断是否出队 if left > mx[0]: mx.popleft() if left > mn[0]: mn.popleft()
classSolution: deflastStoneWeight(self, stones: list[int]) -> int: heap = [-x for x in stones] heapq.heapify(heap) whilelen(heap) >= 2: y = -heapq.heappop(heap) x = -heapq.heappop(heap) if x == y: continue else: heapq.heappush(heap, -(y - x))
classSolution: defrob(self, nums: list[int]) -> int: deffun_rob(nums): n = len(nums) f0 = 0 f1 = 0 for i inrange(0, n): new_f = max(f1, f0 + nums[i]) f0 = f1 f1 = new_f return f1
classSolution: defmaxSubArray(self, nums: list[int]) -> int: ans = float("-inf") cur = 0 for x in nums: cur = max(cur, 0) + x ans = max(ans, cur) return ans
classSolution: defminimumTotal(self, triangle: list[list[int]]) -> int: m, n = len(triangle), len(triangle[-1]) dp = [[0] * (i + 1) for i inrange(n)] dp[-1] = triangle[-1] for i inrange(n - 2, -1, -1): for j, x inenumerate(triangle[i]): dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + x
classSolution: deffindTargetSumWays(self, nums: list[int], target: int) -> int: s = sum(nums) if target > s or (s - target) % 2: return0 bag = (s - target) // 2
dp = [0] * (bag + 1) dp[0] = 1 for num in nums: for j inrange(bag, num - 1, -1): dp[j] += dp[j - num]
classSolution: defreverseList(self, head: ListNode | None) -> ListNode | None: cur = head pre = None while cur: nxt = cur.next cur.next = pre pre = cur cur = nxt return pre
classSolution: defreverseKGroup(self, head: ListNode | None, k: int) -> ListNode | None: dummy = ListNode(0, head) group_prev = dummy # 起点的前一个 whileTrue: # 判断是否有 k 个节点 end = group_prev for _ inrange(k): end = end.next ifnot end: return dummy.next
# 保存下一组起点 group_next = end.next # 反转当前 k 个节点 pre = group_next # 重点,因为本来应该是None,这里直接接下一组的头 cur = group_prev.next tail = cur for _ inrange(k): nxt = cur.next cur.next = pre pre = cur cur = nxt # 重新连接 group_prev.next = pre group_prev = tail
classSolution: defswapNodes(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: cur = head for _ inrange(k - 1): cur = cur.next p1 = cur p2 = head
while cur.next: cur = cur.next p2 = p2.next p1.val, p2.val = p2.val, p1.val return head
classSolution: defdetectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if fast == slow: # 相遇 slow = head while fast isnot slow: # 再走 a 步 slow = slow.next fast = fast.next return slow returnNone
classSolution: defaddTwoNumbers( self, l1: Optional[ListNode], l2: Optional[ListNode] ) -> Optional[ListNode]: defreverse(head): cur = head pre = None while cur: nxt = cur.next cur.next = pre pre = cur cur = nxt return pre
h1 = reverse(l1) h2 = reverse(l2)
dummy = ListNode(0) cur = dummy add = 0 while h1 or h2 or add: val = add if h1: val += h1.val h1 = h1.next if h2: val += h2.val h2 = h2.next add = val // 10 cur.next = ListNode(val % 10) cur = cur.next
# 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 classSolution: defpreorderTraversal(self, root: Optional[TreeNode]) -> List[int]: ans = []
defdfs(node): if node isNone: return ans.append(node.val) dfs(node.left) dfs(node.right)
dfs(root) return ans classSolution: definorderTraversal(self, root: TreeNode | None) -> list[int]: ans = []
defdfs(node): if node isNone: return dfs(node.left) ans.append(node.val) dfs(node.right)
dfs(root) return ans
classSolution: defpostorderTraversal(self, root: Optional[TreeNode]) -> List[int]: ans = []
defdfs(node): if node isNone: return dfs(node.left) dfs(node.right) ans.append(node.val)
classSolution: deflowestCommonAncestor( self, root: "TreeNode", p: "TreeNode", q: "TreeNode" ) -> "TreeNode": if root isNoneor root is p or root is q: return root left = self.lowestCommonAncestor(root.left, p, q) right = self.lowestCommonAncestor(root.right, p, q)
dq = deque([root]) ans = [] while dq: n = len(dq) layer = [] for _ inrange(n): node = dq.popleft() if node.left: dq.append(node.left) if node.right: dq.append(node.right) layer.append(node.val) ans.append(layer) return ans
classSolution: defrightSideView(self, root: Optional[TreeNode]) -> List[int]: if root isNone: return [] dq = deque([root]) ans = [] while dq: n = len(dq) for i inrange(n): node = dq.popleft() if node.left: dq.append(node.left) if node.right: dq.append(node.right) if i == n - 1: ans.append(node.val) return ans
classSolution: defwordBreak(self, s: str, wordDict: List[str]) -> List[str]: word_set = set(wordDict) n = len(s) path = [] ans = []
defdfs(i, j): if i == n: if j == n: ans.append(" ".join(word for word in path)) return if s[j : i + 1] in word_set: path.append(s[j : i + 1]) dfs(i + 1, i + 1) path.pop() dfs(i + 1, j)
defdfs(row): if row == n: res = ["." * c + "Q" + "." * (n - c - 1) for c in path] ans.append(res) return for col inrange(n): if col in cols or row - col in diag1 or row + col in diag2: continue path.append(col) cols.add(col) diag1.add(row - col) diag2.add(row + col)
classSolution: defcombinationSum2(self, candidates: list[int], target: int) -> list[list[int]]: candidates.sort() ans = [] path = [] n = len(candidates)
defdfs(i, s): if s > target: return if i == n: if s == target: ans.append(path.copy()) return path.append(candidates[i]) dfs(i + 1, s + candidates[i]) path.pop() j = i + 1 while j < n and candidates[j] == candidates[i]: j += 1 dfs(j, s)
cnt = len(word) ans = False m, n = len(board), len(board[0]) used = [[False] * n for _ inrange(m)]
defdfs(i, j, k): nonlocal ans if ans or i < 0or i > m - 1or j < 0or j > n - 1or used[i][j]: return if board[i][j] != word[k]: return # 已经匹配到最后一个字符 if k == cnt - 1: ans = True return used[i][j] = True
dfs(i, j + 1, k + 1) dfs(i + 1, j, k + 1) dfs(i - 1, j, k + 1) dfs(i, j - 1, k + 1)
used[i][j] = False
for i inrange(m): for j inrange(n): dfs(i, j, 0) if ans: returnTrue returnFalse
classSolution: defgetMaximumGold(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) ans = 0 used = [[False] * n for _ inrange(m)]
defdfs(i, j, k): nonlocal ans if ( i > m - 1 or i < 0 or j > n - 1 or j < 0 or used[i][j] or grid[i][j] == 0 ): ans = max(ans, k) return k += grid[i][j] used[i][j] = True dfs(i, j + 1, k) dfs(i, j - 1, k) dfs(i + 1, j, k) dfs(i - 1, j, k) used[i][j] = False k -= grid[i][j]
for i inrange(n): l = r = i while l >= 0and r < n and s[l] == s[r]: l -= 1 r += 1 # 循环结束后,s[l+1] 到 s[r-1] 是回文串 if r - l - 1 > ans_right - ans_left: ans_left, ans_right = l + 1, r
for i inrange(n - 1): l, r = i, i + 1 while l >= 0and r < n and s[l] == s[r]: l -= 1 r += 1 if r - l - 1 > ans_right - ans_left: ans_left, ans_right = l + 1, r
for i inrange(2 * n - 1): l, r = i // 2, (i + 1) // 2 while l >= 0and r < n and s[l] == s[r]: l -= 1 r += 1 # 循环结束后,s[l+1] 到 s[r-1] 是回文串 if r - l - 1 > ans_right - ans_left: ans_left, ans_right = l + 1, r # 左闭右开区间
classSolution: defcountSubstrings(self, s: str) -> int: n = len(s) ans = 0 for i inrange(2 * n - 1): l, r = i // 2, (i + 1) // 2 while l >= 0and r < n and s[l] == s[r]: ans += 1 l -= 1 r += 1 return ans
classSolution: defpartitionLabels(self, s: str) -> list[int]: last = {c: i for i, c inenumerate(s)} # 每个字母最后出现的下标 ans = [] start = end = 0 for i, c inenumerate(s): end = max(end, last[c]) # 更新当前区间右端点的最大值 if i == end: # 当前区间合并完毕 ans.append(end - start + 1) start = i + 1 return ans
classSolution: defpartition(self, nums: List[int], left: int, right: int) -> int: i = randint(left, right) pivot = nums[i] nums[i], nums[left] = nums[left], nums[i] i, j = left + 1, right whileTrue: while i <= j and nums[i] < pivot: i += 1 # 此时 nums[i] >= pivot
while i <= j and nums[j] > pivot: j -= 1 # 此时 nums[j] <= pivot
if i >= j: break # 维持循环不变量 nums[i], nums[j] = nums[j], nums[i] i += 1 j -= 1 # 循环结束后 # [ pivot | <=pivot | >=pivot ] # ^ ^ ^ ^ # left j i right nums[left], nums[j] = nums[j], nums[left] return j
deffindKthLargest(self, nums: List[int], k: int) -> int: n = len(nums) target_index = n - k left, right = 0, n - 1 whileTrue: i = self.partition(nums, left, right) if i == target_index: return nums[i] if i > target_index: right = i - 1 else: left = i + 1
堆选择
1 2 3 4 5 6 7 8
classSolution: deffindKthLargest(self, nums: List[int], k: int) -> int: heap = [] for num in nums: heapq.heappush(heap, num) iflen(heap) > k: heapq.heappop(heap) return heap[0]