Showing posts with label DP - Space Optimization. Show all posts
Showing posts with label DP - Space Optimization. Show all posts

LeetCode 877 - Stone Game


Related: LeetCode 486 - Predict the Winner
https://leetcode.com/problems/stone-game/description/
Alex and Lee play a game with piles of stones.  There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].
The objective of the game is to end with the most stones.  The total number of stones is odd, so there are no ties.
Alex and Lee take turns, with Alex starting first.  Each turn, a player takes the entire pile of stones from either the beginning or the end of the row.  This continues until there are no more piles left, at which point the person with the most stones wins.
Assuming Alex and Lee play optimally, return True if and only if Alex wins the game.

Example 1:
Input: [5,3,4,5]
Output: true
Explanation: 
Alex starts first, and can only take the first 5 or the last 5.
Say he takes the first 5, so that the row becomes [3, 4, 5].
If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alex, so we return true.

It's tricky when we have even number of piles of stones. You may not have this condition in an interview.
Follow-up:
What if piles.length can be odd?
What if we want to know exactly the diffenerce of score?
Then we need to solve it with DP.
X.
Alex clearly always wins the 2 pile game. With some effort, we can see that she always wins the 4 pile game.
If Alex takes the first pile initially, she can always take the third pile. If she takes the fourth pile initially, she can always take the second pile. At least one of first + third, second + fourth is larger, so she can always win.
We can extend this idea to N piles. Say the first, third, fifth, seventh, etc. piles are white, and the second, fourth, sixth, eighth, etc. piles are black. Alex can always take either all white piles or all black piles, and one of the colors must have a sum number of stones larger than the other color.

Approach 1: Just return true

Alex is first to pick pile.
piles.length is even, and this lead to an interesting fact:
Alex can always pick odd piles or always pick even piles!
For example,
If Alex wants to pick even indexed piles piles[0], piles[2], ....., piles[n-2],
he picks first piles[0], then Lee can pick either piles[1] or piles[n - 1].
Every turn, Alex can always pick even indexed piles and Lee can only pick odd indexed piles.
In the description, we know that sum(piles) is odd.
If sum(piles[even]) > sum(piles[odd]), Alex just picks all evens and wins.
If sum(piles[even]) < sum(piles[odd]), Alex just picks all odds and wins.
So, Alex always defeats Lee in this game.
https://leetcode.com/articles/stone-game/
dp[i][j] means the biggest number of stones you can get more than opponent picking piles in piles[i] ~ piles[j].
You can first pick piles[i] or piles[j].
  1. If you pick piles[i], your result will be piles[i] - dp[i + 1][j]
  2. If you pick piles[j], your result will be piles[j] - dp[i][j - 1]
So we get:
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
We start from smaller subarray and then we use that to calculate bigger subarray.
Note that take evens or take odds, it's just an easy strategy to win when the number of stones is even.
It's not the best solution!
I didn't find a tricky solution when the number of stones is odd (maybe there is).
    public boolean stoneGame(int[] p) {
        int n = p.length;
        int[][] dp  = new int[n][n];
        for (int i = 0; i < n; i++) dp[i][i] = p[i];
        for (int d = 1; d < n; d++)
            for (int i = 0; i < n - d; i++)
                dp[i][i + d] = Math.max(p[i] - dp[i + 1][i + d], p[i + d] - dp[i][i + d - 1]);
        return dp[0][n - 1] > 0;
    }

