看了好几种算法实现,感觉这里说的最清楚,也比较简单,本算法思想也是来源于此。
解法
看完这道题目,可能大部分的读者都能够想出这样一个相对简单的解法:将每个位置都看作一个点,并从第i个点向它之后的nums[i]个点都连一条长度为1的有向边,而现在的问题就是从0号点到达size-1号点需要的最短距离,这就是一个很简单的最短路问题,实际上由于边的长度均为1,而且不存在环,我们可以用宽度优先搜索(时间复杂度为O(n^2),即边数)来进行相关的计算。
但是这样一道难度为Hard的题会让我们就这么简单通过么?笔者觉得是不会的,所以这题肯定还存在着一些优化。
不难发现,这道题目转换出的最短路问题存在三个条件:
(1)边的长度均为1
(2)不存在环
(3)连出的边是连续的
我们是不是可以用这三个“很强”的条件来做一些优化呢,答案自然是肯定的!
——如果令f[i]表示从0号点到达i号点的最短路径,那么对于任意i小于j,有f[i]<=f[j],即f是非递减的,这个结论的证明是显然的,在此不作过多赘述。
在有了这样的结论之后,我们就会发现,其实对于f数组来说,它会是一段段的存在,先是一个0,然后是一段1,然后是一段2,依此类推,那么现在问题来了,每一段的长度是多少呢?
这个问题很好回答,如果我们令l[k]表示f数组中值为k的一段的左边界,r[k]表示f数组中值为k的一段的有边界,那么有
l[k] = r[k - 1] + 1,这是显然的
r[k] = max{i + nums[i] | l[k - 1] <= i <= r[k - 1]},由于f值为k的位置一定是从f值为k-1的位置走来的,所以只需要看从所有f值为k-1的位置里最远可以到达的地方即可。
也就是说,我们可以在对nums的一遍扫描中,依次求出所有的l[k]和r[k],而f数组也就自然求解出来了——答案也就得到了。
这道题目虽然在LeetCode上标记为Hard,但是最后得出的解决方法也不算特别的复杂,主要是利用转换后最短路问题的一些特殊性质得到了非常有用的结论,来加速了整个最短路径的计算。
C++代码:
#include
#include
#include
using namespace std;
class Solution {
public:
int jump(vector<int>& nums) {
int l = 0;
int r = 0;
int next_r = 0;
//k记录跳转到r需要的最少的次数
int k = 0;
while (r1)
{
for (int i=l;i<=r;++i)
{
next_r = max(next_r, i+nums[i]);
}
//更新k次跳转能到达的左右两边下标的范围
l = r+1;
r = next_r;
++k;
}
return k;
}
};
int main()
{
Solution s;
vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(1);
v.push_back(1);
v.push_back(4);
cout<return 0;
}