1. 程式人生 > 資訊 >馬斯克向投資者承諾:投資推特交易可獲得 3 倍回報,最高 10 倍

馬斯克向投資者承諾:投資推特交易可獲得 3 倍回報,最高 10 倍

定義

  記憶化搜尋其實很好理解。當我們用DFS,BFS暴力搜尋的時候,有很多狀態其實是重複計算了很多次的,這時候,我們就可以用空間換取時間,將這些狀態全都裝在數組裡,當我們再次搜尋到該狀態的時候,便可以直接返回記錄的值。

例題

e.g.1 檢查是否有合法括號字串路徑 (leetcode T2267)

  這道題就是萬惡之源,本來我也不怎麼想去學記憶化搜尋的,畢竟暴搜不用動腦子嘛。但是在這場周賽中某蒟蒻遺憾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 網格中的最短路徑 (leetcode T 1293)

  這題也是一道極好的記憶化例題。前面是個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 切披薩的方案數 (leetcode T1444)

  這題巧妙融合了二維字首和與記憶化搜尋,是道好題。

  • 主要思路是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)