Let's change the game so that whenever Lee scores points, it deducts from Alex's score instead.
Let dp(i, j) be the largest score Alex can achieve where the piles remaining are piles[i], piles[i+1], ..., piles[j]. This is natural in games with scoring: we want to know what the value of each position of the game is.
We can formulate a recursion for dp(i, j) in terms of dp(i+1, j) and dp(i, j-1), and we can use dynamic programming to not repeat work in this recursion. (This approach can output the correct answer, because the states form a DAG (directed acyclic graph).)
Algorithm
When the piles remaining are piles[i], piles[i+1], ..., piles[j], the player who's turn it is has at most 2 moves.
The person who's turn it is can be found by comparing j-i to N modulo 2.
If the player is Alex, then she either takes piles[i] or piles[j], increasing her score by that amount. Afterwards, the total score is either piles[i] + dp(i+1, j), or piles[j] + dp(i, j-1); and we want the maximum possible score.
If the player is Lee, then he either takes piles[i] or piles[j], decreasing Alex's score by that amount. Afterwards, the total score is either -piles[i] + dp(i+1, j), or -piles[j] + dp(i, j-1); and we want the minimum possible score.

  • Time Complexity: O(N^2), where N is the number of piles.
  • Space Complexity: O(N^2), the space used storing the intermediate results of each subgame. 

  public boolean stoneGame(int[] piles) {
    int N = piles.length;

    // dp[i+1][j+1] = the value of the game [piles[i], ..., piles[j]].
    int[][] dp = new int[N + 2][N + 2];
    for (int size = 1; size <= N; ++size)
      for (int i = 0; i + size <= N; ++i) {
        int j = i + size - 1;
        int parity = (j + i + N) % 2; // j - i - N; but +x = -x (mod 2)
        if (parity == 1)
          dp[i + 1][j + 1] = Math.max(piles[i] + dp[i + 2][j + 1], piles[j] + dp[i + 1][j]);
        else
          dp[i + 1][j + 1] = Math.min(-piles[i] + dp[i + 2][j + 1], -piles[j] + dp[i + 1][j]);
      }

    return dp[1][N] > 0;
  }


  public boolean stoneGame(int[] piles) {
    Map<Integer, Long> gains = new HashMap<>();
    dfs(gains, piles, 0, piles.length - 1);
    return gains.get(piles.length - 1) > 0;
  }

  private long dfs(Map<Integer, Long> gains, int[] piles, int start, int end) {
    int pos = start * piles.length + end;
    if (gains.containsKey(pos)) {
      return gains.get(pos);
    }
    if (start < 0 || end >= piles.length) {
      return 0;// ?
    }

    if (start == end) {
      gains.put(pos, (long) piles[start]);
      return piles[start];
    }

    long gain = Math.max(piles[start] - dfs(gains, piles, start + 1, end),
        piles[end] - dfs(gains, piles, start, end - 1));
    gains.put(pos, gain);
    return gain;

  }

X.
Approach 3: 1D DP
Follow up: Use only O(N) space?
Simplify to 1D DP.


    public boolean stoneGame(int[] p) {
        int[] dp = Arrays.copyOf(p, p.length);;
        for (int d = 1; d < p.length; d++)
            for (int i = 0; i < p.length - d; i++)
                dp[i] = Math.max(p[i] - dp[i + 1], p[i + d] - dp[i]);
        return dp[0] > 0;
    }

https://leetcode.com/problems/stone-game/discuss/154610/C%2B%2BJavaPython-DP-or-Just-return-true
Alex is first to pick pile.
piles.length is even, and this lead to an interesting fact:
Alex can always pick odd piles or always pick even piles!
For example,
If Alex wants to pick even indexed piles piles[0], piles[2], ....., piles[n-2],
he picks first piles[0], then Lee can pick either piles[1] or piles[n - 1].
Every turn, Alex can always pick even indexed piles and Lee can only pick odd indexed piles.
In the description, we know that sum(piles) is odd.
If sum(piles[even]) > sum(piles[odd]), Alex just picks all evens and wins.
If sum(piles[even]) < sum(piles[odd]), Alex just picks all odds and wins.


So, Alex always defeats Lee in this game.


Alex clearly always wins the 2 pile game. With some effort, we can see that she always wins the 4 pile game.
If Alex takes the first pile initially, she can always take the third pile. If she takes the fourth pile initially, she can always take the second pile. At least one of first + third, second + fourth is larger, so she can always win.
We can extend this idea to N piles. Say the first, third, fifth, seventh, etc. piles are white, and the second, fourth, sixth, eighth, etc. piles are black. Alex can always take either all white piles or all black piles, and one of the colors must have a sum number of stones larger than the other color.
Hence, Alex always wins the game.

  public boolean stoneGame(int[] piles) {
    return true;
  }

