LeetCode(45): 跳跃游戏 II

Hard!html

题目描述:算法

给定一个非负整数数组,你最初位于数组的第一个位置。数组

数组中的每一个元素表明你在该位置能够跳跃的最大长度。优化

你的目标是使用最少的跳跃次数到达数组的最后一个位置。this

示例:spa

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 。
     从下标为 0 跳到下标为 1 的位置,跳  步,而后跳  步到达数组的最后一个位置。
213

说明:3d

假设你老是能够到达数组的最后一个位置。code

解题思路:htm

这题是以前那道Jump Game 跳跃游戏 的延伸,那题是问能不能到达最后一个数字,而此题只让咱们求到达最后一个位置的最少跳跃数,貌似是默认必定能到达最后位置的? 此题的核心方法是利用贪婪算法Greedy的思想来解,想一想为何呢? 为了较快的跳到末尾,咱们想知道每一步能跳的范围,这里贪婪并非要在能跳的范围中选跳力最远的那个位置,由于这样选下来不必定是最优解,这么一说感受又有点不像贪婪算法了。咱们这里贪的是一个能到达的最远范围,咱们遍历当前跳跃能到的全部位置,而后根据该位置上的跳力来预测下一步能跳到的最远距离,贪出一个最远的范围,一旦当这个范围到达末尾时,当前所用的步数必定是最小步数。blog

咱们须要两个变量cur和pre分别来保存当前的能到达的最远位置和以前能到达的最远位置,只要cur未达到最后一个位置则循环继续,pre先赋值为cur的值,表示上一次循环后能到达的最远位置,若是当前位置i小于等于pre,说明仍是在上一跳能到达的范围内,咱们根据当前位置加跳力来更新cur,更新cur的方法是比较当前的cur和i + A[i]之中的较大值,若是题目中未说明是否能到达末尾,咱们还能够判断此时pre和cur是否相等,若是相等说明cur没有更新,即没法到达末尾位置,返回-1。

C++解法一:

 1 class Solution {  2 public:  3     int jump(vector<int>& nums) {  4         int res = 0, n = nums.size(), i = 0, cur = 0;  5         while (cur < n - 1) {  6             ++res;  7             int pre = cur;  8             for (; i <= pre; ++i) {  9                 cur = max(cur, i + nums[i]); 10  } 11             if (pre == cur) return -1; // May not need this
12  } 13         return res; 14  } 15 };

还有一种写法,跟上面那解法略有不一样,可是本质的思想仍是同样的,关于此解法的详细分析可参见http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html。这里cur是当前能到达的最远位置,last是上一步能到达的最远位置,咱们遍历数组,首先用i + nums[i]更新cur,这个在上面解法中讲过了,而后判断若是当前位置到达了last,即上一步能到达的最远位置,说明须要再跳一次了,咱们将last赋值为cur,而且步数res自增1,这里咱们小优化一下,判断若是cur到达末尾了,直接break掉便可。

C++解法二:

 1 class Solution {  2 public:  3     int jump(vector<int>& nums) {  4         int res = 0, n = nums.size(), last = 0, cur = 0;  5         for (int i = 0; i < n - 1; ++i) {  6             cur = max(cur, i + nums[i]);  7             if (i == last) {  8                 last = cur;  9                 ++res; 10                 if (cur >= n - 1) break; 11  } 12  } 13         return res; 14  } 15 };

要理解这个算法,首先明白,这个题只要咱们求跳数,怎么跳,最后距离是多少,都没让求的。

大牛这个算法的思想主要是,扫描数组(废话。。。),以肯定当前最远能覆盖的节点,放入curr。而后继续扫描,直到当前的路程超过了上一次算出的覆盖范围,那么更新覆盖范围,同时更新条数,由于咱们是通过了多一跳才能继续前进的。

形象地说,这个是在争取每跳最远的greedy。举个栗子。

好比就是咱们题目中的[2,3,1,1,4]。初始状态是这样的:cur表示最远能覆盖到的地方,用红色表示。last表示已经覆盖的地方,用箭头表示。它们都指在第一个元素上。

接下来,第一元素告诉cur,最远咱能够走2步。因而:

下一循环中,i指向1(图中的元素3),发现,哦,i小于last能到的范围,因而更新last(至关于说,进入了新的势力范围),步数ret加1.同时要更新cur。由于最远距离发现了。

接下来,i继续前进,发现i在当前的势力范围内,无需更新last和步数ret。更新cur。

i继续前进,接下来发现超过当前势力范围,更新last和步数。cur已然最大了。

最后,i到最后一个元素。依然在势力范围内,遍历完成,返回ret。

这道题让咱们明白一个道理:

不要作无必要的计算。

对了,有同窗会问,那为啥要用last,直接用curr跳不就好了。直接用curr跳那每次都是跳最远的,可是最优路径不不必定是这样。该算法时间复杂度为O(n)。

C++解法三:

 1 /*
 2  * We use "last" to keep track of the maximum distance that has been reached  3  * by using the minimum steps "ret", whereas "curr" is the maximum distance  4  * that can be reached by using "ret+1" steps. Thus,  5  * curr = max(i+A[i]) where 0 <= i <= last.  6  */
 7 class Solution {  8 public:  9     int jump(int A[], int n) { 10         int ret = 0; 11         int last = 0; 12         int curr = 0; 13         for (int i = 0; i < n; ++i) { 14             if (i > last) { 15                 last = curr; 16                 ++ret; 17  } 18             curr = max(curr, i+A[i]); 19  } 20 
21         return ret; 22  } 23 };
相关文章
相关标签/搜索