Week7
Greedy
question source: Jump Game
question description
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
Example 1:
Input: [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:
Input: [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum jump length is 0, which makes it impossible to reach the last index.
这是一道标签为贪心算法的题。这周开始学习了贪心算法,讲了一些贪心算法的证明。这道题是说,给定一个数组,标记着它所能走的最大步,从数组0出发,判断它是否能到达数组的最后一项。
解决方法
因为上周学的是图的内容,这很明显能构成一个有向图,所以就能用遍历的方法来实现。于是我先尝试用DFS算法从0开始遍历这个图,最后判断到底有没有访问最后一个。
实现代码如下
class Solution {
public:
bool canJump(vector<int>& nums) {
int last_index = nums.size() - 1;
int visited[nums.size()];
memset(visited, 0, sizeof(visited));
travel(0, nums, visited);
return (visited[last_index] == 1);
}
void travel(int node, vector<int>& nums, int* visited){
visited[node] = 1;
//if(nums[node] == 0) return;
for(int i = 1; i <= nums[node]; i++){
if((node + i < nums.size()) && visited[node + i] == 0){
travel(node + i, nums, visited);
}
}
}
};
要注意数组的越界问题,它有可能到达的最远是超过数组的最大下标值的。运行结果是超过10% 的C++代码。这也是意料之中,不然为什么放在Greedy里面呢。
于是就要思考怎么贪心了。每次走最大步,嗯,这很贪心了。算法就是说,一开始放一个0在点集S中,对于S中每一个点,求点所能到达的最远的地方,然后把最远的地方的点前面的,还没加入到S中的点,都加进S中。重复这个过程,直到最后一个点在S中,或者S集不变。若S包含最后一个点,则说明能到达,若S集在经历这个过程中不变,说明只能跳这么远了,不能到达最后一个点。
实现代码如下
class Solution {
public:
bool canJump(vector<int>& nums) {
if(nums.size() == 1) return true;
int last = nums.size() - 1;
int begin = 0;
int max = 0;
int best = 0;
while(true){
best = 0;
for(int i = begin; i <= max; i++){
if(nums[i] + i > best){
best = nums[i] + i;
}
}
//can not forward
if(best == max) return false;
begin = max;
max = best;
if(max >= last) return true;
}
}
};
结果:超过98%C++代码。
这个算法不但能判断它是否能到达,同时也能算出最少需要跳几次。
好,我们看这一题的升级版。
question source: Jump Game II
就是要我们求最小能跳几次。
证明
算法就是说,选跳最远的点,然后在剩余的点中选最优解。
这想想就很正确啊,但现实就是这样,就是要你证明,那就勉为其难地证证吧。
- 算法是安全的。S集是0所能跳的最远的集合,则一定有某个最优解包含S集中的一个元素。
- 问题具有最优子结构。
综上证毕。
代码实现
只要在上面的基础上,记录while循环的次数就可以了。
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size() == 1) return 0;
int last = nums.size() - 1;
int begin = 0;
int max = 0;
int best = 0;
int step = 0;
while(true){
step++;
best = 0;
for(int i = begin; i <= max; i++){
if(nums[i] + i > best){
best = nums[i] + i;
}
}
begin = max;
max = best;
if(max >= last) return step;
}
}
};
结果是超过40%C++算法。
DFS算法的复杂度O(V+E), 而贪心算法的复杂度是O(n),问题是这个图是个稠密图,E比较大,所以花费的时间要比贪心算法长很多。