来见识一下贪心算法:Jump Game
在这个算法题中,我们将看到贪心算法的应用,今天要讲的是LeetCode的第55号题目,Jump Game,这个题目是什么意思呢?
给定一个非负数的数组,最初我们在第一个数字的位置上,数组中的每一个数字表示在当前的位置上你最多可以跳多远,题目要求我们确定能否跳到数组中的最后一个数。
比如给定数组[2,3,1,1,4],你的代码应该返回true,这其中一个可能的跳跃方式是从2跳到3,然后从3跳到4;当然从2开始以一次一步的方式也可以跳到最后。
而给定数组[3,2,1,0,4],那么你的函数应该返回false,在这种情况下无论你用什么方式都不可能从3跳到最后的4。
那么我们该怎样解决这个问题呢?
我的思路
有的同学会觉得的这个问题不好解决,原因就在于给定一个位置,假设这个位置上的数字是6,也就是说从这个位置开始,你可以选择跳一步、也可以是两部、三步、四步、五步或者六步,有这么多可能性你该跳多少步呢?
顺着这个思路走下去的话,那么你只能把每一种可能性都试一遍了,还是以上图为例,从该位置开始有6种可能性,那么我们依次遍历每一个种可能的选择,各种遍历方式的组合数是非常巨大的,因此这种思路不可行。
正着走不通,那么让我们反向思考一下。
这个问题的反向思考是基于这样的一种观察,也就是,如果数组中有某个位置无法到达,那么我们就无法跳到最后一个节点,这是显而易见的,因为如果能跳到最后一个节点的话,那么我们必然要经过数组中的所有节点。换句话说就是如果我们能跳到最后一个节点的话,那么我们必然能跳到数组中任意一个节点。
有了这样的分析实际上就非常简单了,我们之前在很多个中都说过,有时解决一个通用的问题比解决一个具体的问题思考起来要简单,在这里具体的那个问题就是判断是否能跳到最后一个节点,而通用的那个问题就是能否跳到数组中任意一个节点,在这里我们就来思考在给定条件下能否跳到数组中任意一个节点,如果可以的话那么我们的函数应该返回true,否则返回false。
我们还是从最简单的情况一步一步来分析。
对于数组[2,3,1,1,4],首先我们会被放到2这个位置,接下来我们判断能否跳到3?
那么从2能否跳到3呢?答案很简单,当然是可以的,为什么呢?因为从2跳到3只需要一步,而第一个位置我们最远可以跳两步,因此我们可以跳到3:
这样我们来到了数组中的第二个数,也就是3,接下来就比较有意思了。
此时我们的任务就是判断从节点3能否跳到节点1,那么可以跳到吗?
答案是显而易见的,当然可以跳到,为什么呢?因为在当前位置上我们可以最多跳3步,而从3跳到1只需要一步,那么如果当前位置上的数不是3而是0呢?
那么我们还能跳到1吗?答案是可以的,因为虽然我们不能从0跳到1,但是可以从2直接跳到1,从这两种情况下你能得到什么启示?
以上两种情况给我的启示就是,能否跳到当前位置取决于当前位置之前的所有位置上的步数,有的同学会说这不是废话吗,是的,这是一句废话,这句废话会引导我们进一步思考,那就是这到底是怎么的取决呢?
让我们再次回到数组[2,3,1,1,4]的例子,从2可以跳到1,从3也可以跳到1,但是2最多可以跳到1,超出0步,而3最多可以跳到4,超过了1这个位置两步,如图所示:
那么这张图告诉我们什么呢?是什么样的条件决定了能不能跳到当前位置呢?
如果我们能够保证从在此位置之前任意一个位置(前提是我们可以从数组开头跳到这些位置)起跳能跳到或跳过当前位置,那么我们就可以确定可以从数组开头跳到当前位置。
因此我们需要记下在该位置之前的所有位置起跳能超过当前位置的最大步数,只要这个最大步数经过每一个位置时都大于0,那么我们就能确定一定可以从开头跳到最后。而这本质上就是贪心算法,贪心算法的思想是我们总是在当前就做出最好的选择,而不是从整体上加以考虑。
上述结论比较拗口,举个例子你就明白啦,我们还是以[2,3,1,1,4]为例来说明。
最开始我们在位置2上,此时我们可以认为从位置2起跳可以超过当前位置2步,因此最大步数是2,我们将这个最大步数记为max_step,也就是能超过当前位置的最大步数,我们的任务就是要保证在每个位置max_step都要大于0,这样我们就一定能到达最后,如图所示:
接下来是位置3,此时我们需要将max_step减1,此时max_step为1是大于0的,因为我们向前走了一步。我们可以选出从3开始起跳,从而能超过3这个位置3步,也可以选择使用max_step的起跳位置,也就是2,因此这个最大步数就是max_step = max(3, max_step-1),这时max_step就是3。
接下来是位置1,同样将max_step减1,此时max_step为2是大于0的,在这里我们可以选择从1这个位置起跳,也可以选择使用max_step的起跳位置,也就是位置3,这样我们又得到了能新的max_step,即max_step = max(1, max_step-1)。
重复上述过程直到数组最后,如果中途发现max_step已经等于0了,那么说明我们无法从数组开头跳到最后。
基于上述分析,代码就很简单了。
代码实现
bool canJump(vector<int>& nums) {
int len = nums.size();
if(len == 0 || len == 1)
return true;
int step=nums[0];
for(int i=0;i<len-1;i++){
step = max(step-1,nums[i]);
if(step<=0)
return false;
}
return true;
}
怎么样,有了仔细的分析,代码是不是不要太简单?
总结
在本文中我们首次见到了贪心算法,贪心算法在每次做决策时总是选择在当前看来最好的那个,值得注意的是,不是所有问题都适用于贪心算法,那么什么样的问题才适用此算法呢,现在就去讲解该理论的话可能理解不会那么深刻,在后续文章中我们还会遇到该算法,见得多了你自然就会明白什么样的问题可以用贪心算法,到那时我们再来详解讲解该理论。
为你推荐
1,
2,
3,