跳到主要内容

2140.解决智力问题

· 阅读需 8 分钟

1、题干

给你一个下标从 0 开始的二维整数数组 questions ,其中 questions[i] = [pointsi, brainpoweri] 。

这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题 i 将让你 获得  pointsi 的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri 个问题)。如果你跳过问题 i ,你可以对下一个问题决定使用哪种操作。

  • 比方说,给你 questions = [[3, 2], [4, 3], [4, 4], [2, 5]] :
    • 如果问题 0 被解决了, 那么你可以获得 3 分,但你不能解决问题 1 和 2 。
    • 如果你跳过问题 0 ,且解决问题 1 ,你将获得 4 分但是不能解决问题 2 和 3 。

请你返回这场考试里你能获得的 最高 分数。

 

示例 1:

输入:questions = [[3,2],[4,3],[4,4],[2,5]]
输出:5
解释:解决问题 0 和 3 得到最高分。
- 解决问题 0 :获得 3 分,但接下来 2 个问题都不能解决。
- 不能解决问题 1 和 2
- 解决问题 3 :获得 2 分
总得分为:3 + 2 = 5 。没有别的办法获得 5 分或者多于 5 分。

示例 2:

输入:questions = [[1,1],[2,2],[3,3],[4,4],[5,5]]
输出:7
解释:解决问题 1 和 4 得到最高分。
- 跳过问题 0
- 解决问题 1 :获得 2 分,但接下来 2 个问题都不能解决。
- 不能解决问题 2 和 3
- 解决问题 4 :获得 5 分
总得分为:2 + 5 = 7 。没有别的办法获得 7 分或者多于 7 分。

 

提示:

  • 1 <= questions.length <= 105
  • questions[i].length == 2
  • 1 <= pointsi, brainpoweri <= 105

动态规划没学明白,周赛坑了一小时没写出来,可能我智力有问题吧

13375743a4d4704fbb667f6be417fd85.gif

2、解法1-记忆化搜索

  • 每个问题都有解决和跳过两种选择
  • 对于任意问题 ii 如果解决则得分叠加下一道可解的题即 dfs(i + 1 + questions[i][1]) + questions[i][0])
  • 如果跳过则得分与下一道题相同即 dfs(i + 1)
  • 题目要求最高分数因此取二者的最大值,如此深度遍历下去直至结束
  • 由于每个问题都有2种状态,因此时间复杂度接近 O(2n)O(2^n),为了避免超时使用哈希表记录已搜索过的题目后续直接返回

一般动态规划的题都能用暴力穷举(DFS、BFS、回溯等)解决,如果实在想不到动态规划的解题思路,可以尝试暴力穷举+记忆化。


3、复杂度

  • 时间复杂度:不使用记忆化时为 O(2n)O(2^n),使用记忆化后无法确定
  • 空间复杂度:O(n)O(n)

4、代码

var mostPoints = function (questions) {
const n = questions.length, visited = new Map();
function dfs(idx) {
if (idx > n - 1) return 0;
if (visited.has(idx)) return visited.get(idx);
const [score, skip] = questions[idx];
return visited.set(idx, Math.max(dfs(idx + 1), dfs(idx + 1 + skip) + score)).get(idx);
}
return dfs(0);
};

5、执行结果

执行用时: 360 ms 内存消耗: 91.8 MB


6、解法2-顺序DP

  • 对于任意问题 ii 而言 dp[i]dp[i] 表示完成 [0,i][0,i] 题能获得的最高分,另外每个问题都有解决和跳过两种选择
  • 如果解决问题 ii 则其得分 dp[i]+questions[i][0]dp[i] + questions[i][0] 可以转移给下一道可解的题 jj 使用(其中 j=questions[i][1]+i+1j = questions[i][1] + i + 1),转移时 dp[j]dp[j] 取二者最大值即 dp[j]=Math.max(dp[i]+questions[i][0],dp[j])dp[j] = Math.max(dp[i] + questions[i][0], dp[j])
  • 如果跳过问题 ii 则其得分 dp[i]dp[i] 可以转移给下一道题 i+1i + 1 使用,转移时 dp[i+1]dp[i + 1] 取二者最大值即 dp[i+1]=Math.max(dp[i],dp[i+1]);dp[i + 1] = Math.max(dp[i], dp[i + 1]);

注意:\ 一般动态规划按以上思路遍历下去最终返回 dpdp 数组末尾元素即可,但这里会出现一种特殊情况:最高分在 dpdp 数组中的索引可能大于 questions.lengthquestions.length。 例如用例:[[25,1],[44,3],[91,1],[12,3],[93,3]] 的长度为5,而最高分出现在 dp[8]dp[8]

因此使用变量 max 在动态规划过程中记录所有得分中的最高分并返回,最后AC了。

所以最后注意动态规划过程中处理好最高得分数组越界的问题就能AC


7、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

8、代码

var mostPoints = function (questions) {
const n = questions.length, dp = new Array(n).fill(0);
let max = 0;
for (let i = 0; i < n; i++) {
dp[i + 1] = Math.max(dp[i], dp[i + 1] || 0);
const j = questions[i][1] + i + 1;
dp[j] = Math.max(dp[i] + questions[i][0], dp[j] || 0);
max = Math.max(dp[i + 1], dp[j], max);
}
return max;
};

9、执行结果

执行用时: 280 ms 内存消耗: 69.8 MB


10、解法3-逆序DP

看了些资料才发现,逆序DP也是一种解题方法,并不是特例

  • 逆序DP中状态转移是相反的,即将靠后问题的得分转移给前面的问题,对于任意问题 ii 而言 dp[i]dp[i] 表示完成 [i,n1][i,n-1] 题能获得的最高分
  • 如果选择跳过问题 ii,则将下一题得分 dp[i+1]dp[i + 1] 转移给 dp[i]dp[i]
  • 如果选择解决问题 ii,则将下一可解题得分 dp[questions[i][1]+i+1]dp[questions[i][1] + i + 1] 转移给 dp[i]dp[i],并叠加当前题得分 questions[i][0]questions[i][0]
  • 依次处理所有问题,最后返回 dp[0]dp[0]

11、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

12、代码

var mostPoints = function (questions) {
const n = questions.length, dp = new Array(n).fill(0);
for (let i = n - 1; i > -1; i--) {
dp[i] = Math.max(dp[i + 1] || 0, questions[i][0] + (dp[questions[i][1] + i + 1] || 0));
}
return dp[0];
};

13、执行结果

执行用时: 176 ms 内存消耗: 65 MB