我的LeetCode代码仓:https://github.com/617076674/LeetCode
原题链接:https://leetcode-cn.com/problems/jump-game-ii/description/
题目描述:
知识点:贪心算法
思路一:回溯法(在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);
}
}
}
思路二:动态规划(在LeetCode中提交会超时)
状态定义: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];
}
}
思路三:贪心算法
既然动态规划都超时了,那么必然就得用贪心算法了。贪心算法的本质是在动态规划的基础上舍弃一些不可能的情况,类似于回溯算法的剪枝过程。
对于本题而言,假设我们现在在索引i的位置。
如果索引i的值为0,那么我们不可能再继续前进了,这种情况舍弃。
如果索引i的值不为0,那么我们下一步可以走到索引i + k(1 <= k <= nums[i])。而在索引i + k我们又可以走到索引i + k + p(1 <= p <= nums[i + k]),我们选取索引i + k的原则是i + k + p取得最大值。
对于贪心算法而言,其实现永远不是重点,其重点在于为什么可以使用贪心算法。那么,为什么本题可以这样使用贪心算法呢?
(1)如果此时i + k + p的最大值仍然小于数组的长度 - 1,说明经过此步还未能抵达数组中最后一个元素。
对于索引i而言,假设索引i下一步的最优解为索引i + k。索引i + k的下一步所能到达的范围是索引i + k + 1 ~ i + k + nums[i + k]。
假设索引i + k不是索引i下一步的最优解,索引i下一步的最优解为索引i + j(j != k)。索引i + j的下一步所能到达的范围是索引i + j + 1 ~ i + j + nums[i + j],由索引i + k的定义可知,i + j + nums[i + j] <= i + k + nums[i + k]。
如果j >= k,那么i + j + 1 >= i + k + 1,索引i + j的下一步所能到达的范围是小于索引i + k所能到达的范围的,即如果索引i + j能到的地方,索引i + k也能到,但是索引i + k能到的地方,索引i + j却不一定能到。因此索引i下一步的最优解一定只可能是i + k。
如果j < k,对于索引i + k + 1及其之后的索引位置,索引i + j的下一步所能到达的范围是小于索引i + k所能到达的范围的,即如果索引i + j能到的地方,索引i + k也能到,但是索引i + k能到的地方,索引i + j却不一定能到。而对于索引i + k + 1之前的索引位置,索引i + k是到不了的,索引i + j能到,但是此时多走了一步路。因为我们最终肯定是要跨过索引i + k,我们本可以一步到达索引i + k的位置,下一步就跨过索引i + k了,现在我们第一步到达索引i + j的位置,下一步还不能保证跨过索引i + k。因此索引i下一步的最优解一定只可能是i + k。
(2)如果此时i + k + p的最大值大于等于数组的长度 - 1,说明经过此步能抵达数组中最后一个元素,显然索引i + k是最优解。
时间复杂度最差情况是O(n)级别的,其中n为nums数组的长度。在我的实现中,每次寻找i + k使得i + k + p最大的过程中都新建了一个数组,因此在我的实现中空间复杂度是O(n)级别的。
JAVA代码:
public class Solution {
//greedy algorithm
public int jump(int[] nums) {
int n = nums.length;
int steps = 0;
int index = 0;
while(index < n - 1) {
steps++;
int[] lengths = new int[nums[index]];
if(index + nums[index] >= n - 1) {
break;
}
for (int i = index + 1; i <= index + nums[index]; i++) {
lengths[i - index - 1] = i + nums[i];
}
int max = 0;
for (int i = 0; i < lengths.length; i++) {
if(lengths[i] > lengths[max]) {
max = i;
}
}
index = max + index + 1;
}
return steps;
}
}
LeetCode解题报告: