前言
“ 贪心算法“是指,在对问题求解时,总是做出在当前看来是最好的选择。
也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。也就是说,不从整体最优上加以考虑,做出的只是在某种意义上的局部最优解。”
题目描述
给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
解题思路
方法一:
-
我们的目标是到达数组的最后一个位置,因此我们可以考虑最后一步跳跃前所在的位置,该位置通过跳跃能够到达最后一个位置。
-
如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上来看,我们可以
「贪心」
地选择距离最后一个位置最远的那个位置,也就是对应下标最小的那个位置。 -
因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。 找到最后一步跳跃前所在的位置之后,我们继续贪心地寻找倒数第二步跳跃前所在的位置,以此类推,直到找到数组的开始位置。
示例:
var a = [2,3,1,2,4,2,3];
-
从左往右找到能跳数组末尾的元素 index = 4 的时候 step(跳跃次数)++
-
从左往右只要找到能跳到(index = 4), 就从(index = 1)结束, 因为局部离(index = 4)最远为最优解法。step(跳跃次数)++
-
重复上述过程从(index = 1)开始查找。
AC代码
var jump1 = function(nums) {
var position = nums.length - 1;
var steps = 0;
while (position > 0) {
for (var i = 0; i < position; i++) {
if (i + nums[i] >= position) {
position = i;
steps++;
break;
}
}
}
return steps;
}
方法二(优化时间复杂度):
如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
示例:
var a = [2,3,1,2,4,2,3];
-
从左往右(index = 0), 挑选能跳最远的元素(index = 1), 然后再从(index =1)出发,再一次找到能跳最远的元素(index = 4) ,最后就直接跳跃到数组末尾
-
在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素
AC代码
var jump = function(nums) {
let length = nums.length;
let end = 0;
let maxPosition = 0;
let steps = 0;
for (let i = 0; i < length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;
}
总结
贪心算法: 就是以局部的最优解,得到整体的最优解。
判断何种题目能使用贪心。
条件为:
1、该问题的全局最优解可以通过一步又一步获取的多个局部最优解而得到。
2、该问题包含多个子问题,而对子问题求解最优解有助于得到该问题的最优解。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情