leetcode题集: 45. Jump Game II

24 篇文章 0 订阅
该博客探讨了LeetCode中的45题——跳跃游戏II,作者首先尝试使用动态规划解题,但发现其时间复杂度导致无法在限定时间内完成。接着,作者转向使用最小堆优化算法,通过维护最小值并利用STL容器,实现了更快的解决方案。尽管最终代码在时间效率上有所提升,但仍有优化空间。
摘要由CSDN通过智能技术生成

题目大意:就是给定一个非负数组,求到最后一个的最小跳数。每次跳动范围不超过当前索引的数值。举个栗子,当前在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, 自闭。。。。不玩了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值