在上一篇文章中我们看到了Jump Game这道算法题,这个题目还有第二问,这就是LeetCode中的第45号题目,Jump Game II,题目还是给定一个数组,这次我们可以认为一定可以从开头跳到数组结尾,要求我们计算出最少只需要几步就可以跳到最后。
比如给定数组[2,3,1,1,4],那么我们的函数需要返回2,我们只需要从2跳到3,然后从3跳到最后的4,这样我们只需要两步就可以了,这是从开头跳到结尾需要的最少步数。
那么该怎么解决这问题呢?
思路
对于这个问题我们可以继续使用反向思考的方法,我们来考虑一下那些位置可以直接跳到最后,如图所示:

假设有三个节点A、B和C都可以直接调到最后,那么步数最少的跳法必然要经过要么是A、要么是B、要么是C,那么到底最短步数的跳法是经过谁呢?
答案是显而易见的,当然是跳到A最好,原因就在跳到A需要的步数最少,可以给前面预留出更多选择的空间,如果某种步数最少的跳法能跳到B或者C,那么必然可以跳到A,因为A在B和C的前面。
有了这个发现就很简单了,我们只需要对数组进行若干次遍历就可以了。
第一次从左到右依次遍历找到第一个可以跳到最后的节点,我们记为A,如图所示:

接下来我们的任务还是从头开始遍历数组,只不过这次找的不是第一个能跳到最后的节点,而是找到第一个能跳到A的节点,我们将其记为A1,如图所示:

现在你应该明白了吧,重复上述过程直到从开头就能跳到指定的位置,如图所示:

这样我们找到的跳法一定是最短的,代码实现也非常简单,不过这种跳法的时间复杂度为O(n^2),无法满足题目的要求。
那么该怎么优化呢?
优化思路
这上一中解法里我们是从最后一步出发来考虑的,这要求我们需要不断从数组开头遍历,从而使得算法时间复杂度为O(n^2),我们有没有办法能确定每一步的最优跳法是什么样的吗?接下来让我以数组[4,5,1,2,3,4]为例来说明,不同于之前的是我们在图中将每一步最远能跳多少也标识出来,如图所示:

我们知道第一步肯定是从数组的第一个位置开始起跳的,那么关键就在于接下来该怎么跳?
要想知道这个问题的答案我们就必须要知道从第一步可能跳到哪些位置,如图所示:

从图中我们可以看出,在第一步起跳可以跳到第2、3、4和5这几个位置,那么我们到底要跳到哪个位置呢?
显而易见,我们要跳到5这位置,原因也很简单,因为从位置5再次起跳时跳的距离的最远,只有跳到5这个位置在第二步中我们的选择余地才最大,如图所示:

接下来你应该明白了吧,从位置5开始重复上述过程直到跳到最后一个位置。
这个过程本质上还是贪心算法的应用,在每一个位置上我们都选择下一步能跳的最远距离的位置。
有了这些分析,代码就很简单了。
代码实现
上述代码非常简单,时间复杂度为O(n),较第一种解法有了很大的优化。
int jump(vector<int>& nums) {
int l = nums.size();
int steps = 0;
int max_last_step = 0;
int max_so_far = 0;
for (int i = 0; i < l-1; i++) {
max_so_far = max(max_so_far, i + nums[i]);
if (i >= max_last_step) {
max_last_step = max_so_far;
steps++;
if (max_so_far >= l - 1) return steps;
}
}
return steps;
}
总结
这这个题目中我们再一次见到了贪心算法的应用,实际上在该题目的两个解法中都应用了贪心算法,这里再次强调一下不是所有的题目都可以使用贪心算法,贪心算法的应用是用限定条件的,当我们在后续题目中多次使用贪心算法后这个限定条件就比较明显了。
为你推荐
本文详细解析LeetCode第45题Jump Game II,通过反向思考和贪心算法,找出从数组开头到结尾的最少跳跃步数。通过示例解释如何从每个位置选择能跳得最远的下一点,实现O(n)时间复杂度的优化解法。

417

被折叠的 条评论
为什么被折叠?



