leetcode [Greedy] No.45 Jump Game II

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Richard_M_pu/article/details/83242724

题目描述

Given an array of non-negative integers, you are initially positioned at the first index of the array.Each element in the array represents your maximum jump length at that position.Your goal is to reach the last index in the minimum number of jumps.

Example:

Input: [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2.Jump 1 step from index 0 to 1, then 3 steps to the last index.

Note:
You can assume that you can always reach the last index.

解题思路

思路一

看到这道题,一开始我想到的是一种类似动态规划的解法,即维护一个数组,该数组记录到当前位置所需要的最小步数。总的算法步骤是一个二层循环,我们首先遍历输入所给的数组,在访问每个元素的时候,从当前元素的位置,循环到从当前位置可以跳到的最远距离,每次比较当前位置的步数+1是否比之后的元素步数要小,如果小,则更新之后元素的最小步数。当我们遍历完整个数组的时候,答案即是该数组的最后一个元素的值。

代码:

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        vector<int> vec(size, -1);
        vec[0] = 0;
        for (int i = 0; i < size; i++) {
            for (int j = i+1; j < min(size, nums[i] + i + 1); j++) {
                if (vec[j] == -1 || vec[j] > vec[i] + 1) {
                    vec[j] = vec[i] + 1;
                }
            }
        }
        return vec[size - 1];
    }   
};

运行结果:
在这里插入图片描述
然而这种算法运行超时了,可以看出,上述算法的效率为 O ( n 2 ) O(n^2) ,而结果显示,一共92组测试样例,我们一共通过了91组,最后一组样例的数组大小为25000,所以 O ( n 2 ) O(n^2) 的效率队这种大小的数组来说还是太低了。

思路二

既然 O ( n 2 ) O(n^2) 的效率不够,那我们就需要想一种更加高效的算法。我们想想,是否有一种算法只遍历一遍数组就将答案求解出来呢?答案是肯定的,我们考虑下面这一种情况,当我们搜索到某个元素 a i a_i 时,从 a i a_i 最远可以跳到 a i + m a_{i+m} ,那我们来分析下一步应该如何走。当前数组片段为 [ &ThinSpace; , a i , a i + 1 , a i + 2 , &ThinSpace; , a i + m , &ThinSpace; ] [\cdots, a_i, a_{i+1}, a_{i+2}, \cdots, a_{i+m}, \cdots] ,假设 a i a_i 的前一个元素可以跳到 a j a_j ,则 i j i + m i \leq j \leq i+m ,这很容易得出,因为若 j &lt; i j &lt; i ,则我们不可能跳到 a i a_i ,若 j &gt; i + m j &gt; i+m ,那么我们上一步为什么不直接跳到 a j a_j 呢(假设上一步的决策是正确的)?那么我们可以得到,在做出上一步的决策的时候,我们已经访问过了 [ a i , a j ] [a_i,a_j] 这个区间,且得出跳到 a i a_i 是最好的决策,所以在区间 [ a i , a j ] [a_i, a_j] 这个区间我们在做当前这步决策的时候我们就可以不用再访问了,因为这一步的决策一定不再这个区间中国,直观的证明: 如果我们这步决策是跳到区间 [ a i , a j ] [a_i, a_j] 中,那一定有一种更优的决策,即上一步直接跳到这个区间中,在上一步的决策是正确的情况下,那我们这一步则一定不会跳到这个区间中,所以这一步我们只需访问 [ a j + 1 , a i + m ] [a_{j+1},a_{i+m}] 这个区间中的元素。这样我们便保证了在求解这个问题的时候,对任意一个区间,不会重复搜索两次,那么整个数组只遍历了一次。

我们再来讨论遍历时如何做出决策。按照贪心的思想,肯定是能跳的越远越好,但是我们不能单纯的按照当前的最远距离跳,我们需要将眼光放的长远一点,我们可以叫它“两步贪心”,即我们在当前可跳的区间内,找一个元素,使得它下一步可以跳的最远,即找到“位置+可跳距离”最大的元素,然后下一步我们就跳到这个元素。

代码:

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        int count = 0, ptr = 0, subptr = 0;
        while (ptr < size) {
            int step = 0, end = subptr + nums[subptr] + 1;
            for (; ptr < min(size, end); ptr++) {
                if (ptr + nums[ptr] >= step) {
                    step = ptr + nums[ptr];
                    subptr = ptr;
                }
            }
            count++;
        }
        return min(count, size - 1);
    }
    
};

Tips: 在评测代码的时候我发现,有一组样例数据是 [ 0 ] [0] ,但是我们这个算法中,最少会运行一步,即输出最少为1,但是这组数据的结果应该是0,所以在输出结果的时候我加了一个限制,即最差的结果就是数组长度-1,即每次我们只跳一步,所以不可能会有比这更差的结果,如果我们算出来的结果比这个大,则舍弃我们计算的结果,输出数组长度-1.

扫描二维码关注公众号,回复: 3745186 查看本文章

运行结果:
在这里插入图片描述
因为整个数组只访问了一遍,所以我们很容易的可以得出,这个算法的效率为 O ( n ) O(n) ,整个运行时间只有12ms,还是很高效的。

猜你喜欢

转载自blog.csdn.net/Richard_M_pu/article/details/83242724