【LeetCode】935. Knight Dialer 解题报告(Python)
作者: 负雪明烛
id: fuxuemingzhu
个人博客: http://fuxuemingzhu.cn/
题目地址:https://leetcode.com/problems/knight-dialer/description/
题目描述
A chess knight can move as indicated in the chess diagram below:
. 
This time, we place our chess knight on any numbered key of a phone pad (indicated above), and the knight makes N-1 hops. Each hop must be from one key to another numbered key.
Each time it lands on a key (including the initial placement of the knight), it presses the number of that key, pressing N digits total.
How many distinct numbers can you dial in this manner?
Since the answer may be large, output the answer modulo 10^9 + 7.
Example 1:
Input: 1
Output: 10
Example 2:
Input: 2
Output: 20
Example 3:
Input: 3
Output: 46
Note:
- 1 <= N <= 5000
题目大意
马的初始位置可以在拨号按键的任意位置,现在要让它走N - 1步,问这个马能产生出多少种不同的拨号号码?
解题方法
动态规划TLE
本周周赛第二题,卡了我好久啊!好气!
这个题本身肯定是动态规划题目,设置dp数组为当前步以每个按键结尾的状态数。所以我使用了一个4×3的二维数组,需要注意的是左下角和右下角的位置不可能到达,设置它的数值为0.状态转移方程很好求得,那就是把上一步可能存在的位置状态累加在一起就成了当前位置的状态数。
问题是会超时啊!甚至可能会超过内存限制!
先上一份很容易想到的,但是会超时TLE的代码:
时间复杂度是O(N),空间复杂度O(N).
class Solution:
def knightDialer(self, N):
"""
:type N: int
:rtype: int
"""
self.ans = dict()
self.ans[0] = 10
board = [[1] * 3 for _ in range(4)]
board[3][0] = board[3][3] = 0
pre_dict = {(i, j) : self.prevMove(i, j) for i in range(4) for j in range(3)}
for n in range(1, N):
new_board = copy.deepcopy(board)
for i in range(4):
for j in range(3):
cur_move = 0
for x, y in pre_dict[(i, j)]:
cur_move = (cur_move + board[x][y]) % (10 ** 9 + 7)
new_board[i][j] = cur_move
board = new_board
return sum([board[i][j] for i in range(4) for j in range(3)]) % (10 ** 9 + 7)
def prevMove(self, i, j):
if (i, j) == (3, 0) or (i, j) == (3, 2):
return []
directions = [(-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1)]
res = []
for d in directions:
x, y = i + d[0], j + d[1]
if 0 <= x < 4 and 0 <= y < 3 and (x, y) != (3, 0) and (x, y) != (3, 2):
res.append((x, y))
return res
在比赛的时候剩下的一个小时都在优化这个题,个人感觉这个题卡时间卡的有点太严了,上面这个做法应该是标准做法吧,通过不了,需要一些奇技淫巧才能通过。
空间换时间,利用对称性
这是我在比赛最后的时间通过的代码,把所有状态给初始化了,这样好处是可以不用在循环中不停地copy原来的棋盘状态了,同时利用了对称性,只需要求出4个位置(1,2,4,0)的状态,其余状态可以直接利用对称性得到。
还有一个优化的地方在于在每次的过程中进行取模!虽然取模运算是耗时的运算,但是数字很大的时候,大整数既占空间又占时间,所以取模!
经过上面的优化勉强通过了,真是不容易,我觉得这个题非常不友好,因为同样的Java代码可以不做任何优化就通过了。这个题在N很大的时候还会告诉我内存超了……简直了。。
时间复杂度是O(N),空间复杂度O(N).总时间1500ms。
class Solution:
def knightDialer(self, N):
"""
:type N: int
:rtype: int
"""
self.ans = dict()
self.ans[0] = 10
board = [[[1] * 3 for _ in range(4)] for _ in range(N)]
board[0][3][0] = board[0][3][2] = 0
pre_dict = {(i, j) : self.prevMove(i, j) for i in range(4) for j in range(3)}
for n in range(1, N):
for i in range(2):
cur_move = 0
for x, y in pre_dict[(i, 0)]:
cur_move += board[n - 1][x][y]
board[n][i][0] = cur_move % (10 ** 9 + 7)
cur_move = 0
for x, y in pre_dict[(0, 1)]:
cur_move += board[n - 1][x][y]
board[n][0][1] = cur_move % (10 ** 9 + 7)
cur_move = 0
for x, y in pre_dict[(3, 1)]:
cur_move += board[n - 1][x][y]
board[n][3][1] = cur_move % (10 ** 9 + 7)
board[n][4][0] = board[n][0][0]
board[n][0][2] = board[n][0][0]
board[n][5][1] = 0
board[n][6][2] = board[n][7][0]
board[n][8][1] = board[n][0][1]
board[n][9][2] = board[n][0][2]
board[n][3][0] = board[n][3][2] = 0
return (board[N - 1][0][0] * 4 + board[N - 1][0][1] * 2 + board[N - 1][10][0] * 2 + board[N - 1][3][1] + board[N - 1][11][1]) % (10 ** 9 + 7)
def prevMove(self, i, j):
if (i, j) == (3, 0) or (i, j) == (3, 2):
return []
directions = [(-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1)]
res = []
for d in directions:
x, y = i + d[0], j + d[1]
if 0 <= x < 4 and 0 <= y < 3 and (x, y) != (3, 0) and (x, y) != (3, 2):
res.append((x, y))
return res
优化空间复杂度
上面的做法我一直在想着优化时间复杂度,事实上,每个状态只和之前的状态有关,所以很容易想到优化空间复杂度。
使用10个变量,分别保存每个位置能取到的状态数,然后人为的把每个状态能通过其他的状态得到的代码给写出来就行了。
代码如下,真的很简洁,为什么我没有想到优化空间!!优化之后时间降到了264 ms,这个告诉我们,优化空间同样可以大规模地降低时间,如果DP问题超时的话,优先考虑空间!
时间复杂度是O(N),空间复杂度O(1).时间264 ms.
class Solution:
def knightDialer(self, N):
"""
:type N: int
:rtype: int
"""
if N == 1: return 10
x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = x0 = 1
MOD = 10 ** 9 + 7
for i in range(N - 1):
x1, x2, x3, x4, x5, x6, x7, x8, x9, x0 = (x6 + x8) % MOD,\
(x7 + x9) % MOD, (x4 + x8) % MOD, (x3 + x9 + x0) % MOD, 0, (x1 + x7 + x0) % MOD,\
(x2 + x6) % MOD, (x1 + x3) % MOD, (x2 + x4) % MOD, (x4 + x6) % MOD
return (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x0) % MOD
如果在上面的解法上再利用好对称性的话,可以把时间再次降低到160 ms。
时间复杂度是O(N),空间复杂度O(1).时间160 ms。
class Solution:
def knightDialer(self, N):
"""
:type N: int
:rtype: int
"""
if N == 1: return 10
x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = x0 = 1
MOD = 10 ** 9 + 7
for i in range(N - 1):
x1, x2, x4, x0 = (x6 + x8) % MOD, (x7 + x9) % MOD, (x3 + x9 + x0) % MOD, (x4 + x6) % MOD
x3, x5, x6, x7, x8, x9 = x1, 0, x4, x1, x2, x1
return (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x0) % MOD
相似题目
688. Knight Probability in Chessboard
参考资料
https://leetcode.com/problems/knight-dialer/discuss/189252/O(logN)
日期
2018 年 11 月 4 日 —— 下雨的周日
【LeetCode】935. Knight Dialer 解题报告(Python)的更多相关文章
- [LeetCode] 935. Knight Dialer 骑士拨号器
A chess knight can move as indicated in the chess diagram below: . This time, we place o ...
- LeetCode 935. Knight Dialer
原题链接在这里:https://leetcode.com/problems/knight-dialer/ 题目: A chess knight can move as indicated in the ...
- 【LeetCode】120. Triangle 解题报告(Python)
[LeetCode]120. Triangle 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址htt ...
- LeetCode 1 Two Sum 解题报告
LeetCode 1 Two Sum 解题报告 偶然间听见leetcode这个平台,这里面题量也不是很多200多题,打算平时有空在研究生期间就刷完,跟跟多的练习算法的人进行交流思想,一定的ACM算法积 ...
- 【LeetCode】Permutations II 解题报告
[题目] Given a collection of numbers that might contain duplicates, return all possible unique permuta ...
- 【LeetCode】Island Perimeter 解题报告
[LeetCode]Island Perimeter 解题报告 [LeetCode] https://leetcode.com/problems/island-perimeter/ Total Acc ...
- 【LeetCode】01 Matrix 解题报告
[LeetCode]01 Matrix 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/01-matrix/#/descripti ...
- 【LeetCode】Largest Number 解题报告
[LeetCode]Largest Number 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/largest-number/# ...
- 【LeetCode】Gas Station 解题报告
[LeetCode]Gas Station 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/gas-station/#/descr ...
随机推荐
- js浮点运算的坑
1,js浮点型小数点运算的问题. 这么简单的计算,js竟然算的是错的,究其原因,是因为js小数在内存存储方式的原因. 具体原因: JavaScript 里的数字是采用 IEEE 754 标准的 64 ...
- 使用C语言来扩展PHP,写PHP扩展dll
转自http://www.cnblogs.com/myths/archive/2011/11/28/2266593.html 以前写过一次PHP扩展DLL,那个是利用调用系统的COM口实现的扩展,与P ...
- Git分布式版本控制系统基础
查看创建的账号 下来在该当前的⽬录下创建⽂件,并且进⾏提交 使⽤git log就可以看到最近提交的⽇志记录的信息 查看窗户的状态信息 某些时候我们可能需要回退到之前的版本,那么具体处理的步骤为: 1. ...
- ab命令执行压力测试
ab是Apache超文本传输协议(HTTP)的性能测试工具:设计意图是描绘当前所安装的Apache的执行性能,主要是显示你安装的Apache每秒可以处理多少个请求:ab不仅仅能进行基于apache服务 ...
- A Child's History of England.48
A few could not resolve to do this, but the greater part complied. They made a blazing heap of all t ...
- adjective
形容词用来描述名词或代词:副词用来描述剩下的(动词.形容词.副词和整句).adverb: to word. Adjectives are used almost exclusively to modi ...
- Hadoop的HA机制浅析
Zookeeper在Hadoop的HA中的应用 非HA的弊端: HDFS集群的分布式存储是靠namenode节点(namenode负责响应客户端请求)来实现.在非HA集群中一旦namenode宕机,虽 ...
- canal从mysql拉取数据,并以protobuf的格式往kafka中写数据
大致思路: canal去mysql拉取数据,放在canal所在的节点上,并且自身对外提供一个tcp服务,我们只要写一个连接该服务的客户端,去拉取数据并且指定往kafka写数据的格式就能达到以proto ...
- 数仓day04----日志预处理2
1.详细描述idmap的整个计算方案 (1)使用SparkSession对象读取用户不同类别的埋点日志,解析并抽取出相应的标识id,使用union进行合并,得到装有汇总标识id的rdd(ids) (2 ...
- 【STM32】使用SDIO进行SD卡读写,包含文件管理FatFs(七)-准备移植FatFs
[STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(一)-初步认识SD卡 [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(二)-了解SD总线,命令的相关介绍 [STM3 ...