CSP认证 备考(python)

第一题

1.python的接收输入

import sys

line = sys.stdin.readline() # 假设输入"hello"后回车

print(repr(line)) # 输出: 'hello\n' (能看到\n)

循环读取接下来的n行,每一行是一个点的坐标 for _ in range(n): x, y = map(float, sys.stdin.readline().split())

2.十位数,个位数,十分位,百分位

个位:a = x%10

十位:b = (x//10)%10

十分位:c = int(x*10)%10

百分位:col = int(absZ * 100) % 10

3.最大共约数

def gcd(a, b):

while b:

a, b = b, a % b

return a

直接 import math

g = math.gca(a,b)

多个数的最大公约数:

4.二分查找
复制代码
def binary_search(nums, target):
    left, right = 0, len(nums)-1
    
    while left <= right:  # 包含等号
        mid = left + (right - left) // 2
        
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1  # 明确排除mid
        else:  # nums[mid] > target
            right = mid - 1  # 明确排除mid
    
    return -1
复制代码
while x < y:  # 当 x==y 时,只剩一个元素,就是峰值

第二题

def sliding_window_matrix_optimized(grid, k):

"""

计算 k*k 滑动窗口在矩阵上的和。

时间复杂度: O(m*n)

空间复杂度: O(m*n) (用于存储中间结果,可优化至 O(n))

"""

if not grid or not grid[0]:

return []

m, n = len(grid), len(grid[0])

if m < k or n < k:

return []

Step 1: 计算每一行的水平滑动窗口和

horizontal_sums 的大小为 m x (n - k + 1)

horizontal_sums = [[0] * (n - k + 1) for _ in range(m)]

for i in range(m):

初始化当前行第一个窗口的和

current_sum = sum(grid[i][:k])

horizontal_sums[i][0] = current_sum

滑动窗口:减去左边离开的,加上右边进入的

for j in range(1, n - k + 1):

current_sum = current_sum - grid[i][j - 1] + grid[i][j + k - 1]

horizontal_sums[i][j] = current_sum

Step 2: 在 horizontal_sums 的基础上,计算垂直滑动窗口和

result 的大小为 (m - k + 1) x (n - k + 1)

result = []

只需要遍历列的宽度 (n - k + 1)

width = n - k + 1

for j in range(width):

提取这一列所有的水平和

col_data = [horizontal_sums[i][j] for i in range(m)]

初始化这一列第一个垂直窗口的和

current_col_sum = sum(col_data[:k])

col_result = [current_col_sum]

垂直滑动

for i in range(1, m - k + 1):

current_col_sum = current_col_sum - col_data[i - 1] + col_data[i + k - 1]

col_result.append(current_col_sum)

将这一列的结果暂时存入,注意这里得到的是转置的结构,或者是列表的列表

这种写法为了代码清晰分了两步,实际为了 result 结构正确,通常会按行遍历

if j == 0:

result = [[x] for x in col_result]

else:

for i in range(len(col_result)):

result[i].append(col_result[i])

return result

def max_sliding_window(nums, k):

"""单调队列实现滑动窗口最大值"""

from collections import deque

n = len(nums)

if n * k == 0:

return []

if k == 1:

return nums

def clean_deque(i):

移除不在窗口中的元素

if dq and dq[0] == i - k:

dq.popleft()

移除小于当前元素的元素

while dq and nums[i] > nums[dq[-1]]:

dq.pop()

dq = deque()

max_idx = 0

for i in range(k):

clean_deque(i)

dq.append(i)

if nums[i] > nums[max_idx]:

max_idx = i

output = [nums[max_idx]]

for i in range(k, n):

clean_deque(i)

dq.append(i)

output.append(nums[dq[0]])

return output

2.递归公式构建

from functools import lru_cache

from typing import List

class RecursionTemplate:

"""递归+记忆化通用模板"""

def fibonacci(self, n: int) -> int:

"""斐波那契数列:f(n) = f(n-1) + f(n-2)"""

@lru_cache(None)

def dfs(x):

if x <= 1:

return x

return dfs(x-1) + dfs(x-2)

return dfs(n)

def combination_sum(self, nums: List[int], target: int) -> int:

"""组合求和:从nums中选取元素和为target的方案数"""

@lru_cache(None)

def dfs(remaining):

if remaining == 0:

return 1

if remaining < 0:

return 0

total = 0

for num in nums:

total += dfs(remaining - num)

return total

return dfs(target)

def dfs_with_params(self, grid: List[List[int]]) -> int:

"""带参数的DFS模板"""

m, n = len(grid), len(grid[0])

directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]

@lru_cache(None)

def dfs(x, y, visited_mask):

"""visited_mask可以用位运算表示访问状态"""

if x < 0 or x >= m or y < 0 or y >= n:

