【Leetcode每日一题】2021-04-29 403 青蛙过河 - 困难 - 动态规划 (Python & C++)
为了选出新的首领,青蛙部落准备在河边举办一场比赛,部落的首领必须同时具备强健的体魄和灵活的头脑,所以举办方制定了如下的比赛规则:
- 假定河流被等分为若干个单元格,在其中的一些单元格内放有一块石子。
- 石子的位置存在stones中(用石子所在单元格的序号升序表示)。
- 候选人只能从一块石子跳到另一块石子,跳入水中即视为淘汰。
- 开始时,候选人站在第一块石子上,规定第一步只能跳一个单元格的距离,即只能从单元格1跳到单元格2,即
stones[0]
为0
,stones[1]
必须为1
。- 为了选出最优秀的候选人,规定如果候选人上一步跳跃了
k
个单位,那么它接下来的跳跃距离只能为k-1
、k
或k+1
个单位,而且只准向前跳(终点的方向)。- 胜利的条件就是跳到最后一块石子上(跳不到或者跳过头都视为淘汰)。
举办方为了比赛的公平和多样性,设计了很多不同的赛道stones
,每个候选人都会从这些赛道中抽取自己的赛道,所以必须保证每条赛道都是有解的(即肯定有一种满足比赛规则的跳法能胜利),所以举办方请你来帮助验证赛道的有效性。
示例 1:
输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:该赛道是有效的,按照如下方案跳跃:跳1
个单位到第2
块石子, 然后跳2
个单位到第3
块石子, 接着跳2
个单位到第4
块石子, 然后跳3
个单位到第6
块石子, 跳4
个单位到第7
块石子, 最后,跳5
个单位到第8
个石子(即最后一块石子)。
示例 2:
输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第5
和第6
个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。
提示:2 <= stones.length <= 2000
0 <= stones[i] <= 231 - 1
stones[0] == 0
标签:回溯,记忆化搜索,动态规划
1.记忆化搜索(DFS)
数据结构:set,map/dict
知识点:
Python:
- 用functools
里的@lru_cache(None)
及其语法糖@cache
来实现记忆化搜索
- set
和dict
的查找复杂度(a in set(b))
都为$\mathcal{O}(1)$,而list
查找复杂度为$\mathcal{O}(n)$,bisect
查找复杂度为$\mathcal{O}(log(n))$
1 | class Solution: |
2. 动态规划
思路:
首先要确定dp
中的状态,这里我们将以石子为主体,将青蛙能否通过跳k
个单位到该石子作为每个状态,所以dp
是个二维数组,第一个维度自然是stones
的长度,要确定第二个维度我们需要知道青蛙最多跳几个单位到达当前石子。因为青蛙第一次必然从stones[0]
以一个单位跳到stones[1]
,然后下一次最多跳1+1=2
到stones[2]
,以此类推,我们每次都能以最大距离跳到stones
中的下一个石子,那么青蛙最多能跳i
个单位到stones[i]
,所以我们可以确定青蛙在整个过程中的跳跃距离不可能大于stones
的长度n
。所以dp
第二个维度可以也设为n
。
然后要推出状态转移方程。以dp[i][k]
为例,dp[i][k]
代表青蛙从stones[j]
跳k
个单位到达stones[i]
,有三种可能状态变为dp[i][k]
,即dp[j][k+1]
$\rightarrow$dp[i][k]
,dp[j][k]
$\rightarrow$dp[i][k]
,dp[j][k-1]
$\rightarrow$dp[i][k]
,所以状态转移方程为:
dp[i][k] = dp[j][k+1] or dp[j][k] or dp[j][k-1]
数据结构:list
(Python), vector
(C++)
知识点:
- 这里的动态规划和常见的动态规划不同,当前的状态不是固定从前一个或几个状态转移而来,而是要遍历之前所有可能的状态。
1 | class Solution: |