top down DP

# time O(N) space O(N)
class Solution:
    def knightDialer(self, n: int) -> int:
        dp = [[[0]*3 for _ in range(4)] for _ in range(n+1)]
        self.m = 10**9+7
        self.dir = [(-1,-2),(-1,2),(1,-2),(1,2),(-2,-1),(-2,1),(2,-1),(2,1)]
        res = 0
        for i in range(4):
            for j in range(3):
                res = (res + self.dfs(dp,i,j,n)) % self.m
        return res
    def dfs(self,dp,i,j,n):
        if i < 0 or i >=4 or j < 0 or j >= 3 or (i==3 and j !=1) :
            return 0
        if n == 1:
            return 1
        if dp[n][i][j]: 
            return dp[n][i][j]
        for d in self.dir:
            dp[n][i][j] += self.dfs(dp,i+d[0],j+d[1],n-1)% self.m
        return dp[n][i][j]

optimization


class Solution:
    def knightDialer(self, n: int) -> int:
        self.m = 10**9+7
        g = [(4,6),6,8), (7,9), (4,8), (3,9,0), (), (1,7,0), (2,6), (1,3), (2,4)]
        res = 0
        memo = [[0]*10 for _ in range(n+1)]
        for i in range(10): 
            res = (res + self.dfs(i,g,n,memo)) % self.m
        return res
    def dfs(self,cur,g,n,memo):
        if n == 1:
            return 1
        if memo[n][cur] != 0: 
            return memo[n][cur]
        cnt = 0
        for nei in g[cur]:
            cnt = (cnt + self.dfs(nei,g,n-1,memo))% self.m
        memo[n][cur] = cnt 
        
        return cnt