return 0

pos_mask = 1 << (x * n + y)

if visited_mask & pos_mask:

return 0

标记访问

new_mask = visited_mask | pos_mask

递归探索

best = 1 # 当前格子

for dx, dy in directions:

best = max(best, 1 + dfs(x+dx, y+dy, new_mask))

return best

return dfs(0, 0, 0)

3.分治

class DivideConquer:

"""分治算法模板"""

def merge_sort(self, nums: List[int]) -> List[int]:

"""归并排序"""

if len(nums) <= 1:

return nums

mid = len(nums) // 2

left = self.merge_sort(nums[:mid])

right = self.merge_sort(nums[mid:])

return self._merge(left, right)

def _merge(self, left: List[int], right: List[int]) -> List[int]:

result = []

i = j = 0

while i < len(left) and j < len(right):

if left[i] <= right[j]:

result.append(left[i])

i += 1

else:

result.append(right[j])

j += 1

result.extend(left[i:])

result.extend(right[j:])

return result

def max_subarray(self, nums: List[int]) -> int:

"""最大子数组和(分治版本)"""

def divide_conquer(l, r):

if l == r:

return nums[l]

mid = (l + r) // 2

分治递归

left_max = divide_conquer(l, mid)

right_max = divide_conquer(mid + 1, r)

计算跨越中点的最大子数组和

从中点向左

left_cross = nums[mid]

curr = nums[mid]

for i in range(mid - 1, l - 1, -1):

curr += nums[i]

left_cross = max(left_cross, curr)

从中点向右

right_cross = nums[mid + 1] if mid + 1 <= r else float('-inf')

curr = nums[mid + 1] if mid + 1 <= r else 0

for i in range(mid + 2, r + 1):

curr += nums[i]

right_cross = max(right_cross, curr)

cross_max = left_cross + right_cross

return max(left_max, right_max, cross_max)

return divide_conquer(0, len(nums) - 1) if nums else 0

def closest_pair(self, points: List[List[int]]) -> float:

"""最近点对问题"""

import math

def distance(p1, p2):

return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)

def brute_force(pts):

min_dist = float('inf')

for i in range(len(pts)):

for j in range(i + 1, len(pts)):

min_dist = min(min_dist, distance(pts[i], pts[j]))

return min_dist

def strip_closest(strip, d):

min_dist = d

strip.sort(key=lambda p: p[1])

for i in range(len(strip)):

j = i + 1

只需检查y坐标差小于d的点

while j < len(strip) and (strip[j][1] - strip[i][1]) < min_dist:

min_dist = min(min_dist, distance(strip[i], strip[j]))

j += 1

return min_dist

def divide_conquer(pts):

n = len(pts)

if n <= 3:

return brute_force(pts)

mid = n // 2

mid_point = pts[mid]

dl = divide_conquer(pts[:mid])

dr = divide_conquer(pts[mid:])

d = min(dl, dr)

检查跨越分割线的点对

strip = []

for p in pts:

if abs(p[0] - mid_point[0]) < d:

strip.append(p)

return min(d, strip_closest(strip, d))

points.sort(key=lambda p: p[0])

return divide_conquer(points)

4.最长回文子串
复制代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        def expand(l, r):
            while l >= 0 and r < len(s) and s[l] == s[r]:
                l -= 1
                r += 1
            return s[l + 1:r]
        
        res = ""
        for i in range(len(s)):
            # 奇数中心:i 是中心字符
            odd = expand(i, i)
            # 偶数中心:i 和 i+1 是中心两侧
            even = expand(i, i + 1)
            # 取最长
            res = max(res, odd, even, key=len)
        
        return res
复制代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
        
        # dp[i][j] 表示 s[i:j+1] 是否是回文
        dp = [[False] * n for _ in range(n)]
        start = 0
        max_len = 1
        
        # 初始化:所有单个字符都是回文
        for i in range(n):
            dp[i][i] = True
        
        # 先枚举右边界 j
        for j in range(1, n):
            # 再枚举左边界 i (从 0 到 j-1)
            for i in range(j):
                # 核心判断:首尾字符是否相等
                if s[i] == s[j]:
                    # 如果子串长度 <= 3,一定是回文
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        # 否则取决于去掉首尾后的子串
                        dp[i][j] = dp[i + 1][j - 1]
                else:
                    dp[i][j] = False
                
                # 如果是回文且更长,更新结果
                if dp[i][j] and (j - i + 1) > max_len:
                    max_len = j - i + 1
                    start = i
        
        return s[start:start + max_len]
5.KMP算法

最短回文串

def shortestPalindrome_optimized(s: str) -> str:

"""优化版本,减少内存使用"""

if not s:

return ""

rev_s = s[::-1]

寻找s + "#" + rev_s的前缀函数

