[贪心算法]LeetCode 45.跳跃游戏

题面

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

数组中的每个元素代表你在该位置可以跳跃的最大长度。

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

链接: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不加时候所能到达的最远距离。当然这个题之所以可以贪心完全是因为每一步可以走任意步数。

AC代码

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]

你可能感兴趣的:(贪心)