题目大意:就是给定一个非负数组,求到最后一个的最小跳数。每次跳动范围不超过当前索引的数值。举个栗子,当前在0号位置,最多只能跳到+2,也就是2号位置。
题目分析:第一感觉,就是个动态规划嘛。很快动态转移方程:dp[j] = min(dp[j], dp[i]+1) ,i = 0....j。于是很快写出了以下代码:
class Solution {
public:
int jump(vector<int>& nums) {
int len = nums.size();
if(len==0 || len==1) return 0;
int *dp = (int*)malloc(sizeof(int)*len);
memset(dp, 0, sizeof(int)*len);
dp[0] = 0;
for(int i=1;i<len;i++){
dp[i] = INT_MAX-1;
for(int j=i-1;j>=0;j--){ // 往前迭代
if(nums[j] >= i-j){ // 判断从 j 是否能跳动 i
// cout <<dp[j] <<endl;
dp[i] = min(dp[i], dp[j]+1);
}
}
}
//for(int i=0;i<len;i++) cout <<dp[i] <<" ";
//cout <<endl;
return dp[len-1];
}
};
过于简单,但是看到这个作为一个hard级别的题目,感觉会卡时间,果然,在91个样例卡死了。
那既然动态规划O(n^2)的时间复杂度不行,那么就稍微优化一点吧。往前迭代过程中,其实使当前最小的情况就是,在上一个也应该是最小,但是要在能到达当前索引;也就是 j+nums[j] >= i 的前提下,dp[j] 最小。比较直接的方法就是从最小的依次开始取,满足要求即可。这就让我想到了最小堆,正好插入只需要 log(n)的时间,是否可以让这个问题优化一下呢?于是有了下面的代码(正好也是突然想起了之前看到一个STL的容器用法,顺便温习一下):
class Solution {
public:
struct comparemm{ // 重载仿函数
bool operator() (pair<int, int>& a, pair<int,int>& b){
return a.second>b.second;
}
};
int jump(vector<int>& nums) {
int len = nums.size();
if(len==0 || len==1) return 0;
int *dp = (int*)malloc(sizeof(int)*len);
memset(dp, 0, sizeof(int)*len);
// 优先队列,priority_queue<value, contianer, compare>
// value是优先队列类型,container是存储数值的容器,默认是vector, compare比较函数
priority_queue<pair<int,int>, vector<pair<int,int>>, comparemm> bb;
dp[0] = 0;
bb.push(pair<int,int>(0, dp[0])); //放入index和到达该index的跳数
for(int i=1;i<len;i++){
dp[i] = INT_MAX-1;
// 选择跳数可以到达当前 i 的,不满足的即可弹出,因为后面的索引 更达不到
while(!bb.empty() && bb.top().first+nums[bb.top().first]<i) bb.pop();
dp[i] = bb.top().second+1; // 这里就是直接 +1 了
bb.push(pair<int,int>(i, dp[i])); // 将当前这个已求解的加入队列
}
return dp[len-1];
}
};
提交果然是通过了,哈哈哈,不过不太理想,只跑赢了一半的人,16ms。这就说明这道题还可以继续优化呀,那就只有O(n)的复杂度了,考虑能能一次循环就解决呢?考虑正向迭代,每一次的 i 可以更新往后 nums[i] 个位置的数值,得出以下结论:
dp[i] <= dp[j] , j>i,即dp[j] 不可能比它之前的跳数更小。因为此前能到达 j 位置的,一定也能到达 i 位置。那就是就是说,到达每个位置的最小跳数就是等于它第一次(最早一次)被更新的时候的数值。这么时候有点抽象,直接看代码吧:
class Solution {
public:
int jump(vector<int>& nums) {
int len = nums.size();
if(len==0 || len==1) return 0;
int *dp = (int*)malloc(sizeof(int)*len);
memset(dp, 0, sizeof(int)*len);
dp[0] = 0;
int cur = 0; // cur记录当前已经更新的最大位置
for(int i=0;i<len;i++){ // 从 0 开始正向迭代
if(i+nums[i]<=cur) continue; // 如果当前索引无法到达 cur 位置,则不用进行更新
for(int j=cur+1;j<=i+nums[i]&&j<len;j++){//更新从 cur 位置开始,到达i+nums[i]位置
dp[j] = dp[i]+1;
}
cur=i+nums[i]; // 更新 cur 位置
}
return dp[len-1];
}
};
应该已经很快了吧,但是还是只有12ms, 自闭。。。。不玩了