pattern = s + "#" + rev_s

只计算前缀函数

n = len(pattern)

next_arr = [0] * n

for i in range(1, n):

j = next_arr[i - 1]

while j > 0 and pattern[i] != pattern[j]:

j = next_arr[j - 1]

if pattern[i] == pattern[j]:

j += 1

next_arr[i] = j

最长回文前缀长度

palindrome_len = next_arr[-1]

需要添加的前缀

add_front = s[palindrome_len:][::-1]

return add_front + s

class KMP:

"""KMP算法完整模板"""

def build_next(self, pattern: str) -> List[int]:

"""构建next数组(前缀函数)"""

n = len(pattern)

next_arr = [0] * n

j = 0

for i in range(1, n):

while j > 0 and pattern[i] != pattern[j]:

j = next_arr[j - 1]

if pattern[i] == pattern[j]:

j += 1

next_arr[i] = j

return next_arr

def search(self, text: str, pattern: str) -> List[int]:

"""在text中搜索pattern的所有出现位置"""

if not pattern:

return [0]

next_arr = self.build_next(pattern)

m, n = len(text), len(pattern)

result = []

j = 0

for i in range(m):

while j > 0 and text[i] != pattern[j]:

j = next_arr[j - 1]

if text[i] == pattern[j]:

j += 1

if j == n:

result.append(i - n + 1)

j = next_arr[j - 1]

return result

def repeated_substring_pattern(self, s: str) -> bool:

"""判断字符串是否由重复子串构成"""

n = len(s)

if n <= 1:

return False

next_arr = self.build_next(s)

如果存在重复子串,则n % (n - next_arr[-1]) == 0

return next_arr[-1] != 0 and n % (n - next_arr[-1]) == 0

6.二叉树最近公共祖先

(包含节点不在树中的情况)

Definition for a binary tree node.

class TreeNode:

def init(self, x):

self.val = x

self.left = None

self.right = None

#哈希表法

class Solution:

def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':

if not root:

return None

parents = {root:None}

stack = [root]

while stack and (p not in parents or q not in parents):

node = stack.pop()

if node.left:

parents[node.left] = node

stack.append(node.left)

if node.right:

parents[node.right] = node

stack.append(node.right)

if p not in parents or q not in parents:

return None

ancestor = set()

while p:

ancestor.add(p)

p = parents[p]

while q not in ancestor:

q = parents[q]

return q

#递归法

复制代码
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        self.find_p = False
        self.find_q = False

        def f(root):
            if not root:
                return None

            left = f(root.left)
            right = f(root.right)

            if root==p:
                self.find_p=True
                return root
            if root==q:
                self.find_q=True
                return root


            if left and right:
                return root
            
            return left if left else right

        res = f(root)
        return res if self.find_p and self.find_q else None
深度优先搜索

class Solution:

def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:

result = []

path = []

def dfs(node: Optional[TreeNode], target: int):

if not node: return

path.append(node.val)

if not node.left and not node.right:

if target == node.val:

result.append(path[:])

if node.left:

dfs(node.left, target - node.val)

if node.right:

dfs(node.right, target - node.val)

path.pop()

dfs(root, targetSum)

return result

第三题

1.队列/栈

from collections import deque

class StackQueueTemplates:

"""栈和队列常用模板"""

def monotonic_stack(self, nums: List[int]) -> List[int]:

"""单调栈:下一个更大元素"""

n = len(nums)

result = [-1] * n

stack = [] # 存储索引

for i in range(n):

while stack and nums[i] > nums[stack[-1]]:

idx = stack.pop()

result[idx] = nums[i]

stack.append(i)

return result

def sliding_window_queue(self, nums: List[int], k: int) -> List[int]:

"""单调队列实现滑动窗口最大值"""

n = len(nums)

if n == 0:

return []

dq = deque()

result = []

for i in range(n):

移除不在窗口中的元素

if dq and dq[0] == i - k:

dq.popleft()

维护单调递减队列

while dq and nums[dq[-1]] < nums[i]:

dq.pop()

dq.append(i)

当窗口形成时记录结果

if i >= k - 1:

result.append(nums[dq[0]])

return result

def daily_temperatures(self, temperatures: List[int]) -> List[int]:

"""每日温度(下一个更高温度的天数)"""

n = len(temperatures)

result = [0] * n

stack = [] # 存储(温度, 索引)

for i, temp in enumerate(temperatures):

while stack and temp > stack[-1][0]:

_, idx = stack.pop()

result[idx] = i - idx

stack.append((temp, i))

return result

2.树构建

中序遍历与后续遍历还原树

class Solution:

def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:

dic = {val:index for index,val in enumerate(inorder)}

def build(in_l,in_r,post_l,post_r):

if in_l>in_r or post_l>post_r:

