题目(困难)
给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。
每一步,你可以从下标 i 跳到下标:
i + 1 满足:i + 1 < arr.length
i - 1 满足:i - 1 >= 0
j 满足:arr[i] == arr[j] 且 i != j
请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。
注意:任何时候你都不能跳到数组外面。
示例 1:
输入:arr = [100,-23,-23,404,100,23,23,23,3,404]
输出:3
解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。
示例 2:
输入:arr = [7]
输出:0
解释:一开始就在最后一个元素处,所以你不需要跳跃。
示例 3:
输入:arr = [7,6,9,6,9,6,9,7]
输出:1
解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。
示例 4:
输入:arr = [6,1,9]
输出:2
示例 5:
输入:arr = [11,22,7,7,7,7,7,7,7,22,13]
输出:3
提示:
1 <= arr.length <= 5 * 10^4
-10^8 <= arr[i] <= 10^8
解题思路
一个位置可由左边,右边,对应数值的最小步数,三处更新而来,所以传统动态规划的搞不定了
dp[i] = min(dp[i], dp[i-1]+1, dp[i+1]+1, minstep[val]+1)
所以需要BFS探索的来,对于每个位置,往下走一步来探索同属一个值的所有节点们,探索他的左右两边;因此需要建图,建立值到他的索引们的映射。
因为这个过程会重复遇到节点,如果已访问过,肯定之前的步数更少,再次访问就不用实际探索,所以需要一个set来存已访问节点;
并且如果将一个值的所有节点都探索了,其实也就可由从图中去掉该值,减少重复判断,因为判断了也是已访问,不然会超时。
代码
class Solution {
public:
queue<pair<int,int> > q; //队列存索引和到该索引的步数
unordered_set<int> vis; //存已访问过的节点,因为以前能访问过的步数肯定更小,不必再进队
void visit(int id, int step) {
if(!vis.count(id)) {
vis.insert(id);
q.push({id, step});
}
}
int minJumps(vector<int>& arr) {
int n = arr.size();
unordered_map<int, vector<int>> hash; //值到索引们的映射
for(int i = 0; i < n; i++) hash[arr[i]].push_back(i);
q.push({0, 0});
vis.insert(0);
while(!q.empty()) {
auto [id, step] = q.front();
q.pop();
if(id == n-1) return step; //到达目标
step++;
if(hash.count(arr[id])) for(auto x : hash[arr[id]]) visit(x, step);
hash.erase(arr[id]); //该值对应的索引们都访问过了,避免下次判断,不然会超时
if(id-1 >= 0) visit(id-1, step);
if(id+1 < n) visit(id+1, step);
}
return 0;
}
};