X. DFS + cache
记忆化递归的缺点:1.有可能爆栈;2.无法降维,而DP是可以降维的。
https://leetcode.com/problems/stone-game/discuss/154660/Java-This-is-minimax-%2B-dp-(fully-detailed-explanation-%2B-generalization-%2B-easy-understand-code)
The problem was made a "trick problem," because Alex always wins, but if you want to learn the general idea behind these problems here is the explanation for other cases.
class Solution {
    int [][][] memo;
    public boolean stoneGame(int[] piles) {
        memo = new int[piles.length + 1][piles.length + 1][2];
        for(int [][] arr : memo)
            for(int [] subArr : arr)
                Arrays.fill(subArr, -1);
        
        return (helper(0, piles.length - 1, piles, 1) >= 0);
    }
    
    public int helper(int l, int r, int [] piles, int ID){
        if(r < l)
            return 0;
        if(memo[l][r][ID] != -1)
            return memo[l][r][ID];
        
        int next = Math.abs(ID - 1);
        if(ID == 1)
            memo[l][r][ID] = Math.max(piles[l] + helper(l + 1, r, piles, next), piles[r] + helper(l, r - 1, piles, next));
        else
            memo[l][r][ID] = Math.min(-piles[l] + helper(l + 1, r, piles, next), -piles[r] + helper(l, r - 1, piles, next));
        
        return memo[l][r][ID];
    }
}
This is a Minimax problem. Each player plays optimally to win, but you can't greedily choose the optimal strategy so you have to try all strategies, this is DP now.
What does it mean for Alex to win? Alex will win if score(Alex) >= score(Lee), but this also means score(Alex) - score(Lee) >= 0, so here you have a common parameter which is a score variable. The score parameter really means score = score(Alex) - score(Lee).
Now, if each player is suppoed to play optimally, how do you decide this with one variable?
Well since Alex is playing optimally, he wants to maximize the score variable because remember, Alex only wins if score = score(Alex) - score(Lee) >= 0 Alex should addto the score because he wants to maximize it.
Since Lee is also playing optimally, he wants to minimize the score variable, since if the score variable becomes negative, Lee has more individual score than Alex. But since we have only one variable, Lee should damage the score (or in other words, subtract from the score).
Now, let's think of the brute force solution. You are at the current state, say this is Alex's turn. Alex can choose either left or right, but he can't greedily pick so you try both of them, this is O(2^n) for runtime.
But realize the state you are in. You can easily memoize the this, the varying parameters are l, r, ID, where ID is the player ID (1 for Alex, 0 for Lee), hence you can make a DP/Cache table and return answer if you have discovered the state.
Finally, in your main function you call this helper function and check if you were able to get a score >= 0.

https://blog.csdn.net/fuxuemingzhu/article/details/82390672
双函数
使用递归求解。这个解法是左程云的算法讲解。

思路就是,作为先选的人,要选择从前面选和从后面选两种方案中的最大值。
作为后选的人,要选择前面选和从后面选两种方案中的最小值。

alex是先选的,所以调用f函数判断他能否赢。

直接递归超时,所以我是用了记忆化搜索减少了时间,就能通过了。
    def stoneGame(self, piles):
        """
        :type piles: List[int]
        :rtype: bool
        """
        if not piles:
            return False
        self.F = [[0 for i in range(len(piles))] for j in range(len(piles))]
        self.S = [[0 for i in range(len(piles))] for j in range(len(piles))]
        _sum = sum(piles)
        alex = self.f(piles, 0, len(piles) - 1)
        return alex > _sum / 2
     
    def f(self, piles, i, j):
        """
        先选
        """
        if i == j:
            return piles[i]
        if self.F[i][j] != 0:
            return self.F[i][j]
        curr = max(piles[i] + self.s(piles, i + 1, j), piles[j] + self.s(piles, i, j - 1))
        self.F[i][j] = curr
        return curr
     
    def s(self, piles, i, j):
        """
        后选
        """
        if i == j:
            return 0
        if self.S[i][j] != 0:
            return self.S[i][j]
        curr = min(self.f(piles, i + 1, j), self.f(piles, i, j - 1))
        self.S[i][j] = curr
        return curr
用map来完成记忆化搜索,也能通过:

