1696. 跳跃游戏 VI
难度 : 中等
题目大意:
给你一个下标从 0 开始的整数数组
nums
和一个整数k
。一开始你在下标
0
处。每一步,你最多可以往前跳k
步,但你不能跳出数组的边界。也就是说,你可以从下标i
跳到[i + 1, min(n - 1, i + k)]
包含 两个端点的任意位置。你的目标是到达数组最后一个位置(下标为
n - 1
),你的 得分 为经过的所有数字之和。请你返回你能得到的 最大得分 。
提示:
1 <= nums.length, k <= 10^5
-104 <= nums[i] <= 10^4
示例 1:
输入:nums = [1,-1,-2,4,-7,3], k = 2 输出:7 解释:你可以选择子序列 [1,-1,4,3] (上面加粗的数字),和为 7 。
分析
定义f[i]
表示从0~i
首尾必须取到的情况下,我们可以得到的最大的数字之和,那么怎么转移呢,我们可以枚举一下从i
可以跳到哪里,从对应的状态转移过来即可,简单算一下时间复杂度,枚举下标是 O ( n ) O(n) O(n),还要枚举从哪里跳,时间是 O ( k ) O(k) O(k),那么时间就是 O ( n k ) O(nk) O(nk),会超时,需要考虑优化
记忆化和递推 代码实现
// 记忆化(会超时!!)
class Solution {
public:int maxResult(vector<int>& nums, int k) {int n = nums.size();vector<int> f(n, INT_MIN);function<int(int)> dfs = [&](int u) -> int{if (u == 0) return nums[0];int& res = f[u];if (res != INT_MIN) return res;int t = INT_MIN;for (int i = 1; i <= k && u - i >= 0; i ++) {t = max(t, dfs(u - i));}return res = t + nums[u];};return dfs(n - 1);}
};// 递推(会超时!!)
class Solution {
public:int maxResult(vector<int>& nums, int k) {int n = nums.size();vector<int> f(n, INT_MIN);f[0] = nums[0];for (int i = 1; i < n; i ++) {int t = INT_MIN;for (int j = 1; j <= k && i - j >= 0; j ++) {t = max(t, f[i- j]);}f[i] = t + nums[i];}return f[n - 1];}
};
时间复杂度: O ( n k ) O(nk) O(nk)
优化
我们发现枚举可以跳到哪一步时,我们就是再找从[max(0, i - k - 1), i - 1]
这一段区间中找最大值,而且k
是固定的,那么怎么快速算出这一段的最大值呢,单调队列!!!,单调队列可以动态求一段区间的最大值或者最小值
单调队列模板
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口while (hh <= tt && check(q[tt], i)) tt -- ;q[ ++ tt] = i;
}
单调队列优化dp
class Solution {
public:int maxResult(vector<int>& nums, int k) {int n = nums.size();int hh = 0, tt = -1;int q[n + 1];memset(q, 0, sizeof q);vector<int> f(n);for (int i = 0; i < n; i ++) {if (q[hh] < i - k) hh ++;f[i] = f[q[hh]] + nums[i];while (hh <= tt && f[q[tt]] <= f[i]) tt --;q[++ tt] = i;}return f[n - 1];}
};
时间复杂度: O ( n ) O(n) O(n)
结束了