飞道的博客

九章算法 | 美团面试算法题:跳跃游戏

281人阅读  评论(0)
撰文 | JZ
专栏 | 九章算法

题目描述

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

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

判断你是否能到达数组的最后一个位置。


样例 1

        
    
  1. 输入 : [2,3,1,1,4]
  2. 输出 : true

样例 2

        
    
  1. 输入 : [3,2,1,0,4]
  2. 输出 : false

题解

这个问题有两个方法,一个是贪心和 动态规划。

贪心方法时间复杂度为O(N)。

动态规划方法的时间复杂度为为O(n^2)。

九章参考程序

jiuzhang.com/solution/j

1. DP1

每到一个点 i,我们扫描之前所有的点,如果之前某点j本身可达,并且与current 点可达,表示点i是可达的。

返回值:DP数组的最后一个值。

        
    
  1. // DP1.
  2. public boolean canJump1 ( int [] A ) {
  3. if ( A == null || A . length == 0 ) {
  4. return false ;
  5. }
  6. int len = A . length ;
  7. boolean [] can = new boolean [ len ];
  8. can [ 0 ] = true ;
  9. for ( int i = 1 ; i < len ; i ++) {
  10. can [ i ] = false ;
  11. for ( int j = 0 ; j < i ; j ++) {
  12. // j can arrive and can jump to i.
  13. if ( can [ j ] && A [ j ] >= i - j ) {
  14. can [ i ] = true ;
  15. break ;
  16. }
  17. }
  18. }
  19. return can [ len - 1 ];
  20. }


2. DP2

优化的点1:既然某点可达,肯定前面的点全部是可达的。这个比较好理解。因为你到达i点肯定要经过前面的一个点,这个依次推知可知前面所有的点可达。

所以我们不需要数组来记录结果,只要默认i点前全部可达就行了。

优化点2:如果某点不可达了,直接返回false。不需要再往后计算。

返回值:如果全部扫完仍没有返回false,返回true。

        
    
  1. // DP2.
  2. public boolean canJump2 ( int [] A ) {
  3. if ( A == null || A . length == 0 ) {
  4. return false ;
  5. }
  6. int len = A . length ;
  7. for ( int i = 1 ; i < len ; i ++) {
  8. boolean can = false ;
  9. for ( int j = 0 ; j < i ; j ++) {
  10. // j can arrive and can jump to i.
  11. if ( A [ j ] >= i - j ) {
  12. // 说明i是可达的,置标记位
  13. can = true ;
  14. break ;
  15. }
  16. }
  17. // 优化:如果某一步已经到不了了,后面的也不必再计算了.
  18. if (! can ) {
  19. return false ;
  20. }
  21. }
  22. return true ;
  23. }

3. 递归

思想是,从前至尾扫描,至第一个距离与本点可达的点j,计算j点是否可达。使用递归计算j点的可达性。

其实这里还是用到了贪心的思维。在考虑本点是否可达的时候,我们是考虑与本点最远的一个点是否可达。实际上这也make sense。

假设j点可以到达i点,那么后面的点可以不管。

(1)因为如果j点不可达,那么j+1也不可达。如果i不可达,后面的点也可不算。

(2)如果j点可达,并且j点可到达i,那么也没有必要再往后计算了。因为结论已经出来了。

(3)如果j点可达,但j不可达i,那么就继续计算。

        
    
  1. // 3. DFS.
  2. public static boolean canJump3 ( int [] A ) {
  3. if ( A == null || A . length == 0 ) {
  4. return false ;
  5. }
  6. return canJump ( A , A . length - 1 );
  7. }
  8. public static boolean canJump ( int [] A , int index ) {
  9. if ( index == 0 ) {
  10. return true ;
  11. }
  12. for ( int i = 0 ; i <= index - 1 ; i ++) {
  13. if ( A [ i ] >= index - i ) {
  14. return canJump ( A , i );
  15. }
  16. }
  17. return false ;
  18. }


4. 贪心法

我们现在来使用贪心法One pass解决此问题。

维护一个right (表示右边能跳到的最远的点),从左往右扫描,根据当前可跳的步骤不断更新right ,当right到达终点,即可返回true. 若更新完right后,right未动,并且index = right,而且这时没到达终点,代表我们不可能到达终点了。(当前index的可跳值应该是0)。

        
    
  1. // solution 3: one pass.
  2. public boolean canJump ( int [] A ) {
  3. // 4:42
  4. if ( A == null ) {
  5. return false ;
  6. }
  7. int len = A . length ;
  8. int right = 0 ;
  9. for ( int i = 0 ; i < A . length ; i ++) {
  10. right = Math . max ( right , i + A [ i ]);
  11. if ( right == len - 1 ) {
  12. return true ;
  13. }
  14. if ( i == right ) {
  15. return false ;
  16. }
  17. }
  18. return true ;
  19. }


对于大厂面试中高频出现的经典题,我们需要了解它的不同解法,有的大厂不喜欢你一上来就秒掉题目,而更重视你一步步根据题目优化解法,最后得到最优解的解题思路和过程。

另外即使运气真的很不好碰到了新题,做过的高频题也会激发你解题的思路。

算法面试高频题班免费试听,攻略更多大厂常考题型。


转载:https://blog.csdn.net/JiuZhang_ninechapter/article/details/105930329
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场