class Solution(object):
 
    def stoneGame(self, piles):
        """
        :type piles: List[int]
        :rtype: bool
        """
        self.f_map, self.s_map = dict(), dict()
        _sum = sum(piles)
        alex = self.f(piles, 0, len(piles)-1)
        print(alex, _sum)
        return alex > _sum / 2.0
     
    def f(self, piles, start, end):
        if start == end:
            return piles[start]
        if (start, end) not in self.f_map:
            f_val = max(piles[start] + self.s(piles, start+1, end), piles[end] + self.s(piles, start, end-1))
            self.f_map[(start, end)] = f_val
        return self.f_map[(start, end)]
 
    def s(self, piles, start, end):
        if start == end:
            return 0
        if (start, end) not in self.s_map:
            s_val = min(self.f(piles, start+1, end), self.f(piles, start, end-1))
            self.s_map[(start, end)] = s_val
        return self.s_map[(start, end)]

LeetCode 576 - Out of Boundary Paths


http://bookshadow.com/weblog/2017/05/07/leetcode-out-of-boundary-paths/
There is an m by n grid with a ball. Given the start coordinate (i,j) of the ball, you can move the ball to adjacent cell or cross the grid boundary in four directions (up, down, left, right). However, you can at most move N times. Find out the number of paths to move the ball out of grid boundary. The answer may be very large, return it after mod 109 + 7.
Example 1:
Input:m = 2, n = 2, N = 2, i = 0, j = 0
Output: 6
Explanation:

Example 2:
Input:m = 1, n = 3, N = 3, i = 0, j = 1
Output: 12
Explanation:

Note:
  1. Once you move the ball out of boundary, you cannot move it back.
  2. The length and height of the grid is in range [1,50].
  3. N is in range [0,50].

下面这种方法虽然也是用的DP解法,但是DP数组的定义和上面的不一样,这种解法相当于使用了BFS搜索,以(i, j)为起始点,其中dp[k][x][y]表示用了k步,进入(x, y)位置的路径数,由于dp[k][x][y]只依赖于dp[k-1][x][y],所以我们可以用一个二维dp数组来代替,初始化dp[i][j]为1,总共N步,进行N次循环,每次都新建一个mxn大小的临时数组t,然后就是对于遍历到的每个位置,都遍历其四个相邻位置,如果相邻位置越界了,那么我们用当前位置的dp值更新结果res,因为此时dp值的意义就是从(i,j)到越界位置的路径数。如果没有,我们将当前位置的dp值赋给t数组的对应位置,这样在遍历完所有的位置时,将数组t整个赋值给dp,然后进入下一步的循环
https://leetcode.com/problems/out-of-boundary-paths/discuss/102967/java-solution-dp-with-space-compression
DP[i][j][k] stands for how many possible ways to walk into cell j,k in step iDP[i][j][k] only depends on DP[i - 1][j][k], so we can compress 3 dimensional dp array to 2 dimensional.
    public int findPaths(int m, int n, int N, int i, int j) {
        if (N <= 0) return 0;
        
        final int MOD = 1000000007;
        int[][] count = new int[m][n];
        count[i][j] = 1;
        int result = 0;
        
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        for (int step = 0; step < N; step++) {
            int[][] temp = new int[m][n];
            for (int r = 0; r < m; r++) {
                for (int c = 0; c < n; c++) {
                    for (int[] d : dirs) {
                        int nr = r + d[0];
                        int nc = c + d[1];
                        if (nr < 0 || nr >= m || nc < 0 || nc >= n) {
                            result = (result + count[r][c]) % MOD;
                        }
                        else {
                            temp[nr][nc] = (temp[nr][nc] + count[r][c]) % MOD;
                        }
                    }
                }
            }
            count = temp;
        }
        
        return result;
    }
  public int findPaths(int m, int n, int N, int x, int y) {
    int M = 1000000000 + 7;
    int dp[][] = new int[m][n];
    dp[x][y] = 1;
    int count = 0;
    for (int moves = 1; moves <= N; moves++) {
      int[][] temp = new int[m][n];
      for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
          if (i == m - 1)
            count = (count + dp[i][j]) % M;
          if (j == n - 1)
            count = (count + dp[i][j]) % M;
          if (i == 0)
            count = (count + dp[i][j]) % M;
          if (j == 0)
            count = (count + dp[i][j]) % M;
          temp[i][j] = (((i > 0 ? dp[i - 1][j] : 0) + (i < m - 1 ? dp[i + 1][j] : 0)) % M
              + ((j > 0 ? dp[i][j - 1] : 0) + (j < n - 1 ? dp[i][j + 1] : 0)) % M) % M;
        }
      }
      dp = temp;
    }
    return count;

  }
