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

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

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输入: [2,3,1,1,4]

输出: 2

解释: 跳到最后一个位置的最小跳跃数是 2。

     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

主函数

public static void main(String[] args) {
    System.out.println("请输入一个数组");
    Scanner sc = new Scanner(System.in);
    String str = sc.nextLine().trim();
    String[] strings = str.substring(1,str.length()-1).split("\\,");
    int[] nums = new int[strings.length];
    for (int i =0;i<strings.length;i++){
        nums[i] = Integer.parseInt(strings[i]);
    }
    System.out.println(jump(nums));
}

思路1  贪心

贪心算法的本质是在动态规划的基础上舍弃一些不可能的情况,类似于回溯算法的剪枝过程。

每一次都是下一步的贪心。下一步贪心最远的就选择哪个

其实重点不在于判断当前位置跳几步,而是要记录在当前位置可以到达的范围内,下一跳可以到达的最远位置,如下面代码中的里层for循环,就记录了当前位置可以到达的范围内,下一跳可以到达的最远位置,而并不关心从当前位置跳多少步。

class Solution {
    public int jump(int[] nums) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int reach = 0;//当前所能到达的最远坐标
        int last = 0;//上一跳可达最远坐标
        int count = 0;//跳跃次数
        for (int i = 0; i < nums.length; i++) {
            if (i > reach) {
                return -1;
            }
            if (i > last) {
                count++;
                last = reach;
            }
            if (i + nums[i] > reach) {
                reach = i + nums[i];
            }
        }
        return count;
    }
}

思路2 动态规划

定义状态:dp[i] 表示到达 i 位置的最小跳数

起始装填:dp[0]=0 到达下标0的最小跳数是0

终止状态:dp[nums.length-1] 即到达最后一个位置的最小跳数

决策:选择 i 之前的能跳到 i 的所有位置j 中, dp[j] 值最小的位置 j 作为上一步要跳到 i 的位置

费用表示:dp[i]=dp[j]+1 ,从 j 跳到 i ,步数加1

无后效性:收益1 是个常数,不随状态改变

class Solution {
    public int jump(int[] nums) {
        int[] dp = new int[nums.length];//dp[i] 为到达 i 位置的最小跳数
        dp[0] = 0;//到达下标0的最小跳数是0
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Integer.MAX_VALUE;
            for (int j = 0; j < i; j++) {
                if (i - j <= nums[j]) {
                    dp[i] = Math.min(dp[i], dp[j] + 1);
                }
            }
        }
        return dp[nums.length - 1];
    }
}

方法二

状态定义:f(x, y)--------表示从索引x,走到索引y的最短步数
状态转移:

(1)如果nums[x] + x >= y,说明一步就可以从索引x走到索引y,f(x, y) = 1。

(2)如果nums[x] + x < y,f(x, y) = 1 + min{f(x + 1,y), f(x + 2, y), ... , f(x + nums[x], y)}。

时间复杂度和空间复杂度都是O(n ^ 2)级别的。

JAVA代码:

public class Solution {
 
	//dynamic programming
	public int jump(int[] nums) {
        int n = nums.length;
        if(n == 1) {
        	return 0;
        }
        int[][] steps = new int[n][n];
        for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				steps[i][j] = n - 1;
			}
		}
        for (int i = 0; i < n; i++) {
			steps[i][i] = 0;
		}
        for (int i = 0; i >= 1 - n; i--) {
			for (int j = 0; j < n + i; j++) {
				if(nums[j] + j >= j - i) {
					steps[j][j - i] = 1;
				}else {
					for (int k = 1; k <= nums[j]; k++) {
						steps[j][j - i] = Math.min(steps[j][j - i], 1 + steps[j + k][j - i]);
					}
				}
			}
		}
        return steps[0][n - 1];
    }

思路3:回溯法(在LeetCode中提交会超时)

回溯法的思想很简单,寻找到所有能到达数组的最后一个位置的可能路径
,计算其最短值即可。由于是穷举,其时间复杂度是很高的,达到了
O(nums[0] + nums[1] + nums[2] + ... + nums[n - 1])级别,其中n为nums数组的长度。
而空间复杂度则是递归深度,是O(n)级别的。

JAVA代码:

//backtracking
public class Solution {
	
	int steps;
 
	public int jump(int[] nums) {
        int n = nums.length;
        steps = n - 1;
        
        jump(nums, 0, 0);
        
        return steps;
    }
	
	/*
	 * Now I'm in the indexth position of nums, I have take tempSteps steps
	 */
	private void jump(int[] nums, int index, int tempSteps) {
		if(index >= nums.length - 1) {
			if(index == nums.length - 1) {
				steps = Math.min(steps, tempSteps);
			}
			return;
		}
		for (int i = 1; i <= nums[index]; i++) {
			jump(nums, index + i, tempSteps + 1);
		}
	}
}