赞
踩
问题概述:
使用最小的步数到达数组末尾。注意每个位置上的元素是在该位置上最多能往后跳的步数,而不是必须跳这么多步,不然有可能根本到不了数组末尾。
问题分析:
这个问题比较复杂,想一下子得到正确思路难度比较大,我们从题目所给的这个例子出发,尝试着寻找解题思路。
首先前两个位置肯定是很好分析的。第一个位置,我们一开始就在这里,所以步数是0.第二个位置也很简单,只能从第一个位置跳过来,所以最小步数是1.在数组长度为1或者2时,就可以直接返回。
那么第三个位置呢?这就有两种选择:从第一个格子跳过来或者从第二个格子跳过来。从第一个格子跳过来,只需要一步;而从第二个格子跳过来,需要两步:第一个格子跳到第二个格子,再从第二个格子跳到第三个格子。这样就涉及了比较。
第四个位置的分析遇上类似。对于更一般的情况,即第三个格子及以后的任意位置,我们需要遍历前面所有的格子,挨个判断两件事情:
1.能不能从这个格子直接跳到当前位置上来;如果能则判断下一件事,不能就continue;
2.如果能的话,那么从这个格子跳到当前位置上来总共需要多少步?是否存在更优的解?
这里就需要比较了。每进行一轮循环,就把这个步数与代表最小步数的变量作比较。
更重要的是,我们发现每个位置上的最小步数都是与这个位置有一一对应关系,是由他前面的所有位置上的元素所决定的,这让我们很自然的想到动态规划。联想能力强的小伙伴可能想到了青蛙跳台阶问题,不过那个里面青蛙一次只能跳1级或者2级,而在这个问题里面他可以选择的跳的级数很多,但是总体想法有相通之处。
方法1即由上面的分析自然引出,方法2是方法1的优化。
我们做一个辅助数组temp,初始化为0,1,integer.maxvalue,integer.maxvalue…直到数组末尾。temp[i]表示从第一个位置跳到第i个位置的最小步数。
声明两个循环变量i,j,分别对应外层循环和内层循环。再声明一个变量minstep用来存放最小步数,初始化为integer.maxvalue.对于某个任意位置i,我们有:
对i之前的所有位置进行遍历,先判断i-j是否小于等于nums[j].这决定了能否从j位置一部跳到i位置上。我们同时还将temp[j]与minstep作比较,决定从j位置跳到i位置是否可以减小总的跳跃步数。内循环走完一轮之后,将temp[i]赋值为minstep+1.同时不要忘记将minstep回复成integer.maxvalue。
随着外层循环指针i的不断后移,temp数组也慢慢的被填起来。当填充完成之后,返回temp数组的最后一个元素即可。
根据以上分析,写出如下代码:
试运行之,结果如下:
代码可以通过,但还有很大的提升空间。可以分析得知,该方法对于每个元素都要遍历之前所有的元素,时间复杂度为O(n^2).方法2是对方法1的改进,改的也不算多,只改变了内层循环:
与方法1不同,我们没有在到达一个位置之后从头开始遍历,进而得到当前位置的最小步数值,而是主动出击,在访问某个位置i的时候,就根据该位置上的nums[i]值,求出后面nums[i]个位置的最小步数值.与上类似,我们只有在出现更优情况时才对temp[i+j]做更新.这样避免了大量的重复遍历,有效提高了效率.可以发现时间复杂度与数组各元素的和线性相关(并且斜率应该为1).在靠近数组末尾时,还有很多遍历步数被数组长度所截断,这更加减少了循环遍历的次数.试运行之,看看优化效果如何.
可以发现优化效果非常明显.因此确定方法2为本题最终解法.
总结与归纳:
这是一个典型的动态规划问题,我们选择使用动态规划的思想是因为这道题具有明显的动态规划特征:最终问题可以由前面的简单小问题一步一步推导而来,同时每个位置上的元素和之前的元素有直接的依赖关系.上文提到,该问题与青蛙跳台阶很类似,而青蛙跳台阶也是动态规划的经典例子.
除此之外,也可以利用前面元素直接推导后面的元素,而不是在访问后面元素的时候,又倒过来把它前面的都遍历一遍(这个笨办法是对上面一段的直接翻译),可以有效的优化运行时间.
这里有一道类似的题目,有兴趣的小伙伴可以看一看:
这个问题的分析与解决就告一段落了.希望大佬批评指正,希望对小伙伴们有所帮助!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。