两种解法。
第一种,第一直觉就是动态规划。令dp[i][step]表示跳了step格到达第i块石头是否可行,那么需要判断第stones[i]-step格是不是石头。这里我们转换一下,第一层循环是i,第二层是j,假设要从第j块石头跳到第i块石头,step为stones[i]-stones[i],现在要求dp[i][step],那么我们需要知道dp[j][step-1],dp[j][step]和dp[j][step+1]。还有一点需要注意,我们从第0块石头跳到第i块石头,最多跳了i次,由于每次从一块石头跳到另一块石头最多比前一次跳的step多一格,因此跳到第i块石头时最大的step为i,不得超过i。最终时间复杂度,空间复杂度.
var canCross = function(stones) {
const n = stones.length
if (stones[1] != 1) return false
if (stones.length == 2) return true
let dp = new Array(n).fill(null).map(d=>new Array(n).fill(false))
dp[0][0] = true
dp[1][1] = true
for (let i = 2; i < n; i++) {
for (let j = 0; j < i; j++) {
let step = stones[i] - stones[j]
if (step > i) continue
dp[i][step] = dp[j][step] || dp[j][step-1] || dp[j][step+1]
if (i==n-1&&dp[i][step]) return true
}
}
return false
};
这里再介绍第二种解法。大多数人看到这题的直觉就是DFS,时间复杂度,即使加上剪枝在时间上也是严重超时。主要原因还是太多重复计算,我们采用记忆化搜索,参考自【宫水三叶】一题四解 : 降低确定「记忆化容器大小」的思维难度 & 利用「对偶性质」构造有效状态值 。时间复杂度,空间复杂度。
var canCross = function(stones) {
const n = stones.length
if (stones[1] != 1) return false
if (n == 2) return true
let visited = new Array(n).fill(null).map(d=>new Array(n).fill(false))
let map = new Map()
for (let i = 0; i < n; i++) map.set(stones[i], i)
let deque = [[1,1]]
visited[1][1] = true
while (deque.length) {
// 上一次是跳了step格到了第idx块石头,它所在的格子为stones[idx]
const [idx, step] = deque.shift()
if (idx == n-1) return true
for (let x = -1; x <= 1; x++) {
// 往前跳step+x格
let next = stones[idx]+step+x
if (next == stones[idx]) continue
// 判断从第idx块石头即stones[idx]往前跳step+1格所在的格子是否是石头
if (map.has(next)) {
// 判断是否visited过
let next_idx = map.get(next)
if (!visited[next_idx][step+x]) {
visited[next_idx][step+x] = true
deque.push([next_idx,step+x])
}
}
}
}
return false
};