题目来源:https://leetcode-cn.com/problems/jump-game-iv/
大致题意:
给一个数组,从第一个元素出发,算出在如下规则下最少多少步可以到达最后一个元素
- 移到下一个元素
- 移到上一个元素
- 移到一个相同的元素位置
思路
最少多少步,就容易想到最短路径,最短路径就需要图或者树,可是这是一个数组
不过可以利用规则,想象成图:
- 把该数组想象成一个无向图
- 一个元素与它的相邻元素以及相同元素之间构成边
有了图,就可以开始遍历了,可以使用 BFS 来搜索
常规 BFS 的时间复杂度为点数 + 边数的和,这样的话会超时,对于该题可以进行剪枝:
- 相同元素之间的边只用遍历一次即可,所以可以在遍历到一个节点时,将其值相同的节点直接的边删除–也就是不再遍历到这些边
- 可以用哈希表实现该操作,首先遍历数组,将所有值相同的元素放入一个集合中,然后再搜索时,当第一次遍历到一个节点值时,就将值对应的元素从哈希表中取出,之后就不再进行该操作
BFS + 剪枝
- 遍历数组,将相同元素放入哈希表中,key 为元素值,value 为该元素索引的集合
- BFS 遍历。对于每次搜索到的节点,先判断哈希表中是否有对应的键,若有去除相同元素的集合入队列,并进行标记;否则直接跳过垓步。然后将当前节点的相邻元素入队(如果还未搜索过)
- 当搜索到最后一个元素时直接返回当前的操作次数即可,BFS 保证了第一次搜到的就是最快的
public int minJumps(int[] arr) {
// 存相同元素集合的哈希表
Map<Integer, List<Integer>> idxOfSame = new HashMap<>();
int n = arr.length;
// 标记搜索过的节点
boolean[] vis = new boolean[n];
// 哈希表的初始化
// 即将元素值相同的元素放入一个集合中,再放入哈希表
for (int i = 0; i < n; i++) {
idxOfSame.putIfAbsent(arr[i], new ArrayList<>());
idxOfSame.get(arr[i]).add(i);
}
Queue<int[]> queue = new ArrayDeque<>();
// 放入开始节点并标记
queue.offer(new int[]{0, 0});
vis[0] = true;
// BFS
while (!queue.isEmpty()) {
// 取出节点
int[] pair = queue.poll();
// 当前遍历节点对应的索引
int idx = pair[0];
// 搜索到当前节点的操作数
int step = pair[1];
// 如果搜索到最后一个元素,直接退出
if (idx == n - 1) {
return step;
}
step++;
// 判断当前节点值对应的集合是否在哈希表中
if (idxOfSame.containsKey(arr[idx])) {
// 取出集合
List<Integer> list = idxOfSame.get(arr[idx]);
int size = list.size();
// 将集合对应的元素全部入队
for (int i = 0; i < size; i++) {
int next = list.get(i);
if (!vis[next]) {
vis[next] = true;
queue.offer(new int[]{next, step});
}
}
// 从哈希表中去掉该集合,剪枝
idxOfSame.remove(arr[idx]);
}
// 放入节点的相邻元素
if (idx + 1 < n && !vis[idx + 1]) {
vis[idx + 1] = true;
queue.offer(new int[]{idx + 1, step});
}
if (idx - 1 >= 0 && !vis[idx - 1]) {
vis[idx - 1] = true;
queue.offer(new int[]{idx - 1, step});
}
}
// 如果 BFS 未找到最后一个元素,那么搜索失败
return -1;
}