http://www.cnblogs.com/grandyang/p/6927921.html
由于结果可能是个巨大的数,所以让我们对一个大数取余。那么我们知道对于这种结果很大的数如果用递归解法很容易爆栈,所以最好考虑使用DP来解。那么我们使用一个三维的DP数组,其中dp[k][i][j]表示总共走k步,从(i,j)位置走出边界的总路径数。那么我们来找递推式,对于dp[k][i][j],走k步出边界的总路径数等于其周围四个位置的走k-1步出边界的总路径数之和,如果周围某个位置已经出边界了,那么就直接加上1,否则就在dp数组中找出该值,这样整个更新下来,我们就能得出每一个位置走任意步数的出界路径数了,最后只要返回dp[N][i][j]就是所求结果了
由于结果可能是个巨大的数,所以让我们对一个大数取余。那么我们知道对于这种结果很大的数如果用递归解法很容易爆栈,所以最好考虑使用DP来解。那么我们使用一个三维的DP数组,其中dp[k][i][j]表示总共走k步,从(i,j)位置走出边界的总路径数。那么我们来找递推式,对于dp[k][i][j],走k步出边界的总路径数等于其周围四个位置的走k-1步出边界的总路径数之和,如果周围某个位置已经出边界了,那么就直接加上1,否则就在dp数组中找出该值,这样整个更新下来,我们就能得出每一个位置走任意步数的出界路径数了,最后只要返回dp[N][i][j]就是所求结果了
    int findPaths(int m, int n, int N, int i, int j) {
        vector<vector<vector<int>>> dp(N + 1, vector<vector<int>>(m, vector<int>(n, 0)));
        for (int k = 1; k <= N; ++k) {
            for (int x = 0; x < m; ++x) {
                for (int y = 0; y < n; ++y) {
                    long long v1 = (x == 0) ? 1 : dp[k - 1][x - 1][y];
                    long long v2 = (x == m - 1) ? 1 : dp[k - 1][x + 1][y];
                    long long v3 = (y == 0) ? 1 : dp[k - 1][x][y - 1];
                    long long v4 = (y == n - 1) ? 1 : dp[k - 1][x][y + 1];
                    dp[k][x][y] = (v1 + v2 + v3 + v4) % 1000000007;
                }
            }
        } 
        return dp[N][i][j];
    }
https://zxi.mytechroad.com/blog/dynamic-programming/leetcode-576-out-of-boundary-paths/
  int findPaths(int m, int n, int N, int i, int j) {
    constexpr int kMod = 1000000007;
    vector<vector<int>> dp(m, vector<int>(n, 0));
    vector<int> dirs{-1, 0, 1, 0, -1};
    for (int s = 1; s <= N; ++s) {
      vector<vector<int>> tmp(m, vector<int>(n, 0));
      for (int y = 0; y < m; ++y)
        for (int x = 0; x < n; ++x)
          for (int d = 0; d < 4; ++d) {
            int tx = x + dirs[d];
            int ty = y + dirs[d + 1];            
            if (tx < 0 || ty < 0 || tx >= n || ty >= m)
              tmp[y][x] += 1;
            else
              tmp[y][x] = (tmp[y][x] + dp[ty][tx]) % kMod;
          }
      dp.swap(tmp);
    }
    return dp[i][j];
  }


class Solution(object):
    def findPaths(self, m, n, N, i, j):
        dp = [[0] * n for _ in range(m)]
        for s in range(1, N + 1):
            curStatus = [[0] * n for _ in range(m)]
            for x in range(m):
                for y in range(n):
                    v1 = 1 if x == 0 else dp[x - 1][y]
                    v2 = 1 if x == m - 1 else dp[x + 1][y]
                    v3 = 1 if y == 0 else dp[x][y - 1]
                    v4 = 1 if y == n - 1 else dp[x][y + 1]
                    curStatus[x][y] = (v1 + v2 + v3 + v4) % (10**9 + 7)
            dp = curStatus
        return dp[i][j]

