记忆化搜索
定义
记忆化搜索其实很好理解。当我们用DFS,BFS暴力搜索的时候,有很多状态其实是重复计算了很多次的,这时候,我们就可以用空间换取时间,将这些状态全都装在数组里,当我们再次搜索到该状态的时候,便可以直接返回记录的值。
例题
e.g.1 检查是否有合法括号字符串路径
这道题就是万恶之源,本来我也不怎么想去学记忆化搜索的,毕竟暴搜不用动脑子嘛。但是在这场周赛中某蒟蒻遗憾2题,很懊恼,便学了记忆化(暂时不想碰dp)。
这题嘛,主要难点在怎么有效率地处理合法括号。一开始,我的想法是憨憨栈加DFS暴搜,过不了是必然的。于是,赛后就学到了一种巧妙的记录状态的方法,+1/-1。
接下来就是愉快的代码实现环节。由于Python是真的香,我这次的代码是py版本的。
class Solution:
def hasValidPath(self, grid: List[List[str]]) -> bool:
row,col=len(grid),len(grid[0])
if(row+col)%2==0 or grid[row-1][col-1]=='(' or grid[0][0]==')': return False
@lru_cache(None) #将每次计算结果记录在缓存里,相当于visit数组
def dfs(x:int,y:int,c:int)->bool:
if x==row-1 and y==col-1:return c==1
if row-x+col-y-1<c:return False
c+=1 if grid[x][y]=='(' else -1
return c>=0 and (x<row-1 and dfs(x+1,y,c) or y<col-1 and dfs(x,y+1,c))
return dfs(0,0,0)
e.g.2 网格中的最短路径
这题也是一道极好的记忆化例题。前面是个DFS,这里来一道BFS(此题DFS会T,因为DFS的绕路太多,而BFS扩散法可以有效减少绕路的情况)。
class Solution:
def shortestPath(self, grid: List[List[int]], k: int) -> int:
row,col=len(grid),len(grid[0])
if row==1 and col==1:return 0
k=min(k,row+col-3)
q=collections.deque([(0,0,k)])
visit=set([(0,0,k)])
step=0
while q:
step+=1
cur_len=len(q)
for _ in range(cur_len):
x,y,c=q.popleft()
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx,ny=x+dx,y+dy
if 0<=nx<row and 0<=ny<col:
if grid[nx][ny]==0 and (nx,ny,c) not in visit:
if nx==row-1 and ny==col-1:
return step
visit.add((nx,ny,c))
q.append((nx,ny,c))
elif grid[nx][ny]==1 and c>0 and (nx,ny,c-1) not in visit:
visit.add((nx,ny,c-1))
q.append((nx,ny,c-1))
return -1
e.g.3 切披萨的方案数
这题巧妙融合了二维前缀和与记忆化搜索,是道好题。
- 主要思路是DFS+记忆化记录还需要分几块(状态)
- 拿到题第一个想法就是二维前缀和,但是后来发现用右下角前缀和的话在切的时候需要倒序遍历(写起来有点麻烦),所以就学了个左上角前缀和
class Solution:
def ways(self, pizza: List[str], k: int) -> int:
row,col=len(pizza),len(pizza[0])
pre_apple=[[0 for _ in range(col)]for _ in range(row)]
for i in range(row-1,-1,-1):
for j in range(col-1,-1,-1):
if i==row-1 and j==col-1:
pre_apple[i][j]=1 if pizza[i][j]=='A' else 0
elif i==row-1:
pre_apple[i][j]=pre_apple[i][j+1]+1 if pizza[i][j]=='A' else pre_apple[i][j+1]
elif j==col-1:
pre_apple[i][j]=pre_apple[i+1][j]+1 if pizza[i][j]=='A' else pre_apple[i+1][j]
else:
pre_apple[i][j]=pre_apple[i+1][j]+pre_apple[i][j+1]-pre_apple[i+1][j+1]
if pizza[i][j]=='A':
pre_apple[i][j]+=1
dp = [[[-1 for _ in range(k+1)] for _ in range(col) ]for _ in range(row)]
def dfs(x,y,c:int)->int:
apple=pre_apple[x][y]
if dp[x][y][c]!=-1: #经历过这个状态,则直接返回相应值
return dp[x][y][c]
if pre_apple[x][y]<c: #剪枝,如果接下来的苹果不够c人分就返回
return 0
if c==1: #剩下一块不用切
return 1
dp[x][y][c]=0
for i in range(x+1,row):
if pre_apple[i][y]<apple: #如果切下来的里面有苹果
dp[x][y][c]+=dfs(i,y,c-1)
for i in range(y+1,col):
if pre_apple[x][i]<apple:
dp[x][y][c]+=dfs(x,i,c-1)
return dp[x][y][c]%(10**9+7)
return dfs(0,0,k)
e.g.4 滑雪
也是记忆化搜索比较经典的题目。记录每个搜到的点的可行步数状态。
static constexpr int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };
int main() {
int row = read(),col = read();
vector<vector<int>> grid(row, vector<int>(col));
vector<vector<int>> dp(row, vector<int>(col, 0));
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
grid[i][j] = read();
}
}
function<int(int, int)> dfs = [&](int x, int y)->int {
if (dp[x][y])return dp[x][y];
dp[x][y] = 1;
for (int i = 0; i < 4; ++i) {
int nx = x + dir[i][0], ny = y + dir[i][1];
if (nx >= 0 and nx < row and ny >= 0 and ny<col and grid[x][y]>grid[nx][ny]) {
grid[x][y] = max(dp[x][y], 1 + dp[nx][ny]);
}
}
return dp[x][y];
};
int ans = 0;
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
ans = max(dfs(i, j), ans);
}
}
printf("%d", ans);
return 0;
}
e,g.5 火柴拼正方形
由于这是一道medium,朴素暴搜还是可以过这道题的。当然,我们是要学习的,怎么能不用一下记忆化呢。
记忆化搜索的复杂度为,每根棍子有个状态(其他棍子选或不选)。
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
c=sum(matchsticks)
if c%4!=0:return False
h,n=c//4,len(matchsticks)
final=(1<<n)-1
@lru_cache(None)
def dfs(state,cur_l):
if cur_l==h:
cur_l=0
if state==final:return True
for i in range(n):
if not 1<<i & state and cur_l+matchsticks[i]<=h:
if dfs(1<<i|state,cur_l+matchsticks[i]):return True
return False
return dfs(0,0)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!