给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
分析:
方法1:回溯法
模拟从第一个位置跳到最后位置的所有方案,从第一个位置开始,模拟所有可以跳到的位置,然后从当前位置重复上述操作,当没有办法继续跳的时候,就回溯
时间复杂度:O(2^n),每个位置都有跳和不跳两种选择,需要遍历所有选择
空间复杂度:O(N),递归栈的开销
方法2:回溯+备忘录法(自低向上)
当我们确定一个坐标可以跳到最后位置的时候,结果就不会改变了,这意味着我们可以记录这个结果,每次不用重新计算
时间复杂度:O(N*N)
空间复杂度:O(2*N)=O(N),第一个N为递归栈的开销,第二个N为备忘录的开销
方法3:自底向上的动态规划
我们把可以到达最后位置的点叫做好点,否则叫做坏点
自低向上可以消除递归栈的开销
从后往前遍历,当前点的位置i+此时可以走的步数j(>=1&&j<=len,len为此时可以走的最多步数)=k,如果k没有越界并且k是一个好点,那么可以推导出i也是一个好点,最后我们只需要确认起始0点是不是一个好点就可以了
时间复杂度:O(N*N)
空间复杂度:O(N)
class Solution { public: bool canJump(vector<int>& v) { int n=v.size(); int dp[n]; memset(dp,0,sizeof(dp)); dp[n-1]=1; for(int i=n-2;i>=0;i--)//从后往前 { int len=v[i];//此时可以走的最大步数 for(int j=1;j<=len;j++) { int k=i+j;//位置k if(k>=n)//k越界 break; if(dp[k]==1)//k是一个好点,那么i肯定也是一个好点 { dp[i]=1; break; } } } if(dp[0]==1)//最后只需要判断起始0点是不是一个好点 return true; return false; } };
方法四:贪心法
我们发现每次都是在k点的左边寻找一个好点,那么如果当前位置i+当前位置可以走的步数v[i]>=上一个好点的位置(i右边第一个好点的位置),那么则i也是一个好点,最后确认一个最后得到的好点的位置是不是起始0点即可
时间复杂度:O(N)
空间复杂度:O(1)
class Solution { public: bool canJump(vector<int>& v) { int n=v.size(); int pre=n-1;//上一个好点的位置 for(int i=n-1;i>=0;i--) { if(i+v[i]>=pre)//i点可以到达上一个好点,那么i点也是好点 pre=i;//上一个好点位置设置为i点 } return pre==0;//最后只需要判断最后得到的好点是不是起始0点即可 } };