题目描述
一只青蛙想要过河。 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以跳上石子,但是不可以跳入水中。
给你石子的位置列表 stones(用单元格序号升序表示),请判定青蛙能否成功过河(即能否在最后一步跳至最后一块石子上)。
开始时,青蛙默认已站在第一块石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格 1 跳至单元格 2 )。
如果青蛙上一步跳跃了k个单位,那么它接下来的跳跃距离只能选择为k - 1、k或k + 1个单位。另请注意,青蛙只能向前方(终点的方向)跳跃。
样例
输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。
输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第 5 和第 6 个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。
算法1
(动态规划) $O(n^2)$
时间复杂度:O(n^2),我们需要遍历n个石子,每次遍历到第i个石子时,需要再遍历前i-1个石子,最坏的情况下,我们需要遍历前n-1个石子,所以时间复杂度为O(n^2)。
空间复杂度:O(n^2),我们需要存储每个石子之间的转移状态,一共n*n个状态需要记录。
参考文献
Python 代码
1.状态表示:f[i][k]
表示在上一次跳跃距离为k个单位的条件下,可以达到i石子。
属性:True/False表示是否可以到达该状态。
2.状态计算:
f[i][k] = f[j][k-1] || f[j][k] || f[j][k+1]
从j石子跳到i石子需要满足以上三个条件之一:某一个石子跳到j石子时的跳跃距离为k-1/k/k+1。
j石子与i石子之间距离k个单位,当有其他石子跳到j石子的跳跃距离为k-1/k/k+1时,k-1/k/k+1这三个跳跃距离都可以在下一次选择跳跃距离k。
所以满足上述的任意一个条件,都可以从j石子跳到i石子上。
3.代码优化
通过分析题目,我们可以得到在j石子时,最多能跳跃j+1个单位,所以从j石子与i石子之间的距离大于j+1时,j石子一定不能跳到i石子。
class Solution:
def canCross(self, stones: List[int]) -> bool:
n = len(stones)
f = [[False]*n for _ in range(n)]
f[0][0] = True
# 第i-1格最多能跳i个单位长度
for i in range(1, n):
if stones[i] - stones[i-1] > i:
return False
for i in range(1, n):
for j in range(i-1, -1, -1):
# 第j格跳到第i格需要的距离k
k = stones[i] - stones[j]
# 青蛙在第j个石子上最多只能跳j+1个单位。
if k > j + 1:
break
# 则青蛙在第j格时的上一次跳跃距离为k-1/k/k+1即可满足条件。
f[i][k] = f[j][k-1] or f[j][k] or f[j][k+1]
if i == n-1 and f[i][k]:
return True
return False