题目描述
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
For example:
A = [2,3,1,1,4], return true.
A = [3,2,1,0,4], return false.
对上面的数据,使用示意图帮助理解:
根据题目要求,数组里的每个元素表示从该位置可以跳出的最远距离,要求问从第一个元素(index=0)开始,能否达到数组的最后一个元素,这里认为最后一个元素为终点。需要注意一下几点:
- 数组中全部为非负元素,因此不存在后退的情况;
- 题目要求到达终点,并不要最终求停在终点,因此,如果可以跳过终点也是成功的;
- 题目中说明的是每个元素从该位置能达到的最远距离,但实际可以不Jump那么远的距离。例如上图中的第一个数组第一个元素为2,则从该位置可以跳一步二号元素3处,也可以跳2步到三号位元素1处。
使用动态规划的解法
本题可以用动态规划的思想去做,考察每个位置能达到的最远距离。第k个元素能达到的最远距离,可能为:
- max (k-1位置的最远距离,此时可能没有实际跳到k位置上)
- k+nums[k] (从k位置起跳的最远距离)
遍历数组元素的最远距离,使用最远距离判断能否跳到终点,思路如下:
- 到达第k个元素前的最远距离为max;
- 若 max<k,则无法跳到k位置,程序返回false;
- 否则,计算k位置处能达到的最远距离,max_dis_at_k = max > k + num[k] ? max : k + num[k];
- 若 max_dis_at_k > 数组长度,则程序返回true。
程序最多只要遍历一遍就可以得到结果,因此时间复杂度为O(n)。
代码如下:
class Solution {
public:
bool canJump(vector<int>& nums) {
if (!nums.size())
return false;
int max = nums[0];
for (int i = 1; i != nums.size(); ++i)
{
if (i > max)
return false;
else if (nums[i] + i >= nums.size() - 1)
return true;
else if (nums[i] + i > max)
max = nums[i] + i;
}
return true;
}
};
不使用动态规划的另类解法
在做完本题的时候我查找了一下相关资料,之前就有其他博客说明,本题为一道非常经典的动态规划题目,可惜我还没有系统学习过动态规划,因此在刚开始看到本题的时候,没有使用动态规划的直觉,饶了好大一个弯才想到了上面的解法。
最初的想法是,既然数组中的元素均非负,则只要元素值全部为正,便一定可以达到终点。而无法达到终点的原因就是数组中的0元素,我们可以认为数组中某些位置的0元素相当于一个吸收态,元素跳到该位置就无法前进了。例如上图中的第二个例子,因此在程序中判断0是否为吸收态就可以判断能否达到终点了。
吸收态的判断方法为:若数组中0元素之前的所有元素跳过的距离均不超过0,则0为吸收态。
- 对于位置为i的0元素,从j=i-1元素向前遍历,判断 nums[j] + j > i 是否成立,
- 若成立则说明,程序可以顺利跳过i位置的0元素,继续遍历寻找0元素
- 若不成立则说明i位置的0元素为吸收态,无法达到终点,返回false。
根据这个思路,我们可以进行正向和反向遍历,寻找吸收态。
正向遍历:
class Solution {
public:
bool canJump(vector<int>& nums) {
if (!nums.size())
return false;
int i = 0;
while (i < nums.size() - 1)
{
if (nums[i] == 0)
{
int j = i - 1;
while (i < nums.size() - 1 && !nums[i])
i++;
i--;
bool flag = false;
while (j >= 0)
{
if (nums[j] + j >= nums.size() - 1)
return true;
else if (nums[j] + j > i)
{
i = nums[j] + j;
flag = true;
break;
}
j--;
}
if (!flag)
return false;
}
else
{
i = i + nums[i];
}
}
return true;
}
};
反向遍历
class Solution {
public:
bool canJump(vector<int>& nums) {
if (!nums.size())
return false;
if (nums.size() == 1)
return true;
int i = nums.size() - 1;
for (int i = nums.size() - 1; i >= 0; --i)
{
if (nums[i] == 0)
{
int j = i - 1;
bool flag = false;
while (j >= 0)
{
if ((i == nums.size()-1 && nums[j]+j ==i ) || nums[j] + j > i)
{
flag = true;
i = j + 1;
break;
}
j--;
}
if (!flag)
return false;
}
}
return true;
}
};
后记
后面的另类解法和动态规划解法相比,动态规划的解法真是优雅啊,思路清晰明了,编写代码的过程也很愉快,不得不感叹要好好学习算法。MIT《算法导论》公开课的老师在第一节课就说过,要编写好的程序,可以两年内每天都写代码,或者你可以选一门算法课,真的很有道理呢。