Rolling Array:
    int findPaths(int m, int n, int N, int i, int j) {
        unsigned int dp[2][50][50] = {};
        for (int x = 1; x <= N; ++x) {
            for (int mi = 0; mi < m; ++mi) {
                for (int ni = 0, nc = x % 2, np = (x - 1) % 2; ni < n; ++ni) {
                    dp[nc][mi][ni] = ((mi == 0 ? 1 : dp[np][mi - 1][ni]) + 
                                   (mi == m - 1 ? 1 : dp[np][mi + 1][ni]) + 
                                   (ni == 0 ? 1 : dp[np][mi][ni - 1]) + 
                                   (ni == n - 1 ? 1 : dp[np][mi][ni + 1])
                                  ) % 1000000007;
                }
            }
        }
        return N % 2 == 0 ? dp[0][i][j] : dp[1][i][j];
    }

状态搜索
这个dp其实属于对状态的搜索,如果看了《计算机考研机试指南》或者《挑战程序设计竞赛》的话,会很清楚的知道其实这是个搜索的题目。归根到底都是对状态的转移问题,所以这个方法的名称叫做动归还是搜索都可以。

这种的做法有点类似于BFS搜索的题目,我们在做BFS的时候也会记录当前处于哪一步,所以是非常类似的。我们定义了四个搜索的方向,从当前位置向周围4个方向进行搜索,如果搜索到了边界以外,和上面的做法类似的,我们把当前的步数+1;如果在边界以内,那么就把当前第s步的结果增加第s-1步的(nx, ny)位置能到达边界的解法步数。

动态规划(Dynamic Programming)
数组dp[t][x][y]表示第t次移动时,坐标x, y处的移动路径总数。
状态转移方程:
dp[t + 1][x + dx][y + dy] += dp[t][x][y]    其中t表示移动的次数,dx, dy 取值 (1,0), (-1,0), (0,1), (0,-1)
当x + dx或者y + dy超出边界时,将结果累加至最终答案。
http://www.jianshu.com/p/5cd8eac78efb
一般矩阵题加了一个step的限制条件,则考虑用三维dp来做了。而递推公式如下(四个方向,step-1结果的和)
dp[i][j][step] = dp[i-1][j][step-1] + dp[i+1][j][step-1] + dp[i][j-1][step-1] + dp[i][j+1][step-1]
思路直接,用dfs, 用中心向外正向搜索。如果对应的坐标+步数没被访问过,则以此继续向外dfs.
第二种方法是反向,由step1推到stepn, 由外向内收缩。在一步时,仅仅最外围的坐标可以获得值,而里层点都是0,因为里层点一步走不出去。二步,三步以此类推,均可由一步逆推出来。这种方法还可以用滚动数组节省空间。而值得注意的是一定要用uint或long,来防止overflow.
https://discuss.leetcode.com/topic/88492/c-6-lines-dp-o-n-m-n-6-ms
The number of paths for N moves is the sum of paths for N - 1 moves from the adjacent cells. If an adjacent cell is out of the border, the number of paths is 1.
int findPaths(int m, int n, int N, int i, int j) {
  uint dp[51][50][50] = {};
  for (auto Ni = 1; Ni <= N; ++Ni)
    for (auto mi = 0; mi < m; ++mi)
      for (auto ni = 0; ni < n; ++ni)
        dp[Ni][mi][ni] = ((mi == 0 ? 1 : dp[Ni - 1][mi - 1][ni]) + (mi == m - 1? 1 : dp[Ni - 1][mi + 1][ni])
            + (ni == 0 ? 1 : dp[Ni - 1][mi][ni - 1]) + (ni == n - 1 ? 1 : dp[Ni - 1][mi][ni + 1])) % 1000000007;
  return dp[N][i][j];
}
We can also reduce the memory usage by using two grids instead of N, as we only need to look one step back. We can use N % 2 and (N + 1) % 2 to alternate grids so we do not have to copy.
int findPaths(int m, int n, int N, int i, int j) {
    unsigned int g[2][50][50] = {};
    while (N-- > 0)
        for (auto k = 0; k < m; ++k)
            for (auto l = 0, nc = (N + 1) % 2, np = N % 2; l < n; ++l)
                g[nc][k][l] = ((k == 0 ? 1 : g[np][k - 1][l]) + (k == m - 1 ? 1 : g[np][k + 1][l])
                    + (l == 0 ? 1 : g[np][k][l - 1]) + (l == n - 1 ? 1 : g[np][k][l + 1])) % 1000000007;
    return g[1][i][j];
}
As suggested by @mingthor, we can further decrease the memory usage (2 * m * n >> m * (n + 1)) as we only looking one row up. We will store new values for the current row in an array, and write these values back to the matrix as we process cells in the next row. This approach, however, impacts the runtime as we need extra copying for each step.
I experimented with different n and m (50 - 500), and N (5,000 - 50,000), and the second solution is approximately 10% faster than this one.
int findPaths(int m, int n, int N, int i, int j) {
    unsigned int g[50][50] = {}, r[50];
    while (N-- > 0)
        for (auto k = 0; k <= m; ++k)
            for (auto l = 0; l < n; ++l) {
                auto tmp = r[l];
                r[l] = (k == m ? 0 : ((k == 0 ? 1 : g[k - 1][l]) + (k == m - 1 ? 1 : g[k + 1][l])
                    + (l == 0 ? 1 : g[k][l - 1]) + (l == n - 1 ? 1 : g[k][l + 1])) % 1000000007);
                if (k > 0) g[k - 1][l] = tmp;
            }
    return g[i][j];
}

