作者: 负雪明烛
id: fuxuemingzhu
个人博客: http://fuxuemingzhu.cn/


题目地址:https://leetcode.com/problems/out-of-boundary-paths/description/

题目描述

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 10^9 + 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].

题目大意

每次可以把足球从一个格子移动到另一个格子,要求最多通过N步能使得球移动到外边的方案数?

解题方法

动态规划

这个题有个很明显的对于动态规划的提示,那就是要模10^9 + 7,也就是说结果会很大,普通的搜索可能hold不住。

使用三维数组dp[k][x][y]表示在不超过k步的情况下,从x,y点移动到外边需要的步数。那么,当前位置通过k步移动到外边的步数等于其周围4个位置走k - 1步移动到外边的步数和。

因为当x,y处于边界的时候,实际上只有两个或者三个相邻的位置,因为向边界方向走的话,只需要1步就可以移动到外部。所以,如果向当前位置的周围位置出界的话,那么从这个方向需要出去移动步数就是1.

最后求和取模。

时间复杂度是O(Nmn),空间复杂度是O(Nmn).

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

上面这个做法可以看出每个状态其实只和上一次的状态有关,因此可以做状态压缩节省空间。

只使用二维数组表示地图即可,需要注意的是每次循环的时候还是需要重新开一个全部为0的curStatus,为什么全部是0而不是dp的拷贝呢?因为我们每次对下一次的状态进行搜索之前,下个状态应该全部是未知的,我们下面的代码就是计算每个位置的值,因此不能初始化dp的拷贝,否则下面的代码不work。其实这个和上面的做法对比一下就知道了,因为上面的做法中,每一步开始的时候,里面的二维数组其实全部都是0.

每次搜索结束之后,需要更新dp,也就是我们把当前的状态作为下次搜索的初始状态。

时间复杂度是O(Nmn),空间复杂度是O(m*n).

class Solution(object):
def findPaths(self, m, n, N, i, j):
"""
:type m: int
:type n: int
:type N: int
:type i: int
:type j: int
:rtype: int
"""
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]

状态搜索

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

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

时间复杂度是O(Nmn),空间复杂度是O(Nmn).

class Solution(object):
def findPaths(self, m, n, N, i, j):
"""
:type m: int
:type n: int
:type N: int
:type i: int
:type j: int
:rtype: int
"""
dp = [[[0] * n for _ in range(m)] for _ in range(N + 1)]
ds = [(0, 1), (0, -1), (-1, 0), (1, 0)]
for s in range(1, N + 1):
for x in range(m):
for y in range(n):
for d in ds:
nx, ny = x + d[0], y + d[1]
if nx < 0 or nx >= m or ny < 0 or ny >= n:
dp[s][x][y] += 1
else:
dp[s][x][y] = (dp[s][x][y] + dp[s - 1][nx][ny]) % (10**9 + 7)
return dp[N][i][j]

同样的可以优化空间。

时间复杂度是O(Nmn),空间复杂度是O(m*n).

class Solution(object):
def findPaths(self, m, n, N, i, j):
"""
:type m: int
:type n: int
:type N: int
:type i: int
:type j: int
:rtype: int
"""
dp = [[0] * n for _ in range(m)]
ds = [(0, 1), (0, -1), (-1, 0), (1, 0)]
for s in range(1, N + 1):
curStatus = [[0] * n for _ in range(m)]
for x in range(m):
for y in range(n):
for d in ds:
nx, ny = x + d[0], y + d[1]
if nx < 0 or nx >= m or ny < 0 or ny >= n:
curStatus[x][y] += 1
else:
curStatus[x][y] = (curStatus[x][y] + dp[nx][ny]) % (10**9 + 7)
dp = curStatus
return dp[i][j]

记忆化搜索

其实,应该是先有了记忆化搜索的代码才能推出dp。这个题我用记忆化搜索重新实现了一下,但是发现果然过不了啊!但是记忆化搜索确实能加深我们对这个题目的理解。

把上面的状态搜索的dp改成记忆化搜索后的代码如下。如何加深理解呢?看看dfs的参数,变量其实只有x,y两个。dfs函数代表了我们从(x, y)位置出发,最多移动N次的情况下能到达边界的个数。所以,我们的(x, y)的初始化值是题目要求的(i, j).

最后TLE了,很无奈,因为C++版本的能够通过。

时间复杂度是O(Nmn),空间复杂度是O(Nmn).

class Solution(object):
def findPaths(self, m, n, N, i, j):
"""
:type m: int
:type n: int
:type N: int
:type i: int
:type j: int
:rtype: int
"""
dp = [[[0] * n for _ in range(m)] for _ in range(N + 1)]
return self.dfs(m, n, N, i, j, dp) def dfs(self, m, n, N, x, y, dp):
if N == 0:
return 0
if x < 0 or x >= m or y < 0 or y >= n:
return 1
if dp[N][x][y]:
return dp[N][x][y]
ds = [(0, 1), (0, -1), (-1, 0), (1, 0)]
for d in ds:
nx, ny = x + d[0], y + d[1]
dp[N][x][y] = (dp[N][x][y] + self.dfs(m, n, N - 1, nx, ny, dp)) % (10**9 + 7)
return dp[N][x][y]