return None

value = postorder[post_r]

index = dic[value]

length = index-in_l

root = TreeNode(value)

root.left = build(in_l,index-1,post_l,post_l+length-1)

root.right = build(index+1,in_r,post_l+length,post_r-1)

return root

return build(0,len(inorder)-1,0,len(inorder)-1)

3.散列/哈希表

class HashTemplates:

"""哈希表相关模板"""

def two_sum(self, nums: List[int], target: int) -> List[int]:

"""两数之和"""

hashmap = {}

for i, num in enumerate(nums):

complement = target - num

if complement in hashmap:

return [hashmap[complement], i]

hashmap[num] = i

return []

def subarray_sum(self, nums: List[int], k: int) -> int:

"""和为K的子数组个数"""

prefix_sum = 0

count = 0

hashmap = {0: 1} # 前缀和为0出现了1次

for num in nums:

prefix_sum += num

查找是否存在prefix_sum - k的前缀和

if prefix_sum - k in hashmap:

count += hashmap[prefix_sum - k]

更新当前前缀和的出现次数

hashmap[prefix_sum] = hashmap.get(prefix_sum, 0) + 1

return count

def longest_consecutive(self, nums: List[int]) -> int:

"""最长连续序列"""

if not nums:

return 0

num_set = set(nums)

longest = 0

for num in num_set:

只从序列的起点开始计算

if num - 1 not in num_set:

current_num = num

current_streak = 1

while current_num + 1 in num_set:

current_num += 1

current_streak += 1

longest = max(longest, current_streak)

return longest

第四题

1.最短路径-树

import heapq

from typing import List, Tuple

class GraphShortestPath:

"""图的最短路径模板"""

def dijkstra(self, n: int, edges: List[List[int]], start: int) -> List[int]:

"""

Dijkstra算法 - 邻接表实现

edges: [[u, v, w], ...] u->v权重w

"""

构建邻接表

graph = [[] for _ in range(n)]

for u, v, w in edges:

graph[u].append((v, w))

graph[v].append((u, w)) # 无向图

初始化距离数组

dist = [float('inf')] * n

dist[start] = 0

优先队列 (距离, 节点)

pq = [(0, start)]

while pq:

current_dist, u = heapq.heappop(pq)

如果当前距离大于记录的距离,跳过

if current_dist > dist[u]:

continue

遍历邻居

for v, w in graph[u]:

new_dist = current_dist + w

if new_dist < dist[v]:

dist[v] = new_dist

heapq.heappush(pq, (new_dist, v))

return dist

def floyd_warshall(self, n: int, edges: List[List[int]]) -> List[List[int]]:

"""Floyd-Warshall算法"""

初始化距离矩阵

dist = [[float('inf')] * n for _ in range(n)]

for i in range(n):

dist[i][i] = 0

for u, v, w in edges:

dist[u][v] = w

dist[v][u] = w # 无向图

动态规划

for k in range(n):

for i in range(n):

for j in range(n):

if dist[i][k] + dist[k][j] < dist[i][j]:

dist[i][j] = dist[i][k] + dist[k][j]

return dist

def kruskal_mst(self, n: int, edges: List[List[int]]) -> List[List[int]]:

"""Kruskal算法求最小生成树"""

按权重排序

edges.sort(key=lambda x: x[2])

parent = list(range(n))

def find(x):

if parent[x] != x:

parent[x] = find(parent[x])

return parent[x]

def union(x, y):

root_x = find(x)

root_y = find(y)

if root_x != root_y:

parent[root_x] = root_y

return True

return False

mst = []

total_weight = 0

for u, v, w in edges:

if union(u, v):

mst.append([u, v, w])

total_weight += w

if len(mst) == n - 1:

break

return mst, total_weight

2.树上的公共节点

class TreeLCA:

"""树上最近公共祖先模板"""

def init(self, n: int, edges: List[List[int]], root: int = 0):

self.n = n

self.log = (n).bit_length()

self.parent = [[-1] * n for _ in range(self.log)]

self.depth = [0] * n

构建邻接表

self.adj = [[] for _ in range(n)]

for u, v in edges:

self.adj[u].append(v)

self.adj[v].append(u)

BFS初始化深度和父节点

stack = [root]

visited = [False] * n

visited[root] = True

while stack:

u = stack.pop()

for v in self.adj[u]:

if not visited[v]:

visited[v] = True

self.parent[0][v] = u

self.depth[v] = self.depth[u] + 1

stack.append(v)

倍增预处理

for k in range(1, self.log):

for v in range(n):

if self.parent[k-1][v] != -1:

self.parent[k][v] = self.parent[k-1][self.parent[k-1][v]]

def lca(self, u: int, v: int) -> int:

"""查询u和v的最近公共祖先"""