dp(N,i,j)=dp(N1,i1,j)+dp(N1,i,j1)+dp(N1,i+1,j)+dp(N1,i,j+1)

其中当单元格 (i,j) 在越界时, 有
int findPaths(int m, int n, int N, int i, int j) {
int dp[51][50][50] = {0};
for (auto Ni = 1; Ni <= N; ++Ni)
for (auto mi = 0; mi < m; ++mi)
for (auto ni = 0; ni < n; ++ni) {
dp[Ni][mi][ni] = ((long long)(mi == 0 ? 1 : dp[Ni - 1][mi - 1][ni]) +
(mi == m - 1 ? 1 : dp[Ni - 1][mi + 1][ni]) +
(ni == 0 ? 1 : dp[Ni - 1][mi][ni - 1]) +
(ni == n - 1 ? 1 : dp[Ni - 1][mi][ni + 1])) % 1000000007;
}
return dp[N][i][j];
}

http://ying_xd.leanote.com/post/Leetcode-576-Out-of-Boundary-Paths

X.
下面这种方法虽然也是用的DP解法,但是DP数组的定义和上面的不一样,这种解法相当于使用了BFS搜索,以(i, j)为起始点,其中dp[k][x][y]表示用了k步,进入(x, y)位置的路径数,由于dp[k][x][y]只依赖于dp[k-1][x][y],所以我们可以用一个二维dp数组来代替,初始化dp[i][j]为1,总共N步,进行N次循环,每次都新建一个mxn大小的临时数组t,然后就是对于遍历到的每个位置,都遍历其四个相邻位置,如果相邻位置越界了,那么我们用当前位置的dp值更新结果res,因为此时dp值的意义就是从(i,j)到越界位置的路径数。如果没有,我们将当前位置的dp值赋给t数组的对应位置,这样在遍历完所有的位置时,将数组t整个赋值给dp,然后进入下一步的循环
    int findPaths(int m, int n, int N, int i, int j) {
        int res = 0;
        vector<vector<int>> dp(m, vector<int>(n, 0));
        dp[i][j] = 1;
        vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
        for (int k = 0; k < N; ++k) {
            vector<vector<int>> t(m, vector<int>(n, 0));
            for (int r = 0; r < m; ++r) {
                for (int c = 0; c < n; ++c) {
                    for (auto dir : dirs) {
                        int x = r + dir[0], y = c + dir[1];
                        if (x < 0 || x >= m || y < 0 || y >= n) {
                            res = (res + dp[r][c]) % 1000000007;
                        } else {
                            t[x][y] = (t[x][y] + dp[r][c]) % 1000000007;
                        }
                    }
                }
            }
            dp = t;
        } 
        return res;
    }
