LeetCode1696 Jump Game VI

 题意:给出一个大小为n的序列,以标号0作为起点开始跳,每次最多向右移动k个位置,求到达序列最右端时所经过所有点的权值之和最大为多少。

首先上经典dp,转移公式为f[i] = max(f[i-j]+nums[i], f[i]),其中j∈[1,k],得到O(n*k)复杂度的向右递推,数据范围不支持。

考虑维护每一段长度为k的最值,也就是利用数据结构存储f[i-k]到f[i],然后每次取最大值。考虑到效率因素,一般情况下对于最大值的维护heap(priority_queue)比set好用,但是由于维护过程需要及时清理失效的f[i-k],堆并不支持复杂的删除操作,因此还是需要用到set。又因为此题数据的特点,可能存在f[i-k]到f[i]之间有相同数值的情况,需要使用效率更低的multiset。

这次的复杂度是O(n*logn),但一般而言,C++的STL中set与multiset效率极低,因此只能作为水题方法。

#include <set>
#define INF 1000000000
class Solution {
public:
    int maxResult(vector<int>& nums, int k) {
        int sz = nums.size();
        vector<int>f(nums.size(), -INF);
        multiset<int>s;
        multiset<int>::iterator it;
        f[0] = nums[0];
        s.insert(f[0]);
        int last;
        for (int i = 1; i < nums.size(); ++i){
            it = s.end();
            it--;
            last = *it;
            f[i] = max(f[i], nums[i]+last);
            if (s.size() == k){
                it = s.find(f[i-k]);
                s.erase(it);
            }
            s.insert(f[i]);
        }
        return f[sz-1];
    }
};

运行结果:

考虑进一步的优化,每次向右移动一位,同时需要维护一段长度为k的最大值。也就是最终目的是求这个最大值。一般最大值会保持不变,改变的可能有两种,一是之前f[i-k]是最大值,然后因为i右移使得这个最大值失效了;二是新的f[i]成为了最大值。对于第一种,如果能找到一种方法使得数据结构里面能够快速找到一个次大值,则最大值即使消失也只需要O(1)的代价来保证新的最大值的产生,而第二种可能的解决方法更为简单,只需要将整个数据结构清空即可。

在需要维护的数据结构中,最大值与次大值大小关系是可以传递的,也就是说对于次大值,同样需要相应的比次大值小一点的值来作为候补,那么答案呼之欲出,我们只需要维护一个递减的序列,每次新的值要加入的时候,若是序列最右端比它小,就把这个元素删除掉,直到最新的f[i]成为某个原来元素的次大值、或是成为新的最大值为止,这样仍然保证了整个序列是递减的。同时,当最左端的元素失效的时候,需要将它删除。

于是采用双端队列deque来实现这个数据结构。不难看出整个过程是O(n)的,因为每个元素只进队出队最多一次。deque中记录点的序号index,否则无法知道队头会在何时失效

class Solution {
public:
    int maxResult(vector<int>& nums, int k) {
        deque<int>dq;
        dq.push_back(0);
        int sz = nums.size();
        vector<int>f(sz);
        f[0] = nums[0];
        for (int i = 1; i < sz; ++i){
            int now = dq.front();
            f[i] = f[now] + nums[i];
            while(!dq.empty() && f[dq.back()] < f[i])
                dq.pop_back();
            dq.push_back(i);
            if (i-dq.front() >= k)
                dq.pop_front();
        }
        return f[sz-1];
    }
};

最终结果如下:

可能是太久没做题了,感觉可以的东西就直接上了set、map等等。前几天有道题也是维护区间和,直接无脑就要上线段树,最后想了想树状数组又短又快为什么不呢?

参考链接:https://leetcode.com/problems/jump-game-vi/discuss/1260843/C++JavaPython-DP-and-Decreasing-Deque-Clean-and-Concise-Time-O(N)-Space-O(K)

在leetcode上面做题的人经常为了节省空间,直接对函数中的实参进行覆盖利用,个人认为这并不是一个好的习惯。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值