相似题目

688. Knight Probability in Chessboard
62. Unique Paths
63. Unique Paths II
913. Cat and Mouse

参考资料

http://www.cnblogs.com/grandyang/p/6927921.html
https://zxi.mytechroad.com/blog/dynamic-programming/leetcode-576-out-of-boundary-paths/

日期

2018 年 10 月 27 日 —— 10月份最后一个周末

【LeetCode】576. Out of Boundary Paths 解题报告(Python)的更多相关文章

  1. leetcode 576. Out of Boundary Paths 、688. Knight Probability in Chessboard

    576. Out of Boundary Paths 给你一个棋盘,并放一个东西在一个起始位置,上.下.左.右移动,移动n次,一共有多少种可能移出这个棋盘 https://www.cnblogs.co ...

  2. leetcode 576. Out of Boundary Paths

    leetcode 576 题意大概就是在一个m*n的网格中,在坐标为[i,j]的网格上放一个物体,在规定时间N(t<=N)中,有多少种方法把物体移动出去.物体只能上下左右移动,一次移动一格,移动 ...

  3. 第十一周 Leetcode 576. Out of Boundary Paths (HARD) 计数dp

    Leetcode 576 给定一个二维平面, 一个球在初始位置(i,j)每次可以转移到上下左右的一格. 问在N次转移内,有多少种路径可以转移出边境. dp[i][j][k]为 在点(i,j) 已经走了 ...

  4. 【LeetCode】257. Binary Tree Paths 解题报告(java & python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 迭代 日期 题目地址:https://leet ...

  5. 【LeetCode】62. Unique Paths 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/unique-pa ...

  6. 【LeetCode】206. Reverse Linked List 解题报告(Python&C++&java)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 迭代 递归 日期 [LeetCode] 题目地址:h ...

  7. 【LeetCode】654. Maximum Binary Tree 解题报告 (Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...

  8. 【LeetCode】784. Letter Case Permutation 解题报告 (Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 回溯法 循环 日期 题目地址:https://leet ...

  9. 【LeetCode】113. Path Sum II 解题报告(Python)

    [LeetCode]113. Path Sum II 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fu ...

随机推荐

  1. perl 转置矩阵

    这里提供一个转置矩阵的perl脚本,R语言中的t()函数同样也能实现转置 1 use strict; 2 3 open A,"$ARGV[0]"; 4 5 my %ha; 6 my ...

  2. 60-Lowest Common Ancestor of a Binary Search Tree

    Lowest Common Ancestor of a Binary Search Tree My Submissions QuestionEditorial Solution Total Accep ...

  3. 聚合与分组查询,F与Q查询

    from django.db.models import Q 查询书籍名称是python入门或者价是555.55的书 book_queryset = models.Book.objects.filte ...

  4. excel-合并多个Excel文件--VBA合并当前目录下所有Excel工作簿中的所有工作表

    在网上找EXCEL多文件合并的方法,思路: 一.Linux 或者window+cmder,直接用命令行cat合并EXCEL文件,但是,需要安装辅助东西才能直接处理(也许也不可以,但是,可以用文件格式转 ...

  5. 使用flock命令查看nas存储是否支持文件锁

    上锁 文件锁有两种 shared lock 共享锁 exclusive lock 排他锁 当文件被上了共享锁之后,其他进程可以继续为此文件加共享锁,但此文件不能被加排他锁,此文件会有一个共享锁计数,加 ...

  6. Yarn【架构、原理、多队列配置】

    目录 一.什么是yarn 二.yarn的基本架构和角色 三.yarn的工作机制 四.任务提交流程 五.资源调度器 FIFO 容量调度器 公平调度器 六.容量调度器多队列提交案例实操 1.案例:配置de ...

  7. 基于DataX将数据从Sqlserver同步到Oracle

    DataX是阿里云推出的一款开源的ETL工具,通过配置json文件实现不同数据库之间的数据同步.先有需求是从Sqlserver同步数据到Oracle,网上关于DataX的介绍很多. 框架设计 Data ...

  8. transient关键字和volatile关键字

    看到HashSet的源代码的时候,有一个关键字不太认识它..transient,百度整理之: Java的Serialization提供了一种持久化对象实例的机制,当持久化对象时,可能有一些特殊的对象数 ...

  9. Oracle学习笔记(1)

    折腾了好久 终于把oracle安装成功了.小兴奋下. 创建了一个数据库 dabook. run--> Services.msc查看服务: 可以看到DABOOK的服务已启动. 1,sys用户 在c ...

  10. 阿里巴巴Java开发手册摘要(一)

    一命名风格 1.代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结尾. 反例:_name / $name / name_ / name$ 2.类名使用UpperCamelCase风格 ...