作者: 负雪明烛
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. MariaDB——在Linux中查找数据库路径,并进入数据库

    1.直接在命令行运营mysql,如果出现下图,说明数据库路径没有设置到环境变量里. 2.找出数据库路径,可以用ps -ef | grep my 命令,查找后台正在执行的命令任务中,包含my字母开头的所 ...

  2. c#页面查询、数据显示

    page : <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Queryx ...

  3. 对于vue项目更新迭代导致上传至服务器后出现Loading chunk {n} failed和Unexpected token <的解决方式

    相信大家对于vue项目的维护与更新中会遇见很多问题,其中有两种情况最为常见. 一种是Loading chunk {n} failed,这种情况出现的原因是vue页面更新上传至服务器后,由于vue默认打 ...

  4. 生成接口文档并同步到postman

    前言 当我们开发需要测试接口时,会遇到以下几个问题 1.如果接口过多,参数过多,一个个参数复制到postman简直能要了我的狗命,重复劳动过多. 2.如果接口过多,参数过多,编写接口文档给测试人员或者 ...

  5. 【Linux】【Shell】【Basic】变量与数据类型

    1. 变量: 1.1. 局部变量:作用域是函数的生命周期:在函数结束时被自动销毁: 定义局部变量的方法:local VARIABLE=VALUE 1.2. 本地变量:作用域是运行脚本的shell进程的 ...

  6. [学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

    我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴 ...

  7. jquery的each和js原生for循环性能对比

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  8. 【Spring Framework】Spring注解设置Bean的初始化、销毁方法的方式

    bean的生命周期:创建---初始化---销毁. Spring中声明的Bean的初始化和销毁方法有3种方式: @Bean的注解的initMethod.DestroyMethod属性 bean实现Ini ...

  9. mybatis联合查询

    1.有学生实体 @Component @Scope("prototype") public class StudentInfo { private Integer studentI ...

  10. mysql安装 报错解决

    换了新电脑,重新安装了一下mysql,安装过程出现了一些错误,在此记录一下: 参考菜鸟教程:https://www.runoob.com/mysql/mysql-install.html 1.下载my ...