题目地址:
https://leetcode.com/problems/frog-jump/
给定一个长 n n n的非负整数数组 A A A, A [ 0 ] = 0 A[0]=0 A[0]=0,代表若干石头的坐标,一个青蛙从 A [ 0 ] A[0] A[0]出发,第一步只能跳 1 1 1个单位距离,如果其跳了 k k k个单位距离到达了某个位置,那它下一步可以跳 k − 1 , k , k + 1 k-1,k,k+1 k−1,k,k+1三者之一的距离,但它只能向后跳不能往回跳(也就是它每次跳的位移必须是正的)。问其是否能跳到 A [ n − 1 ] A[n-1] A[n−1]这个石头上。
思路是动态规划。设 f [ i ] f[i] f[i]代表最后一步可以到达 i i i这个位置的最后一跳距离的集合,注意,这里 f [ i ] f[i] f[i]是个集合(哈希表)。这里的意思是, ∀ x ∈ f [ i ] \forall x\in f[i] ∀x∈f[i],那意味着存在一种跳法,最后一步跳 x x x的距离,可以恰好跳到 i i i这个位置。当然,我们只需要存 i i i位置有石头的情形。最后只需要知道 f [ A [ n − 1 ] ] f[A[n-1]] f[A[n−1]]是否为空即可,如果空,说明跳不到,否则能跳到。那么如果已知 f [ i ] f[i] f[i]这个集合,对于任何 x ∈ f [ i ] x\in f[i] x∈f[i],应该就有 x − 1 ∈ f [ i + x − 1 ] , x ∈ f [ i + x ] , x + 1 ∈ f [ i + x + 1 ] x-1\in f[i+x-1],x\in f[i+x],x+1\in f[i+x+1] x−1∈f[i+x−1],x∈f[i+x],x+1∈f[i+x+1],我们可以对这三个集合进行更新。遍历完 A A A之后,看一下 f [ A [ n − 1 ] ] f[A[n-1]] f[A[n−1]]是否为空。代码如下:
import java.util.*;
public class Solution {
public boolean canCross(int[] stones) {
if (stones == null || stones.length == 0) {
return false;
}
int len = stones.length;
// key存的是石头下标,value存的是能到达key的跳法的最后一跳距离
Map<Integer, Set<Integer>> dp = new HashMap<>();
for (int i = 0; i < len; i++) {
dp.put(stones[i], new HashSet<>());
}
dp.get(stones[0]).add(0);
for (int i = 0; i < len - 1; i++) {
// 由于后面要修改dp这个哈希表,必须先缓存,直接遍历会导致ConcurrentModificationException
List<Integer> tmp = new ArrayList<>(dp.get(stones[i]));
for (int step : tmp) {
for (int dx = -1; dx <= 1; dx++) {
// 求出下一跳能到达的位置
int next = stones[i] + step + dx;
// 如果next这个位置恰好是石头,则更新能到它的最后一跳的距离
if (dp.containsKey(next)) {
// 如果这个石头恰好是终点,则直接返回true
if (next == stones[len - 1]) {
return true;
}
dp.get(next).add(step + dx);
}
}
}
}
return false;
}
}
时空复杂度 O ( n 2 ) O(n^2) O(n2),步数的可能性一共是 O ( n ) O(n) O(n)级别的。