给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
链接:https://leetcode-cn.com/problems/jump-game-ii
做这道题目之前相信大家已经做过跳跃游戏的基础版了。我在基础版里面的主要思路是维护一个cur表示可以到达的最大位置:
cclass Solution {
public:
bool canJump(vector<int>& nums) {
int i = 1, cur = nums[0];
int res = nums.size() - 1;
if(res == 0) // 仅有一个元素
return true;
if(cur >= res) // 一步就可以跳过去
return true;
while(i <= cur && i <= res) {
cur = max(cur, i + nums[i]);
if(cur >= res)
return true;
i++;
}
if(cur == res)
return true;
return false;
}
};
信心满满的交上去,很遗憾的发现做错了…这次的错误不是过分的简化了题目的意思,而是超时了,也就是所谓的T掉了呢?仔细观察我自己写的代码,就会发现时间复杂度其实不是O(n),最坏的情况下可能到达O(n^2/2),这是因为我运用了动态规划的思想,用数组f[len]来记录到某位置的的最小的步数,在遍历某一位置时,将在该位置可以到达的所有位置的对应的数组步数+1,加完之后于原先的值进行比较并取最小值,这样就可以保证遍历完成所有位置之后在f[len-1]位置处得到正确的答案。其递推公式为
f[i+j+1] = min(f[i+j+1], f[i] + 1);
class Solution {
public:
int jump(vector<int>& nums) {
int len = nums.size();
if(len == 1)
return 0;
int f[len], i = 1;
for(int j = 1; j < len; j++)
f[j] = len;
if(nums[0] >= len - 1)
return 1;
for(int j = 1; j <= nums[0]; j++)
f[j] = 1; // Init
while(i < len) {
for(int j = 0; j < nums[i] && i+j+1 < len; j++)
f[i+j+1] = min(f[i+j+1], f[i] + 1);
i++;
}
return f[len-1];
}
};
但是这样提交上去会被卡第91个点,说明代码虽然是正确的但是思路还可以简化,毕竟这个题目的分组是贪心算法。
如何才能简化这个问题呢?
通过分析(看题解)发现,保留每一步其实是不必要的,我们只关心最后一步的结果,当然如果题目要问到达每一步的最小步数就另当别论了。
如上述所说,我们的算法有可能被测试样例卡,那么我们需要把时间复杂度进一步的降低为O(n),如何做到这一步呢。在观看了题解之后,我分享一下我自己的想法:
当你遍历某一个位置的时候,在该位置能到达的位置其实和离这个位置最近的位置没有什么两样,那么我只需要去维护一个所能达到的最大值,这还不够,我们无法判断具体的步数,我们还需要保留上一个位置所能到达的最大值,用来和现在正在遍历的位置来进行一定的比较,换句话说,用来判断是否已经脱离了上一个位置所能到达的范围,如果已经脱离,自然更新步数,并且替换为当前位置所能到达的最大值,可能上述表达有些模糊,因为我也有些模糊 。让我们来举一个简单的例子:
初始情况下:
[2,3,1,1,4]
[0,1,2,3,4] (下标)
(↑表示正在遍历的位置,-表示可以到达的位置)
maxPos表示当前位置和之前位置可以到达的最大位置
接下来遍历下标为0的位置:
[2,3,1,1,4]
[↑,-,-,0,0]
此时 step += 1, maxPos = 2, end = 2(end表示step尚未增加时可以到达的最大为自豪)
接下来遍历下标为1的位置:
[2,3,1,1,4]
[·,↑,-,0,0]
[ , ,-,-,-]
此时 step =1, maxPos = 4, end = 2(还没有脱离step控制的范围)
接下来遍历下标为2的位置:
[2,3,1,1,4]
[·,·,↑,0,0]
[ , ,-,-,-]
此时 step =1, maxPos = 4, end = 2(还没有脱离step控制的范围),可以看到通过位置2可以到达位置3,但是没有必要记录,也就没有必要更新了
接下来遍历下标为3的位置:
[2,3,1,1,4]
[·,·,·,0,0]
[ , ,↑,-,-]
此时 step += 1, maxPos = 4, end = 4(脱离step控制的范围,step必须加,否则无法达到位置3)
接下来大同小异
首先来看一下为什么这么贪心是正确的呢:
可以这样理解:只有在必须要走的时候才去走,否则不走,只有在当前位置超过了end的时候才去走一步,end表示step不加时候所能到达的最远距离。当然这个题之所以可以贪心完全是因为每一步可以走任意步数。
class Solution {
public:
int jump(vector<int>& nums) {
int maxPos = 0, i = 0, end = 0, len = nums.size();
int step = 0;
for(int i = 0; i < len - 1; i++) {
maxPos = max(maxPos, i + nums[i]);
if(i >= end) {
end = maxPos;
step++;
if(maxPos >= len - 1)
return step;
}
}
return step;
}
};
-_-贪心最难的就是让人想不到贪心 orz
错误反馈: [email protected]