题目描述
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例 :
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
解题思路
(1)动态规划
- 首先初始化dp数组: d p = i n t [ n u m s . s i z e ( ) ] dp = int[nums.size()] dp=int[nums.size()], d p [ i ] dp[i] dp[i]表示在数组 i i i的位置到达数组末端所走的最少步数。如果位置 i i i无法到达数组末端,则令 d p [ i ] = + ∞ dp[i] =+\infty dp[i]=+∞.
- 现在来计算 d p [ i ] dp[i] dp[i], n u m s [ i ] nums[i] nums[i]表示位置 i i i能走的最大步长。在最大步长覆盖的范围内,找出到达数组末端的最小步数,然后加 1 1 1,即为从位置 i i i走到末端的最少步数,也就是 d p [ i ] = 1 + m i n ( d p [ i + 1 ] , d p [ i + 2 ] , . . . , d p [ i + n u m s [ i ] ] ) dp[i] = 1+min(dp[i+1],dp[i+2],...,dp[i+nums[i]]) dp[i]=1+min(dp[i+1],dp[i+2],...,dp[i+nums[i]])。最开始是从数组末端开始,初始 d p [ n u m s . s i z e − 1 ] = 0 dp[nums.size-1] = 0 dp[nums.size−1]=0表示在数组末端不需要走或者说走 0 0 0步,就到数组末端了。然后依次倒推过去最后就求出 d p [ 0 ] dp[0] dp[0]的值了,而 d p [ 0 ] dp[0] dp[0]表示在数组位置0处到达数组末端所走的最少步数。下面代码在求 m i n ( . . . ) min(...) min(...)的时候,用了快排思想,相当于二分查找,找最小的时间复杂度为 O ( log 2 ( n ) ) O(\log_2(n)) O(log2(n))。该算法总的时间复杂度为 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))
(2)直接贪心求解法
由于该题动态规划超出时间限制,我就看了下评论,很多也是DP、BFS都超时,但是有一位大神的思路不错,而且时间复杂度就是
O
(
n
)
O(n)
O(n)。在此膜拜一下,并将思想理解并叙述一下:
现在发挥你的想象,把数组
n
u
m
s
nums
nums看成一条直线,人最开始在数组下标i为0,相当于直线原点位置。
如果还不清楚就看代码吧!或者看官方发布的思路,就是贪心的用最少的步数到最远的距离。还是太菜了,我没想出来,总是觉得万物皆可DP。
解题代码
(1)动态规划代码
class Solution {
public:
int searchmin(vector<int>dp,int start,int end){
//随机快排
unsigned seed = time(0);
srand(seed);
int key = rand() % (end- start + 1) + start;
swap(dp[key],dp[end]);
//使用二分查找最小值在范围start到end之前,包含两个端点
int i = start-1;
for(int j = start; j < end; ++j){
if(dp[j] < dp[end]){
++i;
swap(dp[i],dp[j]);
}
}
swap(dp[++i],dp[end]);
if(1 < i - start + 1){
return searchmin(dp,start,i-1);
}
return dp[i] == INT_MAX ? INT_MAX : dp[i]+1;
}
int jump(vector<int>& nums) {
vector<int>dp(nums.size(),0); //保存每一个位置跳到最远的最少步数
for (int i = nums.size() - 2; i>=0; --i){
if(nums[i] == 0){
dp[i] = INT_MAX;//说明这个位置不能跳,永远也到不了终点,那就把它置为无穷大
continue;
}
if(nums[i]+i >= dp.size()-1){
dp[i] = 1;//步长能到终点就直接返回1了,不需要找最小
continue;
}
dp[i] = searchmin(dp,i+1,i+nums[i]);
}//for
return dp[0];
}
};
(2)直接贪心法代码
class Solution {
public:
int jump(vector<int>& nums) {
int step = 0;
if(nums.size() == 1){
return step;//表示一出生就在罗马,不用走了
}
int max_coverage = nums[0];//当前初始位置所覆盖的范围
int next_coverage = 0;//表示当前覆盖范围的下一个覆盖范围
step +=1;//表示走了一步
if(max_coverage >=(nums.size()-1)){
return step;
}
for(int i = 1; i<nums.size(); ++i){
if(i > max_coverage){
++step;//如果走出的当前覆盖范围进入下一个覆盖范围
max_coverage = next_coverage;
}
if(next_coverage < i+nums[i]){
//表示当前i所覆盖的范围比原来的更大,则更新覆盖范围
next_coverage = i+nums[i];
}
if(next_coverage >= nums.size()-1){
break;
//如果下一覆盖范围能覆盖到数组末端则表示只需一步就到了
}
}//for
return step+1;
}
};
提交结果
(1)动态规划提交结果
无论怎么优化都是卡在这里,这说明这题动态规划不行。或者是我没有优化好。
(2)直接贪心法提交结果
总结
动态规划法时间复杂度比较高,这题已经明确从原点可以走到终点,只要求出走的最小步数。所以可以直接通过算出下一步能到的最远距离,直到覆盖终点就结束。计算步长就是覆盖面以内就不动,从一个覆盖面到另一个覆盖面就加一。