目录
1. 题目描述
给定四个整数 sx
, sy
,tx
和 ty
,如果通过一系列的转换可以从起点 (sx, sy)
到达终点 (tx, ty)
,则返回 true
,否则返回 false
。
从点 (x, y)
可以转换到 (x, x+y)
或者 (x+y, y)
。
示例 1:
输入: sx = 1, sy = 1, tx = 3, ty = 5 输出: true 解释: 可以通过以下一系列转换从起点转换到终点: (1, 1) -> (1, 2) (1, 2) -> (3, 2) (3, 2) -> (3, 5)
示例 2:
输入: sx = 1, sy = 1, tx = 2, ty = 2 输出: false
示例 3:
输入: sx = 1, sy = 1, tx = 1, ty = 1 输出: true
提示:
1 <= sx, sy, tx, ty <= 10^9
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reaching-points
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2. 方法一
用深度优先搜索或者动态规划都应该可以,但是考虑到输入的动态范围,不论是深度优先搜索还是动态规划所需要的递归深度都应该是无法承受的。比如说在sx,sy很小而tx,ty很大的情况下。
隐隐约约觉得这个跟斐波那契数列有点什么关系似的,有点斐波那契数列与2维随机漫游相结合的那种感觉。
首先实现了一个动态规划算法,毫无意外地提交超时。
然后,改进了一下,采用双向的动态规划。从(sx,sy)向上,和从(tx,ty)向下,两个方向迎头前进,这样可以大大降低所需要的递归深度。比单向的动态规划要快两个数量级左右。但是仍然不够,提交超时。代码如下:
import random
import time
class Solution:
def reachingPoints1(self, sx: int, sy: int, tx: int, ty: int) -> bool:
# With the approach, no need of memoization
memo = dict()
def dp(_sx,_sy,_tx,_ty):
if (_sx,_sy,_tx,_ty) in memo:
return memo[(_sx,_sy,_tx,_ty)]
# baseline case
if _sx > _tx or _sy > _ty:
return False
if _sx == _tx and _sy == _ty:
return True
rslt = dp(_sx+_sy, _sy, _tx, _ty) or dp(_sx, _sx+_sy, _tx, _ty)
memo[(_sx,_sy,_tx,_ty)] = rslt
return rslt
return dp(sx, sy, tx, ty)
def reachingPoints2(self, sx: int, sy: int, tx: int, ty: int) -> bool:
# memo = dict()
def dp(_sx,_sy,_tx,_ty):
# print("dp:", _sx,_sy,_tx,_ty)
# if (_sx,_sy,_tx,_ty) in memo:
# return memo[(_sx,_sy,_tx,_ty)]
# baseline case
if _sx > _tx or _sy > _ty or _tx < 0 or _ty < 0:
return False
if _sx == _tx and _sy == _ty:
return True
if _sx+_sy==_tx and _sy == _ty:
return True
if _sx==_tx and _sx+_sy == _ty:
return True
rslt = dp(_sx+_sy, _sy, _tx-_ty, _ty) or dp(_sx+_sy, _sy, _tx, _ty-_tx) or dp(_sx, _sx+_sy, _tx-_ty, _ty) or dp(_sx, _sx+_sy, _tx, _ty-_tx)
# memo[(_sx,_sy,_tx,_ty)] = rslt
return rslt
return dp(sx, sy, tx, ty)
这种纯模拟的搜索方式行不通,需要一些更深入的洞见来简化问题。
3. 方法二
方法一中所提到的两个方向的处理其实是不对称的。
在不考虑(tx,ty)的情况下,从(sx,sy)往上走有两种可能性:(sx+sy,sy); (sx, sx+sy).
在不考虑其它约束的条件下从(tx,ty)往下走有两种可能性:(tx-ty,ty); (tx, ty-tx).
但是考虑到所有数都为正整数的约束条件,从(tx,ty)往下走事实上只有一种可能性,即在tx>ty时为(tx-ty,ty);在tx<ty时为(tx,ty-tx);因为另外一种可能性会导致负数出现。而如果tx=ty时,向下走会导致0出现同样不行。
因此,可以在满足条件(tx > sx and ty > sy and tx!=ty)的前提下先执行反向向下的迭代。当不能再往下走时,再进行如下判断:
- 如果tx==ty: 只有当tx==ty==sx==sy才满足可到达条件
- 如果tx==sx: 由于tx不能再减小,只有ty可能减小,如果满足ty>sy and (ty-sy)%tx==0则意味着可以到达;否则就不能;
- 如果ty==sy: 由于ty不能再减小,只有tx可能减小,如果满足tx>sx and (tx-sx)%ty==0则意味着可以到达;否则就不能;
- 其它情况就都无法到达了
因为当tx大于ty时,需要一直减ty直到小于ty为止,才可能切换到减ty的操作。反之亦然。因此反向向下处理(在满足反向向下的条件下)可以进一步压缩为:
- 如果tx > ty:更新为(tx%ty, ty)
- 如果tx < ty:更新为(tx, ty%tx)
这个处理是不是有点眼熟,是的,它其实与求最大公约数的辗转相除法如出一辙。
比较难的一个坑是为什么只在满足“tx > sx and ty > sy”条件时执行反向向下操作。举一个例子: (3,3,9,12). 经过一次变换后得到(3,3,9,3),接下来再继续往下走的话,就变成了(3,3,0,3),这种情况仍然需要特殊判断,所以还不如不做向下操作,直接判断。
class Solution:
def reachingPoints3(self, sx: int, sy: int, tx: int, ty: int) -> bool:
while tx>sx and ty>sy and tx!=ty:
if tx>ty:
tx = tx % ty
else:
ty = ty % tx
if tx==ty==sx==sy:
return True
if tx==sx:
if ty>sy and (ty-sy)%tx==0:
return True
else:
return False
if ty==sy:
if tx>sx and (tx-sx)%ty==0:
return True
else:
return False
return False
if __name__ == "__main__":
sln = Solution()
sx, sy, tx, ty = 1,1,3,5
print(sln.reachingPoints3(sx, sy, tx, ty))
sx, sy, tx, ty = 3,3,12,9
print(sln.reachingPoints3(sx, sy, tx, ty))
执行用时:32 ms, 在所有 Python3 提交中击败了80.36%的用户
内存消耗:14.9 MB, 在所有 Python3 提交中击败了75.00%的用户
回到主目录: 笨牛慢耕的Leetcode解题笔记(动态更新。。。)