LintCode刷题——跳跃游戏Ⅱ(贪心法)
题目描述:
给出一个非负整数数组,你最初定位在数组的第一个位置。
数组中的每个元素代表你在那个位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
样例 1
输入 : [2,3,1,1,4]
输出 : 2
解释 : 到达最后位置的最小跳跃次数是2(从下标0到1跳跃1个距离长度,然后跳跃3个距离长度到最后位置)
思路:
一开始对于这题无从下手,后面结合自己大二下学期学的算法分析与设计中的贪心算法,贪心算法有两个重要的性质
① 贪心选择性质:所谓贪心算则性质是指问题的整体最优解可以通过一系列局部最优选择,即贪心选择来得到。这一点不同于动态规划算法,后者每一步也要做出选择,但所做的选择往往要依赖于相关子问题的解,只有在解出相关子问题的解之后才能做出选择。而贪心算法中所做的总是当前(看似)最佳的选择,当前所做的选择要依赖于已经做出的所有选择,但不依赖于随后要做出的选择,也不依赖于子问题的解。对于一个具体的问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终会导致问题的整体最优解。
② 最优子结构性质:最优子结构性质在将动态规划算法章节中已详细阐述过,即当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质,也称此问题满足最优化原理。最优子结构性质是该问题能用动态规划算法或贪心算法求解的关键特性。
二刷心得:
这里容易忽视的一点,就是本来自己脚下的步数就已经可以直接跳到最后一个位置,但是因为我们用的贪心法是找后面能跳过去的位置下的数字的最大值,然后跳到那个位置去,可是如果此时最后一个位置的数小于前面的话,那么你就有可能跳在前面,这样就增加了跳的次数,也就不是用最少次数到达数组的最后一个位置。当选最大的时候,如果与最大值相等,也要替换,意思就是能往后面跳就尽量往后跳,这也是贪心法的精髓之处
贪心算法的求解过程:
(1)候选集合
(2)解集合
(3)解决函数
(4)选择函数
(5)可行函数
一刷代码:
1 public class Solution { 2 /** 3 * @param A: A list of integers 4 * @return: An integer 5 */ 6 public int jump(int[] A) { 7 // write your code here 8 if (A == null || A.length == 0) { 9 return 0; 10 } 11 12 int n = A.length; 13 14 // 用于计数 15 int count = 0; 16 // 从第1个位置开始 17 int i = 1; 18 while (i < n) { 19 // 考虑自己能否一下能跳到最后 20 if (i + A[i - 1] >= n) { 21 count++; 22 break; 23 } 24 int maxv = Integer.MIN_VALUE; 25 int maxPos = 0; 26 // 找到当前位置能跳到的最大范围内的最大的值 27 for (int j = i + 1; j <= i + A[i - 1] && j <= n; j++) { 28 // 这里很容易忽视,因为如果某些位置的值都是一样的值,我们也应该选择跳到最后面的一个位置 29 if (A[j - 1] >= maxv) { 30 maxv = A[j - 1]; 31 // 更新位置 32 maxPos = j; 33 } 34 } 35 count++; 36 // // 跳完之后需要再判断一下是否已经到了第n个位置,这里的if判断也可不要 37 // 因为外层的while循环还会再判断,如若到达最后一个位置,也会跳出 38 // if (maxPos >= n) { 39 // break; 40 // } 41 // 直接到这个位置去 42 i = maxPos; 43 } 44 45 return count; 46 } 47 }
二刷代码:
1 public class Solution { 2 /** 3 * @param A: A list of integers 4 * @return: An integer 5 */ 6 public int jump(int[] A) { 7 /* 8 这里容易忽视的一点,就是本来自己脚下的步数就已经可以直接跳到最后一个位置,但是因为我们 9 用的贪心法是找后面能跳过去的位置下的数字的最大值,然后跳到那个位置去,可是如果此时最后 10 一个位置的数小于前面的话,那么你就有可能跳在前面,这样就增加了跳的次数,也就不是用最少 11 次数到达数组的最后一个位置。 12 */ 13 if (A == null || A.length == 0) { 14 return 0; 15 } 16 17 int n = A.length; 18 19 // 定义一个变量,记录所用步数 20 int count = 0; 21 // 指针,指向当前位置 22 int index = 0; 23 // 如果到了数组的最后一个位置,就跳出循环 24 while (index < n - 1) { 25 // 判断当前脚下的最大步数是否能直接一步跳到最后一个位置 26 // 这样不能用等于,要用大于等于 27 if (index + A[index] >= n - 1) { 28 count++; 29 break; 30 } 31 // 枚举当前能跳到的后面所有的位置 32 // i代表距离当前位置的距离 33 int maxv = Integer.MIN_VALUE; 34 // 记录当前下一步要跳到的位置 35 int j = index; 36 for (int i = 1; i <= A[index] && index + i < n - 1; i++) { 37 // 这一点在两次做的时候都没有第一直觉考虑到,说明对于我来说是个容易忽视的点 38 // 如果与最大值相等,也要替换,意思就是能往后面跳就尽量往后跳 39 if (A[index + i] >= maxv) { 40 maxv = A[index + i]; 41 j = index + i; 42 } 43 } 44 // 跳到j位置 45 index = j; 46 // 增加步数 47 count++; 48 } 49 50 return count; 51 } 52 }
声明
经发现LintCode的数据太弱所以过了,后面用同样的代码去leetcode提交了下没过,然后用dp就过了。其实仔细一想也是,对于这题而言贪心还是有不足的地方(例如:[10,9,8,7,6,5,4,3,2,1,1,0]),之后更新了该题的题解及dp的代码:https://www.cnblogs.com/pengsay/p/14730445.html