LeetCode045——跳跃游戏II

版权声明:版权所有,转载请注明原网址链接。 https://blog.csdn.net/qq_41231926/article/details/82751536

我的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解题报告:

猜你喜欢

转载自blog.csdn.net/qq_41231926/article/details/82751536