问题描述
思路简述
这道题标签是DP类题目,但是用DFS+记忆剪枝也可以解决,DFS的思路是模拟暴搜所有可能的跳跃方案来判断是否有成功的方案,设计DFS函数的时候考虑当前石头的下标(用于判断是否到达了最后一块石头)以及上一步所用步数(用于枚举计算下一步能够跳出的可能步数);
下面先给出DFS不带记忆的代码:
class Solution {
public:
map<int, int> s_map;
bool canCross(vector<int>& stones) {
// 将石头下标存放在map中,方便判断某位置是否有石头以及该石头的下标
for(int i=0; i<stones.size(); i++) s_map[stones[i]] = i;
// 由题意可知,初始时在第一块石头0上,并且第一次只能跳一步,所以如果1不存在,那就直接不可能有可行的方案了
if(s_map.count(1) == 0) return false;
return dfs(stones, 1, 1);
}
// cur_idx:当前所处的石头下标
// k:上一步跳到这一步所用的步数,用于计算下一步跳跃的可能性
bool dfs(vector<int> stones, int cur_idx, int k){
// 当前坐标为最后一块石头,即方案可行
if(cur_idx == stones.size()-1) return true;
// 遍历-1,0,1,也就是遍历下一步能够跳跃的范围
for(int i=-1; i<=1; i++){
int next = stones[cur_idx] + k + i;
// 原地踏步的情况
if(next == stones[cur_idx]) continue;
// 当前的跳跃方案处有石头,则在这种情况下继续DFS
if(s_map.count(next) > 0){
bool flag = dfs(stones, s_map[next], k+i);
if(flag) return true;
}
}
return false;
}
};
不加记忆的DFS在第十五个用例处就会出现超时,于是考虑备忘录搜索,备忘录需要记的内容为:[某坐标,上次跳跃步数]的条件下能否到达最后一块石头,可以考虑用bool型,但是bool型只能存储是否能达到,不能存储当前情况是否在备忘录上,于是可以用能够int型的数组,即:int memo[index][last_jump]这样的数组,但是如果用数组的话考虑到可能会开百万级别大小的数组空间,于是可以用哈希表,哈希表的key为由index和last_jump组成的字符串,value为true或false,如果该key不存在则认为是还没有搜索过的场景。
class Solution {
public:
map<int, int> s_map;
map<string, bool> memo; // 加入备忘录
bool canCross(vector<int>& stones) {
for(int i=0; i<stones.size(); i++) s_map[stones[i]] = i;
if(s_map.count(1) == 0) return false;
return dfs(stones, 1, 1);
}
bool dfs(vector<int> stones, int cur_idx, int k){
// 备忘录的key为cur_index与k组成的字符串
string key = to_string(cur_idx) + "_" + to_string(k);
// 如果memo中存在,则认为已经计算过的情况,则直接返回备忘录的记录
if(memo.count(key) > 0) return memo[key];
if(cur_idx == stones.size()-1) return true;
for(int i=-1; i<=1; i++){
int next = stones[cur_idx] + k + i;
if(next == stones[cur_idx]) continue;
if(s_map.count(next) > 0){
bool flag = dfs(stones, s_map[next], k+i);
// 将当前情况(key)以及对应的解决方案(flag)存入备忘录(memo)中
memo[key] = flag;
if(flag) return true;
}
}
return false;
}
};
虽然记忆化搜索后能够AC了,但是时间复杂度还是有点高。