if self.depth[u] < self.depth[v]:

u, v = v, u

将u提到和v同一深度

diff = self.depth[u] - self.depth[v]

for k in range(self.log):

if diff & (1 << k):

u = self.parent[k][u]

if u == v:

return u

同时向上跳

for k in range(self.log - 1, -1, -1):

if self.parent[k][u] != self.parent[k][v]:

u = self.parent[k][u]

v = self.parent[k][v]

return self.parent[0][u]

def distance(self, u: int, v: int) -> int:

"""计算树上两点间距离"""

lca_node = self.lca(u, v)

return self.depth[u] + self.depth[v] - 2 * self.depth[lca_node]

Definition for a binary tree node.

class TreeNode:

def init(self, x):

self.val = x

self.left = None

self.right = None

class Solution:

def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':

if not root:

return None

parents = {root:None}

stack = [root]

while stack and (p not in parents or q not in parents):

node = stack.pop()

if node.left:

parents[node.left] = node

stack.append(node.left)

if node.right:

parents[node.right] = node

stack.append(node.right)

if p not in parents or q not in parents:

return None

ancestor = set()

while p:

ancestor.add(p)

p = parents[p]

while q not in ancestor:

q = parents[q]

return q

class Solution:

def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:

find_p = False

find_q = False

def f(root):

nonlocal find_p

nonlocal find_q

if not root:

return None

left = f(root.left)

right = f(root.right)

if root==p:

find_p = True

return root

if root==q:

find_q = True

return root

if left and right:

return root

return left if left else right

res = f(root)

return res if find_p and find_q else None

3.二分排序

class BinarySearch:

"""二分查找通用模板"""

def binary_search(self, nums: List[int], target: int) -> int:

"""标准二分查找"""

left, right = 0, len(nums) - 1

while left <= right:

mid = left + (right - left) // 2

if nums[mid] == target:

return mid

elif nums[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1

def lower_bound(self, nums: List[int], target: int) -> int:

"""第一个大于等于target的位置"""

left, right = 0, len(nums)

while left < right:

mid = left + (right - left) // 2

if nums[mid] >= target:

right = mid

else:

left = mid + 1

return left

def upper_bound(self, nums: List[int], target: int) -> int:

"""第一个大于target的位置"""

left, right = 0, len(nums)

while left < right:

mid = left + (right - left) // 2

if nums[mid] > target:

right = mid

else:

left = mid + 1

return left

def search_range(self, nums: List[int], target: int) -> List[int]:

"""在排序数组中查找元素的第一个和最后一个位置"""

start = self.lower_bound(nums, target)

if start == len(nums) or nums[start] != target:

return [-1, -1]

end = self.upper_bound(nums, target) - 1

return [start, end]

def binary_search_answer(self, nums: List[int], k: int) -> int:

"""

二分答案模板

问题:将数组分成k个子数组,最小化最大子数组和

"""

def can_split(max_sum):

"""检查是否能在最大子数组和为max_sum的情况下分成k个子数组"""

current_sum = 0

count = 1

for num in nums:

if current_sum + num > max_sum:

count += 1

current_sum = num

if count > k:

return False

else:

current_sum += num

return True

left, right = max(nums), sum(nums)

while left < right:

mid = left + (right - left) // 2

if can_split(mid):

right = mid

else:

left = mid + 1

return left

4.容斥原理

class InclusionExclusion:

"""容斥原理模板"""

def count_divisible(self, n: int, divisors: List[int]) -> int:

"""

计算1到n中能被至少一个除数整除的数的个数

"""

m = len(divisors)

total = 0

遍历所有非空子集

for mask in range(1, 1 << m):

lcm_val = 1

bits = 0

for i in range(m):

if mask & (1 << i):

bits += 1

lcm_val = self.lcm(lcm_val, divisors[i])

if lcm_val > n: # 超过范围,可以提前终止

break

计算交集大小

count = n // lcm_val

根据子集大小添加或减去

if bits % 2 == 1:

total += count

else:

total -= count

return total

def lcm(self, a: int, b: int) -> int:

"""最小公倍数"""

return a * b // self.gcd(a, b)

def gcd(self, a: int, b: int) -> int:

"""最大公约数"""

while b:

a, b = b, a % b

return a

def count_coprime(self, n: int, m: int) -> int:

"""计算1到m中与n互质的数的个数(欧拉函数)"""

质因数分解

temp = n

primes = []

p = 2

while p * p <= temp:

if temp % p == 0:

primes.append(p)

while temp % p == 0:

temp //= p

p += 1

if temp > 1:

primes.append(temp)

容斥原理计算

total = 0

k = len(primes)

for mask in range(1, 1 << k):

product = 1

bits = 0

for i in range(k):

if mask & (1 << i):

bits += 1

product *= primes[i]

count = m // product

if bits % 2 == 1:

total += count

else:

total -= count

return m - total

5.状态机DP

LeetCode1397. 找到所有好字符串,1931. 用三种颜色给网格涂色,1220. 统计元音字母序列

LeetCode123.买卖股票的最佳时机 III,714.买卖股票的最佳时机含手续费,309.最佳买卖股票时机含冷冻期,72.编辑距离

第五题

动态规划

1.背包问题(完全/不完全)

class Knapsack:

"""背包问题完整模板"""

def zero_one_knapsack(self, weights: List[int], values: List[int], capacity: int) -> int:

"""01背包问题"""

n = len(weights)

dp = [0] * (capacity + 1)

for i in range(n):

逆向遍历,确保每个物品只选一次

for w in range(capacity, weights[i] - 1, -1):

dp[w] = max(dp[w], dp[w - weights[i]] + values[i])

return dp[capacity]

def unbounded_knapsack(self, weights: List[int], values: List[int], capacity: int) -> int:

"""完全背包问题"""

n = len(weights)

dp = [0] * (capacity + 1)

for i in range(n):

正向遍历,允许重复选择

for w in range(weights[i], capacity + 1):

dp[w] = max(dp[w], dp[w - weights[i]] + values[i])

return dp[capacity]

def multi_knapsack(self, weights: List[int], values: List[int], counts: List[int], capacity: int) -> int:

"""多重背包问题(二进制优化)"""

n = len(weights)

二进制拆分

new_weights = []

new_values = []

for i in range(n):

k = 1

remaining = counts[i]

while remaining >= k:

new_weights.append(weights[i] * k)

new_values.append(values[i] * k)

remaining -= k

k <<= 1

if remaining > 0:

new_weights.append(weights[i] * remaining)

new_values.append(values[i] * remaining)

01背包

return self.zero_one_knapsack(new_weights, new_values, capacity)

def knapsack_scheme(self, weights: List[int], values: List[int], capacity: int) -> List[int]:

"""输出具体方案"""

n = len(weights)

dp = [[0] * (capacity + 1) for _ in range(n + 1)]

for i in range(1, n + 1):

for w in range(capacity + 1):

if weights[i-1] <= w:

dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1])

else:

dp[i][w] = dp[i-1][w]

回溯找方案

res = []

w = capacity

for i in range(n, 0, -1):

if dp[i][w] != dp[i-1][w]:

res.append(i-1)

w -= weights[i-1]

return res[::-1] # 返回选择的物品索引

编辑距离
2.反悔贪心

LeetCode 630

import heapq

class RegretGreedy:

"""反悔贪心模板"""

def schedule_course(self, courses: List[List[int]]) -> int:

"""

课程安排III:选择最多的课程

courses[i] = [duration, lastDay]

"""

按截止时间排序

courses.sort(key=lambda x: x[1])

max_heap = [] # 最大堆,存储已选课程的持续时间

current_time = 0

for duration, last_day in courses:

if current_time + duration <= last_day:

heapq.heappush(max_heap, -duration)

current_time += duration

elif max_heap and -max_heap[0] > duration:

反悔:替换掉持续时间最长的课程

longest = -heapq.heappop(max_heap)

current_time = current_time - longest + duration

heapq.heappush(max_heap, -duration)

return len(max_heap)

def max_profit_jobs(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int:

"""最大收益工作安排"""

jobs = sorted(zip(startTime, endTime, profit), key=lambda x: x[1])

n = len(jobs)

dp = [0] * (n + 1)

for i in range(1, n + 1):

s, e, p = jobs[i-1]

找到结束时间不超过s的最后一个工作

j = i - 1

while j >= 1 and jobs[j-1][1] > s:

j -= 1

选择当前工作或不选

dp[i] = max(dp[i-1], dp[j] + p)

return dp[n]

3.最大堆

class HeapTemplates:

"""堆操作模板"""

def median_finder(self):

"""数据流的中位数(双堆法)"""

import heapq

class MedianFinder:

def init(self):

self.small = [] # 最大堆(用负数实现)

self.large = [] # 最小堆

def addNum(self, num: int) -> None:

if len(self.small) == len(self.large):

heapq.heappush(self.large, -heapq.heappushpop(self.small, -num))

else:

heapq.heappush(self.small, -heapq.heappushpop(self.large, num))

def findMedian(self) -> float:

if len(self.small) == len(self.large):

return (-self.small[0] + self.large[0]) / 2

else:

return self.large[0]

return MedianFinder()

def kth_largest(self, nums: List[int], k: int) -> int:

"""第K大的元素"""

import heapq

min_heap = []

for num in nums:

heapq.heappush(min_heap, num)

if len(min_heap) > k:

heapq.heappop(min_heap)

return min_heap[0] if min_heap else -1

def merge_k_sorted(self, lists: List[List[int]]) -> List[int]:

"""合并K个有序链表/数组"""

import heapq

heap = []

初始化堆,存储(值, 列表索引, 元素索引)

for i, lst in enumerate(lists):

if lst:

heapq.heappush(heap, (lst[0], i, 0))

result = []

while heap:

val, list_idx, elem_idx = heapq.heappop(heap)

result.append(val)

如果当前列表还有下一个元素

if elem_idx + 1 < len(lists[list_idx]):

next_val = lists[list_idx][elem_idx + 1]

heapq.heappush(heap, (next_val, list_idx, elem_idx + 1))

return result

4.如何找树的割点

class ArticulationPoints:

"""寻找无向图的割点(Tarjan算法)"""

def find_cut_points(self, n: int, edges: List[List[int]]) -> List[int]:

"""

寻找图中的所有割点

割点:移除该点后,图的连通分量数增加

"""

构建邻接表

graph = [[] for _ in range(n)]

for u, v in edges:

graph[u].append(v)

graph[v].append(u)

visited = [False] * n

disc = [0] * n # 发现时间

low = [0] * n # 可回溯到的最早发现时间

parent = [-1] * n

time = 0

articulation = [False] * n

def dfs(u):

nonlocal time

children = 0

visited[u] = True

disc[u] = low[u] = time

time += 1

for v in graph[u]:

if not visited[v]:

children += 1

parent[v] = u

dfs(v)

更新low值

low[u] = min(low[u], low[v])

判断是否为割点

1. 根节点且有两个以上子节点

if parent[u] == -1 and children > 1:

articulation[u] = True

2. 非根节点且low[v] >= disc[u]

if parent[u] != -1 and low[v] >= disc[u]:

articulation[u] = True

elif v != parent[u]: # 回退边

low[u] = min(low[u], disc[v])

for i in range(n):

if not visited[i]:

dfs(i)

return [i for i in range(n) if articulation[i]]

5.树删除节点后如何划分

树上子树区间(Euler 序)与前缀和

class TreeSubtree:

"""子树统计与划分"""

def init(self, n: int, edges: List[List[int]]):

self.n = n

self.adj = [[] for _ in range(n)]

for u, v in edges:

self.adj[u].append(v)

self.adj[v].append(u)

欧拉序

self.euler_in = [0] * n

self.euler_out = [0] * n

self.euler_path = []

self.time = 0

self.dfs_euler(0, -1)

子树大小

self.subtree_size = [0] * n

self.dfs_size(0, -1)

def dfs_euler(self, u: int, parent: int):

"""DFS求欧拉序"""

self.euler_in[u] = self.time

self.euler_path.append(u)

self.time += 1

for v in self.adj[u]:

if v != parent:

self.dfs_euler(v, u)

self.euler_out[u] = self.time - 1

def dfs_size(self, u: int, parent: int) -> int:

"""计算子树大小"""

size = 1

for v in self.adj[u]:

if v != parent:

size += self.dfs_size(v, u)

self.subtree_size[u] = size

return size

def is_ancestor(self, u: int, v: int) -> bool:

"""判断u是否是v的祖先(欧拉序)"""

return self.euler_in[u] <= self.euler_in[v] <= self.euler_out[u]

def subtree_range(self, u: int) -> Tuple[int, int]:

"""返回节点u的子树在欧拉序中的范围"""

return (self.euler_in[u], self.euler_out[u])

def remove_node_partition(self, u: int) -> List[int]:

"""

删除节点u后,划分成的各个连通块的大小

注意:删除节点后,除了子树外,还有父节点所在的连通块

"""

sizes = []

父节点所在的连通块大小

parent_component = self.n - self.subtree_size[u]

if parent_component > 0:

sizes.append(parent_component)

每个子节点的子树大小

for v in self.adj[u]:

if self.is_ancestor(v, u): # v是u的子节点

sizes.append(self.subtree_size[v])

return sizes

def find_centroid(self) -> List[int]:

"""寻找树的重心(删除后最大子树最小的节点)"""

centroids = []

min_max_subtree = self.n

def dfs_centroid(u: int, parent: int):

nonlocal min_max_subtree, centroids

max_subtree = 0

total = 1

for v in self.adj[u]:

if v != parent:

dfs_centroid(v, u)

subtree_size = self.subtree_size[v]

max_subtree = max(max_subtree, subtree_size)

total += subtree_size

父节点所在的连通块

parent_subtree = self.n - total

max_subtree = max(max_subtree, parent_subtree)

if max_subtree < min_max_subtree:

min_max_subtree = max_subtree

centroids = [u]

elif max_subtree == min_max_subtree:

centroids.append(u)

return total

dfs_centroid(0, -1)

return centroids

6.动态树/查询子树

class DynamicTree:

"""支持子树更新的动态树"""

def init(self, n: int, values: List[int], edges: List[List[int]]):

self.n = n

self.values = values

构建邻接表

self.adj = [[] for _ in range(n)]

for u, v in edges:

self.adj[u].append(v)

self.adj[v].append(u)

欧拉序

self.in_time = [0] * n

self.out_time = [0] * n

self.euler = []

self.time = 0

self.dfs_euler(0, -1)

树状数组(Fenwick Tree)

self.bit = [0] * (n + 2)

初始化树状数组

for i, node in enumerate(self.euler):

self._add(i + 1, self.values[node])

def dfs_euler(self, u: int, parent: int):

"""DFS求欧拉序"""

self.in_time[u] = self.time

self.euler.append(u)

self.time += 1

for v in self.adj[u]:

if v != parent:

self.dfs_euler(v, u)

self.out_time[u] = self.time - 1

def _add(self, idx: int, delta: int):

"""树状数组更新"""

while idx <= self.n:

self.bit[idx] += delta

idx += idx & -idx

def _sum(self, idx: int) -> int:

"""树状数组前缀和"""

res = 0

while idx > 0:

res += self.bit[idx]

idx -= idx & -idx

return res

def update_subtree(self, u: int, delta: int):

"""更新节点u的整个子树"""

l = self.in_time[u] + 1 # 树状数组从1开始

r = self.out_time[u] + 1

self._add(l, delta)

if r + 1 <= self.n:

self._add(r + 1, -delta)

def query_subtree(self, u: int) -> int:

"""查询节点u的子树和"""

idx = self.in_time[u] + 1

return self._sum(idx)

def update_node(self, u: int, new_val: int):

"""更新单个节点的值"""

old_val = self.values[u]

delta = new_val - old_val

self.values[u] = new_val

self.update_subtree(u, delta)

def query_path(self, u: int, v: int) -> int:

"""查询u到v路径上的和(需要LCA)"""

实现路径查询需要LCA,这里给出简化版本

实际应用中可能需要树链剖分

pass

一些细节问题

1.count = defaultdict(int)指定默认值类型

class Utils:

"""常用工具函数"""

@staticmethod

def quick_sort(nums: List[int]) -> List[int]:

"""快速排序"""

if len(nums) <= 1:

return nums

pivot = nums[len(nums) // 2]

left = [x for x in nums if x < pivot]

middle = [x for x in nums if x == pivot]

right = [x for x in nums if x > pivot]

return Utils.quick_sort(left) + middle + Utils.quick_sort(right)

@staticmethod

def union_find(n: int):

"""并查集模板"""

parent = list(range(n))

size = [1] * n

def find(x: int) -> int:

if parent[x] != x:

parent[x] = find(parent[x])

return parent[x]

def union(x: int, y: int) -> bool:

root_x = find(x)

root_y = find(y)

if root_x == root_y:

return False

按秩合并

if size[root_x] < size[root_y]:

root_x, root_y = root_y, root_x

parent[root_y] = root_x

size[root_x] += size[root_y]

return True

def connected(x: int, y: int) -> bool:

return find(x) == find(y)

return find, union, connected

@staticmethod

def topological_sort(n: int, edges: List[List[int]]) -> List[int]:

"""拓扑排序"""

from collections import deque

graph = [[] for _ in range(n)]

indegree = [0] * n

for u, v in edges:

graph[u].append(v)

indegree[v] += 1

queue = deque([i for i in range(n) if indegree[i] == 0])

result = []

while queue:

u = queue.popleft()

result.append(u)

for v in graph[u]:

indegree[v] -= 1

if indegree[v] == 0:

queue.append(v)

return result if len(result) == n else [] # 有环则返回空

相关推荐
databook1 小时前
数据会说谎?三大推断方法帮你“审问”数据真相
后端·python·数据分析
是Dream呀1 小时前
Python圣诞特辑:打造一棵会唱歌、会下雪的魔法圣诞树
开发语言·python·pygame
kyle~2 小时前
排序---常用排序算法汇总
数据结构·算法·排序算法
威哥爱编程2 小时前
使用 TRAE SOLO 一分钟写个简易版飞机大战
python·trae·solo
AndrewHZ2 小时前
【遥感图像入门】DEM数据处理核心算法与Python实操指南
图像处理·python·算法·dem·高程数据·遥感图像·差值算法
CoderYanger2 小时前
动态规划算法-子序列问题(数组中不连续的一段):28.摆动序列
java·算法·leetcode·动态规划·1024程序员节
有时间要学习2 小时前
面试150——第二周
数据结构·算法·leetcode
freedom_1024_2 小时前
红黑树底层原理拆解
开发语言·数据结构·b树