http://blog.csdn.net/gqk289/article/details/73441022
http://imorrun.blogspot.com/2017/07/leetcode-576-out-of-boundary-paths.html
http://blog.csdn.net/anakin7/article/details/71787213
- How to make code clean
DP,时间复杂度O(m * n * step),dp[i][j]表示位置(i, j)的当前路径种数,内层循环每次初始化一个新的二维数组,用来记录当次遍历结果,遍历完之后赋值给cnt
  1.     public int findPaths(int m, int n, int N, int a, int b) {  
  2.         if (N <= 0) {  
  3.             return 0;  
  4.         }  
  5.         int[][] cnt = new int[m][n];  
  6.         int res = 0;  
  7.         int mod = 1000000007;  
  8.         int[][] dirs = {{10}, {-10}, {01}, {0, -1}};  
  9.         cnt[a][b] = 1;  
  10.         for (int i = 0; i < N; i++) {  
  11.             int[][] temp = new int[m][n];  
  12.             for (int j = 0; j < m; j++) {  
  13.                 for (int k = 0; k < n; k++) {  
  14.                     for (int[] dir : dirs) {  
  15.                         int nm = j + dir[0];  
  16.                         int nn = k + dir[1];  
  17.                         if (nm < 0 || nn < 0 || nm >= m || nn >= n) {  
  18.                             // 如果下一位置翻出去了,就只与前一次移动结果相关  
  19.                             res = (res + cnt[j][k]) % mod;  
  20.                         } else {  
  21.                             temp[nm][nn] = (temp[nm][nn] + cnt[j][k]) % mod;  
  22.                         }  
  23.                     }  
  24.                 }  
  25.             }  
  26.             cnt = temp;  
  27.         }  
  28.         return res;  
  29.     } 

X. Approach #2 Recursion with memoization [Accepted]
  • Time complexity : O(m*n*N). We need to fill the memo array once with dimensions mxnxN. Here, mn refer to the number of rows and columns of the given grid respectively. N refers to the total number of allowed moves.
  • Space complexity : O(m*n*N)memo array of size m*n*N is used.
  int M = 1000000007;

  public int findPaths(int m, int n, int N, int i, int j) {
    int[][][] memo = new int[m][n][N + 1];
    for (int[][] l : memo)
      for (int[] sl : l)
        Arrays.fill(sl, -1);
    return findPaths(m, n, N, i, j, memo);
  }

  public int findPaths(int m, int n, int N, int i, int j, int[][][] memo) {
    if (i == m || j == n || i < 0 || j < 0)
      return 1;
    if (N == 0)
      return 0;
    if (memo[i][j][N] >= 0)
      return memo[i][j][N];
    memo[i][j][N] = ((findPaths(m, n, N - 1, i - 1, j, memo) + findPaths(m, n, N - 1, i + 1, j, memo)) % M
        + (findPaths(m, n, N - 1, i, j - 1, memo) + findPaths(m, n, N - 1, i, j + 1, memo)) % M) % M;
    return memo[i][j][N];

  }

X. BFS
https://buptwc.com/2018/07/19/Leetcode-576-Out-of-Boundary-Paths/
下面是bfs代码,在这道题中tle
def findPaths(m,n,N,i,j):
    mod = 10**9 + 7
    Q = collections.deque([(i,j,0)])
    res = 0
    while Q:
        x,y,step = Q.popleft()
        if step > N: break
        if 0<=x<m and 0<=y<n:
            Q.append((x+1,y,step+1))
            Q.append((x-1,y,step+1))
            Q.append((x,y+1,step+1))
            Q.append((x,y-1,step+1))
        else:
            res += 1
    return res % mod
X. Brute Force
https://leetcode.com/articles/out-of-boundary-paths/
In the brute force approach, we try to take one step in every direction and decrement the number of pending moves for each step taken. Whenever we reach out of the boundary while taking the steps, we deduce that one extra path is available to take the ball out.
In order to implement the same, we make use of a recursive function findPaths(m,n,N,i,j) which takes the current number of moves(N) along with the current position((i,j) as some of the parameters and returns the number of moves possible to take the ball out with the current pending moves from the current position. Now, we take a step in every direction and update the corresponding indices involved along with the current number of pending moves.
Further, if we run out of moves at any moment, we return a 0 indicating that the current set of moves doesn't take the ball out of boundary.

Time complexity : O(4^n). Size of recursion tree will be 4^n. Here, n refers to the number of moves allowed.
  public int findPaths(int m, int n, int N, int i, int j) {
    if (i == m || j == n || i < 0 || j < 0)
      return 1;
    if (N == 0)
      return 0;
    return findPaths(m, n, N - 1, i - 1, j) + findPaths(m, n, N - 1, i + 1, j) + findPaths(m, n, N - 1, i, j - 1)
        + findPaths(m, n, N - 1, i, j + 1);

  }

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts