# 前缀和
nums = [1,2,3,4,5]
n = len(nums)
pri = [0] * (n + 1)
for i in range(n):
pri[i + 1] = pri[i] + nums[i]
# [i,j] 区间的和 pri[j + 1] - pri[i]
for i in range(n):
for j in range(i, n):
print(pri[j + 1] - pri[i])
# 二维前缀和
nums = [[1,2,3],[2,3,4],[4,5,6]]
n = len(nums)
m = len(nums[0])
pri = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n):
for j in range(m):
pri[i + 1][j + 1] = pri[i + 1][j] + pri[i][j + 1] - pri[i][j] + nums[i][j]
# [x1][y1] 到 [x2][y2] 的子区间和是 只查询 不用遍历
# pri[x2 + 1][y2 + 1] + pri[x1][y1] - pri[x2 + 1][y1] - pri[x1][y2 + 1]
# 暴力 计算每个 长 a 宽 b 的子区间和
nums = [[1,2,3],[2,3,4],[4,4,5]]
a = 2
b = 2
n = len(nums)
m = len(nums[0])
for i in range(n - a + 1):
for j in range(m - b + 1):
su = 0
for x in range(i,i + a):
for y in range(j,j + b):
su += nums[x][y]
# 暴力 计算固定长度的和
nums = [1,23,34]
k = 2
n = len(nums)
for i in range(n - k + 1):
su = 0
for x in range(i, i + k):
su += nums[x]
# 差分 一堆数一次性加上 k
nums = [1,2,4,3]
op = [(1,2),(0,2)]
k = 2
n = len(nums)
d = [0] * (n + 1) # 差分数组
for x,y in op:
d[x] += k
d[y + 1] -= k
# pri = [0] * (n + 1) 使用前缀数组
# for i in range(n):
# pri[i + 1] = pri[i] + d[i]
# for i in range(1,n + 1):
# nums[i - 1] += pri[i]
su = 0
for i in range(n):
su += d[i]
nums[i] += d[i]
# 二维差分
nums = [[1,2,3],[4,2,3],[3,4,4]]
n = len(nums)
m = len(nums[0])
d = [[0] * (m + 1) for _ in range(n + 1)]
op = [(0,0,1,2),(0,1,2,2)]
for x1,y1,x2,y2 in op:
d[x1][y1] += k
d[x2 + 1][y2 + 1] += k
d[x2 + 1][y1] -= k
d[x1][y2 + 1] -= k
pri = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n):
for j in range(m):
pri[i + 1][j + 1] = pri[i + 1][j] + pri[i][j + 1] - pri[i][j] + d[i][j]
# 二维子区间和 pri[x2 + 1][y2 + 1] + pri[x1][y1] - pri[x2 + 1][y1] - pri[x1][y2 + 1]
# 二维前缀和 pri[i + 1][j + 1] = pri[i + 1][j] + pri[i][j + 1] - pri[i][j] + nums[i][j]
for i in range(n):
for j in range(m):
nums[i][j] += pri[i + 1][j + 1]
# 二分
import bisect
nums = [1,2,3,45,5]
nums.sort() # 必须是升序数组 有序
k = 2
a = bisect.bisect_left(nums,k) # 在 nums 中第一个大于等于 k 的位置
b = bisect.bisect_right(nums,k) # 在 nums 中第一个大于 k 的位置
# 可以用来找在一个数组中小于或大于一个数的数量
bisect.insort_left(nums,a) # 把 a 插到在 nums 数组中第一个大于等于它的位置
bisect.insort_right(nums,b) # 把 b 插到在 nums 数组中第一个大于它扥位置
left = 0
right = n - 1
while left <= right: # 第一个大于等于 k 的位置
mid = (left + right) // 2
if nums[mid] >= k: # 在解决实际问题中 这经常写一个函数来判断满不满足条件 然后寻找答案
right = mid - 1
else:
left = mid + 1
print(left)
# 栈 单调栈 找第一个大于它的数或小于它的数
# 可以找以一个数为最大值的区间
def right_bigger(nums): # 找第一个大于它的数 找大的 维护递减栈
stack = []
n = len(nums)
ans = [n] * n # 存的是索引
for i in range(n):
while len(stack) != 0 and nums[stack[-1]] < nums[i]:
ans[stack[-1]] = i
stack.pop()
stack.append(i) # 操作的都是索引
return ans
def left_bigger(nums):
stack = []
n = len(nums)
ans = [-1] * n
for i in range(n - 1, -1, -1):
while len(stack) != 0 and nums[stack[-1]] < nums[i]:
ans[stack[-1]] = i
stack.pop()
stack.append(i)
return ans
# 队列
# BFS 求最短路 迷宫
from collections import deque
import sys
arr = [
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
]
# 1 墙 0 路
q = deque([(0,0,0)]) # x y steps
qu = set([(0,0)]) # x y
d = [(0,1),(0,-1),(1,0),(-1,0)]
n = len(arr)
m = len(arr[0])
while q:
x, y, steps = q.popleft()
if x == n - 1 and y == m - 1:
print(steps)
sys.exit()
for dx, dy in d:
nx = dx + x
ny = dy + y
if 0 <= nx < n and 0 <= ny < m and arr[nx][ny] == 0 and (nx,ny) not in qu:
q.append((nx,ny,steps + 1))
qu.add((nx,ny))
print(-1) # 没找到
# 可以以当前 q 的长度 作为一轮 for 循环 将当前 q 全部遍历一遍 为了解决一轮一秒类似于
# 可以把所有起点都加入 q
# 双向邻接表用列表 其他的可以用字典
# 这个可以用堆完美代替
# 单调队列 找固定区间的最值
def queue_bigger(nums,k): # 找一个区间的最大值 维持递减队列
q = deque()
ans = [] # 放值
n = len(nums)
for i in range(n):
while len(q) != 0 and nums[q[-1]] <= nums[i]:
q.pop() # 弹出队尾元素
q.append(i)
if q[0] <= i - k: # 维护长度 k
q.popleft() # 弹出队首元素
if i >= k - 1:
ans.append(nums[q[0]]) # 放值
return ans
# 只有单调栈求左边最值时需要倒序
def queue_samller(nums,k):
q = deque()
n = len(nums)
ans = [] # 放值
for i in range(n):
while len(q) != 0 and nums[q[-1]] >= nums[i]:
q.pop() # 弹出队尾元素索引
q.append(i)
if q[0] <= i - k:
q.popleft() # 弹出队首元素索引
if i >= k - 1:
ans.append(nums[q[0]])
return ans
# 哈希表
from collections import Counter
a = Counter(nums) # 自动统计列表 nums 每个元素的数量
# 是字典
a = set(nums) # 只保留 nums 中不重复的元素 去重
# 是集合
a.add(1) # 加元素
a.remove(1) # 减元素
from collections import defaultdict
a = defaultdict(dict) # 默认是字典的字典
# DFS
# 模版题 全排列
# but 内置函数
from itertools import permutations
nums = [1,2,3,4]
for perm in permutations(nums):
print(perm)
n = len(nums)
path = [0] * n
vis = [False] * n
def dfs(depth):
if depth == n:
print(path)
return
for i in range(n):
if not vis[i]:
vis[i] = True
path[depth] = nums[i]
dfs(depth + 1)
vis[i] = False
dfs(0) # 不要忘了调用好吧
# 求组合
from itertools import combinations
for i in range(1, n + 1): # 组合的个数
for com in combinations(nums,i):
print(com)
path = []
def dfs(depth):
if depth == n:
print(path)
return
# 不选
dfs(depth + 1)
# 选
path.append(nums[depth])
dfs(depth + 1)
path.pop()
dfs(0)
# DFS 就是一个一直回溯的思想 不符合了就返回 返回 剪枝 返回 一直返回
# 标记 去标记
# 连通块
arr = [
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
]
def dfs(x,y):
if 0 <= x < n and 0 <= y < m and arr[x][y] == 1:
arr[x][y] = 0 # 淹没
return 1 + dfs(x+1,y) + dfs(x-1,y) + dfs(x,y+1) + dfs(x,y-1)
return 0 # 非陆地返回 0
ans = []
n = len(arr)
m = len(arr[0])
for i in range(n):
for j in range(m):
if arr[i][j] == 1:
ans.append(dfs(i,j))
# 最长公共子序列
s = 'fjsuf'
t = 'sdfjs'
n = len(s)
m = len(t)
dp = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n):
for j in range(m):
if s[i] == t[j]:
dp[i + 1][j + 1] = dp[i][j] + 1
else:
dp[i + 1][j + 1] = max(dp[i + 1][j],dp[i][j + 1])
print(dp[n][m])
# 输出
ans = []
i = n
j = m
while i > 0 and j > 0:
if s[i - 1] == t[j - 1]:
ans.append(s[i - 1])
i -= 1
j -= 1
elif dp[i][j] == dp[i][j - 1]:
j -= 1
else:
i -= 1
ans = ''.join(ans[::-1]) # 反转
print(ans)
# 背包问题
# 0 1 背包
total = 1000
w = [23,43,434]
v = [2,3,4]
n = len(w)
dp = [[0] * (total + 1) for _ in range(n + 1)]
for i in range(1,n + 1):
for j in range(1,total + 1):
if j >= w[i - 1]:
dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i - 1]] + v[i - 1])
else:
dp[i][j] = dp[i - 1][j]
print(dp[n][total])
# 输出
ans = []
cap = total
for i in range(n,0,-1):
if cap >= w[i - 1] and dp[i][cap] == dp[i - 1][cap - w[i - 1]] + v[i - 1]:
ans.append(i - 1)
cap -= w[i - 1]
ans = ans[::-1] # 恢复原来顺序
# 如果 w 补零 在 w v 时就不用减 1 了
# 0 1 完全背包
total = 1000
w = [1,23,4,5]
v = [2,3,4,5]
n = len(w)
dp = [[0] * (total + 1) for _ in range(n + 1)]
for i in range(1,n + 1):
for j in range(1,total + 1):
if j >= w[i - 1]:
dp[i][j] = max(dp[i - 1][j],dp[i][j - w[i - 1]] + v[i - 1])
else:
dp[i][j] = dp[i - 1][j]
print(dp[n][total])
# 输出
ans = []
cap = total
for i in range(n,0,-1):
while cap >= w[i - 1] and dp[i][cap] == dp[i][cap - w[i - 1]] + v[i - 1]:
ans.append(i - 1) # 完全背包对一个物品可以一直选 直到不能选
cap -= w[i - 1]
ans = ans[::-1] # 恢复原来顺序
# 不同组合的个数
# 达到容量时的个数 就是选该值 或者不选
total = 1000
w = [1,3,4,5,3]
n = len(w)
dp = [[0] * (total + 1) for _ in range(n + 1)]
# 用前 i 个物品凑出容量为 j 的组合数
dp[0][0] = 1 # 容量为 0 时有一种情况 就是空
# dp[i][0] = 1 就是 0 不能忽略容量为 0 的情况 j 要从 0 开始 上面的都可以因为是背包容量 0 时什么都装不了 可以从 1 开始
for i in range(1,n + 1):
for j in range(total + 1): # 从 0 开始
if j >= w[i - 1]:
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - w[i - 1]]
else:
dp[i][j] = dp[i - 1][j]
print(dp[n][total])